Skip to content

Commit bd583a8

Browse files
adam77caitp
authored andcommitted
fix($drag): offset dragged element relative to first positioned ancestor
Notes: - $dndDOM contains DOM helpers based on those from angular-ui/bootstrap and jQuery - Removed draggable.positionAbs - Fix issue when using jqLite's css implementation (angular/angular.js#11614) - Manually tested on IE9 Closes #64 Closes #63
1 parent f4a7dfc commit bd583a8

File tree

6 files changed

+233
-145
lines changed

6 files changed

+233
-145
lines changed

angularDrop.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
angularDropFiles = {
22
'angularDropSrc': [
33
'src/utils.js',
4+
'src/dndDOM.js',
45
'src/draggable.js',
56
'src/droppable.js',
67
'src/provider.js',

src/dndDOM.js

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc object
5+
* @module ui.drop
6+
* @name ui.drop.$dndDOM
7+
* @requires $document
8+
* @requires $window
9+
*
10+
* @description
11+
*
12+
* A set of utility methods that can be use to retrieve and manipulate DOM properties.
13+
*
14+
* (based on https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js)
15+
*
16+
*/
17+
var $dndDOMFactory = ['$document', '$window', function ($document, $window) {
18+
19+
function getStyle(el, cssprop) {
20+
if (el.currentStyle) { //IE
21+
return el.currentStyle[cssprop];
22+
} else if ($window.getComputedStyle) {
23+
return $window.getComputedStyle(el)[cssprop];
24+
}
25+
// finally try and get inline style
26+
return el.style[cssprop];
27+
}
28+
29+
/**
30+
* Checks if a given element is statically positioned
31+
* @param element - raw DOM element
32+
*/
33+
function isStaticPositioned(element) {
34+
return (getStyle(element, 'position') || 'static' ) === 'static';
35+
}
36+
37+
/**
38+
* returns the closest, non-statically positioned parentOffset of a given element
39+
* @param element
40+
*/
41+
var parentOffsetEl = function (element) {
42+
var docDomEl = $document[0];
43+
var offsetParent = element.offsetParent || docDomEl;
44+
while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
45+
offsetParent = offsetParent.offsetParent;
46+
}
47+
return offsetParent || docDomEl;
48+
};
49+
50+
function contains(a, b) {
51+
var adown = a.nodeType === 9 ? a.documentElement : a,
52+
bup = b && b.parentNode;
53+
return a === bup || !!( bup && bup.nodeType === 1 && contains(adown, bup) );
54+
};
55+
56+
function swapCss(element, css, callback, args) {
57+
var ret, prop, old = {};
58+
for (prop in css) {
59+
old[prop] = element.style[prop];
60+
element.style[prop] = css[prop];
61+
}
62+
63+
ret = callback.apply(element, args || []);
64+
65+
for (prop in css) {
66+
element.style[prop] = old[prop];
67+
}
68+
69+
return ret;
70+
};
71+
72+
var swapDisplay = /^(none|table(?!-c[ea]).+)/;
73+
74+
var cssShow = {
75+
position: 'absolute',
76+
visibility: 'hidden',
77+
display: 'block'
78+
};
79+
80+
return {
81+
/**
82+
* Provides read-only equivalent of jQuery's position function:
83+
* http://api.jquery.com/position/
84+
*/
85+
position: function (element) {
86+
var elBCR = this.offset(element);
87+
var offsetParentBCR = { top: 0, left: 0 };
88+
var offsetParentEl = parentOffsetEl(element[0]);
89+
if (offsetParentEl != $document[0]) {
90+
offsetParentBCR = this.offset(angular.element(offsetParentEl));
91+
offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
92+
offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
93+
}
94+
95+
var boundingClientRect = element[0].getBoundingClientRect();
96+
return {
97+
width: boundingClientRect.width || element.prop('offsetWidth'),
98+
height: boundingClientRect.height || element.prop('offsetHeight'),
99+
top: elBCR.top - offsetParentBCR.top,
100+
left: elBCR.left - offsetParentBCR.left
101+
};
102+
},
103+
104+
/**
105+
* Provides read-only equivalent of jQuery's offset function:
106+
* http://api.jquery.com/offset/
107+
*/
108+
offset: function (element) {
109+
110+
var doc = element[0] && element[0].ownerDocument;
111+
112+
if (!doc) {
113+
return;
114+
}
115+
116+
doc = doc.documentElement;
117+
118+
if (!contains(doc, element[0])) {
119+
return { top: 0, left: 0 };
120+
}
121+
122+
var boundingClientRect = element[0].getBoundingClientRect();
123+
return {
124+
width: boundingClientRect.width || element.prop('offsetWidth'),
125+
height: boundingClientRect.height || element.prop('offsetHeight'),
126+
top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
127+
left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
128+
};
129+
},
130+
131+
/**
132+
* Partial implementation of https://api.jquery.com/closest/
133+
* @param element
134+
* @param value
135+
* @returns {angular.element}
136+
*/
137+
closest: function(element, value) {
138+
element = angular.element(element);
139+
if ($.fn && angular.isFunction($.fn.closest)) {
140+
return element.closest(value);
141+
}
142+
// Otherwise, assume it's a tag name...
143+
element = element[0];
144+
value = value.toLowerCase();
145+
do {
146+
if (element.nodeName.toLowerCase() === value) {
147+
return angular.element(element);
148+
}
149+
} while (element = element.parentNode);
150+
},
151+
152+
size: function(element) {
153+
var jq = angular.element(element);
154+
element = element.nodeName ? element : element[0];
155+
if (element.offsetWidth === 0 && swapDisplay.test(jq.css('display'))) {
156+
return swapCss(element, cssShow, getHeightAndWidth, [element]);
157+
}
158+
return getHeightAndWidth(element);
159+
160+
function getHeightAndWidth(element) {
161+
return {
162+
width: element.offsetWidth,
163+
height: element.offsetHeight
164+
};
165+
}
166+
},
167+
168+
keepSize: function(element) {
169+
var css = this.size(element);
170+
css.width = css.width + 'px';
171+
css.height = css.height + 'px';
172+
return css;
173+
}
174+
};
175+
}];

src/draggable.js

+33-33
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ var $dragProvider = function() {
6060
* a mechanism to drag-enable any arbitrary element, which allows it to be used in
6161
* custom directives, so that custom dragging behaviour can be achieved.
6262
*/
63-
this.$get = ['$document', '$drop', function($document, $drop) {
63+
this.$get = ['$document', '$drop', '$dndDOM', function($document, $drop, $dndDOM) {
6464
var $drag = {
6565
/**
6666
* @ngdoc method
@@ -197,7 +197,7 @@ var $dragProvider = function() {
197197
currentDrag = self;
198198

199199
self.cssDisplay = self.element.css('display');
200-
self.dimensions = DOM.size(self.element);
200+
self.dimensions = $dndDOM.size(self.element);
201201
if (!self.hanging) {
202202
self.cssPosition = self.element.css("position");
203203

@@ -206,23 +206,23 @@ var $dragProvider = function() {
206206
width: self.element[0].style['width'],
207207
height: self.element[0].style['height']
208208
};
209-
self.element.css(DOM.keepSize(self.element));
209+
self.element.css($dndDOM.keepSize(self.element));
210210
}
211211
}
212212

213213
if (typeof self.options.dragWithin === 'string') {
214214
self.constraints = dragConstraints(self.options.dragWithin, self.element);
215215
}
216216

217-
self.offset = self.positionAbs = DOM.offset(self.element);
217+
self.offset = $dndDOM.position(self.element);
218218

219219
self.offset.scroll = false;
220220

221221
angular.extend(self.offset, {
222222
click: {
223223
top: event.pageY - self.offset.top,
224224
left: event.pageX - self.offset.left
225-
},
225+
}
226226
});
227227

228228
self.lastMouseY = event.pageY;
@@ -232,8 +232,8 @@ var $dragProvider = function() {
232232

233233
self.element.css({
234234
position: 'absolute',
235-
left: self.offset.left,
236-
top: self.offset.top
235+
left: self.offset.left + 'px', //jqlite does not support raw Number
236+
top: self.offset.top + 'px'
237237
});
238238

239239
$document.on("mousemove", self.drag);
@@ -354,6 +354,32 @@ var $dragProvider = function() {
354354
}
355355
};
356356

357+
function dragConstraints(value, element) {
358+
if (value.length === '') {
359+
return;
360+
}
361+
if (/^(\.|#)/.test(value) && $.fn && angular.isFunction($.fn.closest)) {
362+
// Find nearest element matching class
363+
return constraintsFor(element.parent().closest(value));
364+
}
365+
if (/^(\^|parent)$/.test(value)) {
366+
return constraintsFor(element.parent());
367+
}
368+
return constraintsFor($dndDOM.closest(element.parent(), value));
369+
};
370+
371+
function constraintsFor(element) {
372+
if (typeof element === 'undefined' || element.length === 0) {
373+
return;
374+
}
375+
// Use offset + width/height for now?
376+
var constraints = $dndDOM.offset(element),
377+
dimensions = $dndDOM.size(element);
378+
constraints.right = constraints.left + dimensions.width;
379+
constraints.bottom = constraints.top + dimensions.height;
380+
return constraints;
381+
};
382+
357383
/**
358384
* @ngdoc property
359385
* @module ui.drop
@@ -409,32 +435,6 @@ var $dragProvider = function() {
409435
return angular.isObject(thing) && thing.constructor === Draggable;
410436
}
411437

412-
function dragConstraints(value, element) {
413-
if (value.length === '') {
414-
return;
415-
}
416-
if (/^(\.|#)/.test(value) && $.fn && angular.isFunction($.fn.closest)) {
417-
// Find nearest element matching class
418-
return constraintsFor(element.parent().closest(value));
419-
}
420-
if (/^(\^|parent)$/.test(value)) {
421-
return constraintsFor(element.parent());
422-
}
423-
return constraintsFor(DOM.closest(element.parent(), value));
424-
}
425-
426-
function constraintsFor(element) {
427-
if (typeof element === 'undefined' || element.length === 0) {
428-
return;
429-
}
430-
// Use offset + width/height for now?
431-
var constraints = DOM.offset(element),
432-
dimensions = DOM.size(element);
433-
constraints.right = constraints.left + dimensions.width;
434-
constraints.bottom = constraints.top + dimensions.height;
435-
return constraints;
436-
}
437-
438438
function withinConstraints(c, x, y, w, h) {
439439
return x >= c.left && (x+w) <= c.right && y >= c.top && (y+h) <= c.bottom;
440440
}

src/public.js

+2
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ function publishExternalAPI() {
1010
}]).directive({
1111
draggable: draggableDirective,
1212
droppable: droppableDirective
13+
}).factory({
14+
$dndDOM: $dndDOMFactory
1315
});
1416
}

0 commit comments

Comments
 (0)