PluginProbe
RSVP and Event Management / 1.3.0
RSVP and Event Management v1.3.0
trunk 0.5.0 0.6.0 0.7.0 0.8.0 0.9.0 0.9.5 1.0.0 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.5.0 1.6.0 1.6.1 1.6.2 1.6.5 1.7.0 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 All 136 releases
rsvp / jquery-ui-1.7.2.custom / development-bundle / ui / ui.sortable.js

ui.sortable.js in RSVP and Event Management 1.3.0, at jquery-ui-1.7.2.custom/development-bundle/ui/ui.sortable.js

1,020 lines 37.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * jQuery UI Sortable 1.7.2
3 *
4 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT (MIT-LICENSE.txt)
6 * and GPL (GPL-LICENSE.txt) licenses.
7 *
8 * http://docs.jquery.com/UI/Sortables
9 *
10 * Depends:
11 * ui.core.js
12 */
13 (function($) {
14
15 $.widget("ui.sortable", $.extend({}, $.ui.mouse, {
16 _init: function() {
17
18 var o = this.options;
19 this.containerCache = {};
20 this.element.addClass("ui-sortable");
21
22 //Get the items
23 this.refresh();
24
25 //Let's determine if the items are floating
26 this.floating = this.items.length ? (/left|right/).test(this.items[0].item.css('float')) : false;
27
28 //Let's determine the parent's offset
29 this.offset = this.element.offset();
30
31 //Initialize mouse events for interaction
32 this._mouseInit();
33
34 },
35
36 destroy: function() {
37 this.element
38 .removeClass("ui-sortable ui-sortable-disabled")
39 .removeData("sortable")
40 .unbind(".sortable");
41 this._mouseDestroy();
42
43 for ( var i = this.items.length - 1; i >= 0; i-- )
44 this.items[i].item.removeData("sortable-item");
45 },
46
47 _mouseCapture: function(event, overrideHandle) {
48
49 if (this.reverting) {
50 return false;
51 }
52
53 if(this.options.disabled || this.options.type == 'static') return false;
54
55 //We have to refresh the items data once first
56 this._refreshItems(event);
57
58 //Find out if the clicked node (or one of its parents) is a actual item in this.items
59 var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
60 if($.data(this, 'sortable-item') == self) {
61 currentItem = $(this);
62 return false;
63 }
64 });
65 if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target);
66
67 if(!currentItem) return false;
68 if(this.options.handle && !overrideHandle) {
69 var validHandle = false;
70
71 $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
72 if(!validHandle) return false;
73 }
74
75 this.currentItem = currentItem;
76 this._removeCurrentsFromItems();
77 return true;
78
79 },
80
81 _mouseStart: function(event, overrideHandle, noActivation) {
82
83 var o = this.options, self = this;
84 this.currentContainer = this;
85
86 //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
87 this.refreshPositions();
88
89 //Create and append the visible helper
90 this.helper = this._createHelper(event);
91
92 //Cache the helper size
93 this._cacheHelperProportions();
94
95 /*
96 * - Position generation -
97 * This block generates everything position related - it's the core of draggables.
98 */
99
100 //Cache the margins of the original element
101 this._cacheMargins();
102
103 //Get the next scrolling parent
104 this.scrollParent = this.helper.scrollParent();
105
106 //The element's absolute position on the page minus margins
107 this.offset = this.currentItem.offset();
108 this.offset = {
109 top: this.offset.top - this.margins.top,
110 left: this.offset.left - this.margins.left
111 };
112
113 // Only after we got the offset, we can change the helper's position to absolute
114 // TODO: Still need to figure out a way to make relative sorting possible
115 this.helper.css("position", "absolute");
116 this.cssPosition = this.helper.css("position");
117
118 $.extend(this.offset, {
119 click: { //Where the click happened, relative to the element
120 left: event.pageX - this.offset.left,
121 top: event.pageY - this.offset.top
122 },
123 parent: this._getParentOffset(),
124 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
125 });
126
127 //Generate the original position
128 this.originalPosition = this._generatePosition(event);
129 this.originalPageX = event.pageX;
130 this.originalPageY = event.pageY;
131
132 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
133 if(o.cursorAt)
134 this._adjustOffsetFromHelper(o.cursorAt);
135
136 //Cache the former DOM position
137 this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
138
139 //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
140 if(this.helper[0] != this.currentItem[0]) {
141 this.currentItem.hide();
142 }
143
144 //Create the placeholder
145 this._createPlaceholder();
146
147 //Set a containment if given in the options
148 if(o.containment)
149 this._setContainment();
150
151 if(o.cursor) { // cursor option
152 if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
153 $('body').css("cursor", o.cursor);
154 }
155
156 if(o.opacity) { // opacity option
157 if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
158 this.helper.css("opacity", o.opacity);
159 }
160
161 if(o.zIndex) { // zIndex option
162 if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
163 this.helper.css("zIndex", o.zIndex);
164 }
165
166 //Prepare scrolling
167 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
168 this.overflowOffset = this.scrollParent.offset();
169
170 //Call callbacks
171 this._trigger("start", event, this._uiHash());
172
173 //Recache the helper size
174 if(!this._preserveHelperProportions)
175 this._cacheHelperProportions();
176
177
178 //Post 'activate' events to possible containers
179 if(!noActivation) {
180 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
181 }
182
183 //Prepare possible droppables
184 if($.ui.ddmanager)
185 $.ui.ddmanager.current = this;
186
187 if ($.ui.ddmanager && !o.dropBehaviour)
188 $.ui.ddmanager.prepareOffsets(this, event);
189
190 this.dragging = true;
191
192 this.helper.addClass("ui-sortable-helper");
193 this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
194 return true;
195
196 },
197
198 _mouseDrag: function(event) {
199
200 //Compute the helpers position
201 this.position = this._generatePosition(event);
202 this.positionAbs = this._convertPositionTo("absolute");
203
204 if (!this.lastPositionAbs) {
205 this.lastPositionAbs = this.positionAbs;
206 }
207
208 //Do scrolling
209 if(this.options.scroll) {
210 var o = this.options, scrolled = false;
211 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
212
213 if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
214 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
215 else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
216 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
217
218 if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
219 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
220 else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
221 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
222
223 } else {
224
225 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
226 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
227 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
228 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
229
230 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
231 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
232 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
233 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
234
235 }
236
237 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
238 $.ui.ddmanager.prepareOffsets(this, event);
239 }
240
241 //Regenerate the absolute position used for position checks
242 this.positionAbs = this._convertPositionTo("absolute");
243
244 //Set the helper position
245 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
246 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
247
248 //Rearrange
249 for (var i = this.items.length - 1; i >= 0; i--) {
250
251 //Cache variables and intersection, continue if no intersection
252 var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
253 if (!intersection) continue;
254
255 if(itemElement != this.currentItem[0] //cannot intersect with itself
256 && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
257 && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
258 && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
259 ) {
260
261 this.direction = intersection == 1 ? "down" : "up";
262
263 if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
264 this._rearrange(event, item);
265 } else {
266 break;
267 }
268
269 this._trigger("change", event, this._uiHash());
270 break;
271 }
272 }
273
274 //Post events to containers
275 this._contactContainers(event);
276
277 //Interconnect with droppables
278 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
279
280 //Call callbacks
281 this._trigger('sort', event, this._uiHash());
282
283 this.lastPositionAbs = this.positionAbs;
284 return false;
285
286 },
287
288 _mouseStop: function(event, noPropagation) {
289
290 if(!event) return;
291
292 //If we are using droppables, inform the manager about the drop
293 if ($.ui.ddmanager && !this.options.dropBehaviour)
294 $.ui.ddmanager.drop(this, event);
295
296 if(this.options.revert) {
297 var self = this;
298 var cur = self.placeholder.offset();
299
300 self.reverting = true;
301
302 $(this.helper).animate({
303 left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
304 top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
305 }, parseInt(this.options.revert, 10) || 500, function() {
306 self._clear(event);
307 });
308 } else {
309 this._clear(event, noPropagation);
310 }
311
312 return false;
313
314 },
315
316 cancel: function() {
317
318 var self = this;
319
320 if(this.dragging) {
321
322 this._mouseUp();
323
324 if(this.options.helper == "original")
325 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
326 else
327 this.currentItem.show();
328
329 //Post deactivating events to containers
330 for (var i = this.containers.length - 1; i >= 0; i--){
331 this.containers[i]._trigger("deactivate", null, self._uiHash(this));
332 if(this.containers[i].containerCache.over) {
333 this.containers[i]._trigger("out", null, self._uiHash(this));
334 this.containers[i].containerCache.over = 0;
335 }
336 }
337
338 }
339
340 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
341 if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
342 if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
343
344 $.extend(this, {
345 helper: null,
346 dragging: false,
347 reverting: false,
348 _noFinalSort: null
349 });
350
351 if(this.domPosition.prev) {
352 $(this.domPosition.prev).after(this.currentItem);
353 } else {
354 $(this.domPosition.parent).prepend(this.currentItem);
355 }
356
357 return true;
358
359 },
360
361 serialize: function(o) {
362
363 var items = this._getItemsAsjQuery(o && o.connected);
364 var str = []; o = o || {};
365
366 $(items).each(function() {
367 var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
368 if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
369 });
370
371 return str.join('&');
372
373 },
374
375 toArray: function(o) {
376
377 var items = this._getItemsAsjQuery(o && o.connected);
378 var ret = []; o = o || {};
379
380 items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
381 return ret;
382
383 },
384
385 /* Be careful with the following core functions */
386 _intersectsWith: function(item) {
387
388 var x1 = this.positionAbs.left,
389 x2 = x1 + this.helperProportions.width,
390 y1 = this.positionAbs.top,
391 y2 = y1 + this.helperProportions.height;
392
393 var l = item.left,
394 r = l + item.width,
395 t = item.top,
396 b = t + item.height;
397
398 var dyClick = this.offset.click.top,
399 dxClick = this.offset.click.left;
400
401 var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
402
403 if( this.options.tolerance == "pointer"
404 || this.options.forcePointerForContainers
405 || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
406 ) {
407 return isOverElement;
408 } else {
409
410 return (l < x1 + (this.helperProportions.width / 2) // Right Half
411 && x2 - (this.helperProportions.width / 2) < r // Left Half
412 && t < y1 + (this.helperProportions.height / 2) // Bottom Half
413 && y2 - (this.helperProportions.height / 2) < b ); // Top Half
414
415 }
416 },
417
418 _intersectsWithPointer: function(item) {
419
420 var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
421 isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
422 isOverElement = isOverElementHeight && isOverElementWidth,
423 verticalDirection = this._getDragVerticalDirection(),
424 horizontalDirection = this._getDragHorizontalDirection();
425
426 if (!isOverElement)
427 return false;
428
429 return this.floating ?
430 ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
431 : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
432
433 },
434
435 _intersectsWithSides: function(item) {
436
437 var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
438 isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
439 verticalDirection = this._getDragVerticalDirection(),
440 horizontalDirection = this._getDragHorizontalDirection();
441
442 if (this.floating && horizontalDirection) {
443 return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
444 } else {
445 return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
446 }
447
448 },
449
450 _getDragVerticalDirection: function() {
451 var delta = this.positionAbs.top - this.lastPositionAbs.top;
452 return delta != 0 && (delta > 0 ? "down" : "up");
453 },
454
455 _getDragHorizontalDirection: function() {
456 var delta = this.positionAbs.left - this.lastPositionAbs.left;
457 return delta != 0 && (delta > 0 ? "right" : "left");
458 },
459
460 refresh: function(event) {
461 this._refreshItems(event);
462 this.refreshPositions();
463 },
464
465 _connectWith: function() {
466 var options = this.options;
467 return options.connectWith.constructor == String
468 ? [options.connectWith]
469 : options.connectWith;
470 },
471
472 _getItemsAsjQuery: function(connected) {
473
474 var self = this;
475 var items = [];
476 var queries = [];
477 var connectWith = this._connectWith();
478
479 if(connectWith && connected) {
480 for (var i = connectWith.length - 1; i >= 0; i--){
481 var cur = $(connectWith[i]);
482 for (var j = cur.length - 1; j >= 0; j--){
483 var inst = $.data(cur[j], 'sortable');
484 if(inst && inst != this && !inst.options.disabled) {
485 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper"), inst]);
486 }
487 };
488 };
489 }
490
491 queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper"), this]);
492
493 for (var i = queries.length - 1; i >= 0; i--){
494 queries[i][0].each(function() {
495 items.push(this);
496 });
497 };
498
499 return $(items);
500
501 },
502
503 _removeCurrentsFromItems: function() {
504
505 var list = this.currentItem.find(":data(sortable-item)");
506
507 for (var i=0; i < this.items.length; i++) {
508
509 for (var j=0; j < list.length; j++) {
510 if(list[j] == this.items[i].item[0])
511 this.items.splice(i,1);
512 };
513
514 };
515
516 },
517
518 _refreshItems: function(event) {
519
520 this.items = [];
521 this.containers = [this];
522 var items = this.items;
523 var self = this;
524 var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
525 var connectWith = this._connectWith();
526
527 if(connectWith) {
528 for (var i = connectWith.length - 1; i >= 0; i--){
529 var cur = $(connectWith[i]);
530 for (var j = cur.length - 1; j >= 0; j--){
531 var inst = $.data(cur[j], 'sortable');
532 if(inst && inst != this && !inst.options.disabled) {
533 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
534 this.containers.push(inst);
535 }
536 };
537 };
538 }
539
540 for (var i = queries.length - 1; i >= 0; i--) {
541 var targetData = queries[i][1];
542 var _queries = queries[i][0];
543
544 for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
545 var item = $(_queries[j]);
546
547 item.data('sortable-item', targetData); // Data for target checking (mouse manager)
548
549 items.push({
550 item: item,
551 instance: targetData,
552 width: 0, height: 0,
553 left: 0, top: 0
554 });
555 };
556 };
557
558 },
559
560 refreshPositions: function(fast) {
561
562 //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
563 if(this.offsetParent && this.helper) {
564 this.offset.parent = this._getParentOffset();
565 }
566
567 for (var i = this.items.length - 1; i >= 0; i--){
568 var item = this.items[i];
569
570 //We ignore calculating positions of all connected containers when we're not over them
571 if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
572 continue;
573
574 var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
575
576 if (!fast) {
577 item.width = t.outerWidth();
578 item.height = t.outerHeight();
579 }
580
581 var p = t.offset();
582 item.left = p.left;
583 item.top = p.top;
584 };
585
586 if(this.options.custom && this.options.custom.refreshContainers) {
587 this.options.custom.refreshContainers.call(this);
588 } else {
589 for (var i = this.containers.length - 1; i >= 0; i--){
590 var p = this.containers[i].element.offset();
591 this.containers[i].containerCache.left = p.left;
592 this.containers[i].containerCache.top = p.top;
593 this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
594 this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
595 };
596 }
597
598 },
599
600 _createPlaceholder: function(that) {
601
602 var self = that || this, o = self.options;
603
604 if(!o.placeholder || o.placeholder.constructor == String) {
605 var className = o.placeholder;
606 o.placeholder = {
607 element: function() {
608
609 var el = $(document.createElement(self.currentItem[0].nodeName))
610 .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
611 .removeClass("ui-sortable-helper")[0];
612
613 if(!className)
614 el.style.visibility = "hidden";
615
616 return el;
617 },
618 update: function(container, p) {
619
620 // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
621 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
622 if(className && !o.forcePlaceholderSize) return;
623
624 //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
625 if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
626 if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
627 }
628 };
629 }
630
631 //Create the placeholder
632 self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
633
634 //Append it after the actual current item
635 self.currentItem.after(self.placeholder);
636
637 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
638 o.placeholder.update(self, self.placeholder);
639
640 },
641
642 _contactContainers: function(event) {
643 for (var i = this.containers.length - 1; i >= 0; i--){
644
645 if(this._intersectsWith(this.containers[i].containerCache)) {
646 if(!this.containers[i].containerCache.over) {
647
648 if(this.currentContainer != this.containers[i]) {
649
650 //When entering a new container, we will find the item with the least distance and append our item near it
651 var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[i].floating ? 'left' : 'top'];
652 for (var j = this.items.length - 1; j >= 0; j--) {
653 if(!$.ui.contains(this.containers[i].element[0], this.items[j].item[0])) continue;
654 var cur = this.items[j][this.containers[i].floating ? 'left' : 'top'];
655 if(Math.abs(cur - base) < dist) {
656 dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
657 }
658 }
659
660 if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
661 continue;
662
663 this.currentContainer = this.containers[i];
664 itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[i].element, true);
665 this._trigger("change", event, this._uiHash());
666 this.containers[i]._trigger("change", event, this._uiHash(this));
667
668 //Update the placeholder
669 this.options.placeholder.update(this.currentContainer, this.placeholder);
670
671 }
672
673 this.containers[i]._trigger("over", event, this._uiHash(this));
674 this.containers[i].containerCache.over = 1;
675 }
676 } else {
677 if(this.containers[i].containerCache.over) {
678 this.containers[i]._trigger("out", event, this._uiHash(this));
679 this.containers[i].containerCache.over = 0;
680 }
681 }
682
683 };
684 },
685
686 _createHelper: function(event) {
687
688 var o = this.options;
689 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
690
691 if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
692 $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
693
694 if(helper[0] == this.currentItem[0])
695 this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
696
697 if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
698 if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
699
700 return helper;
701
702 },
703
704 _adjustOffsetFromHelper: function(obj) {
705 if(obj.left != undefined) this.offset.click.left = obj.left + this.margins.left;
706 if(obj.right != undefined) this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
707 if(obj.top != undefined) this.offset.click.top = obj.top + this.margins.top;
708 if(obj.bottom != undefined) this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
709 },
710
711 _getParentOffset: function() {
712
713
714 //Get the offsetParent and cache its position
715 this.offsetParent = this.helper.offsetParent();
716 var po = this.offsetParent.offset();
717
718 // This is a special case where we need to modify a offset calculated on start, since the following happened:
719 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
720 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
721 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
722 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
723 po.left += this.scrollParent.scrollLeft();
724 po.top += this.scrollParent.scrollTop();
725 }
726
727 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
728 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
729 po = { top: 0, left: 0 };
730
731 return {
732 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
733 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
734 };
735
736 },
737
738 _getRelativeOffset: function() {
739
740 if(this.cssPosition == "relative") {
741 var p = this.currentItem.position();
742 return {
743 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
744 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
745 };
746 } else {
747 return { top: 0, left: 0 };
748 }
749
750 },
751
752 _cacheMargins: function() {
753 this.margins = {
754 left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
755 top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
756 };
757 },
758
759 _cacheHelperProportions: function() {
760 this.helperProportions = {
761 width: this.helper.outerWidth(),
762 height: this.helper.outerHeight()
763 };
764 },
765
766 _setContainment: function() {
767
768 var o = this.options;
769 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
770 if(o.containment == 'document' || o.containment == 'window') this.containment = [
771 0 - this.offset.relative.left - this.offset.parent.left,
772 0 - this.offset.relative.top - this.offset.parent.top,
773 $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
774 ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
775 ];
776
777 if(!(/^(document|window|parent)$/).test(o.containment)) {
778 var ce = $(o.containment)[0];
779 var co = $(o.containment).offset();
780 var over = ($(ce).css("overflow") != 'hidden');
781
782 this.containment = [
783 co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
784 co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
785 co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
786 co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
787 ];
788 }
789
790 },
791
792 _convertPositionTo: function(d, pos) {
793
794 if(!pos) pos = this.position;
795 var mod = d == "absolute" ? 1 : -1;
796 var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
797
798 return {
799 top: (
800 pos.top // The absolute mouse position
801 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
802 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
803 - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
804 ),
805 left: (
806 pos.left // The absolute mouse position
807 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
808 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
809 - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
810 )
811 };
812
813 },
814
815 _generatePosition: function(event) {
816
817 var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
818
819 // This is another very weird special case that only happens for relative elements:
820 // 1. If the css position is relative
821 // 2. and the scroll parent is the document or similar to the offset parent
822 // we have to refresh the relative offset during the scroll so there are no jumps
823 if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
824 this.offset.relative = this._getRelativeOffset();
825 }
826
827 var pageX = event.pageX;
828 var pageY = event.pageY;
829
830 /*
831 * - Position constraining -
832 * Constrain the position to a mix of grid, containment.
833 */
834
835 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
836
837 if(this.containment) {
838 if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
839 if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
840 if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
841 if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
842 }
843
844 if(o.grid) {
845 var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
846 pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
847
848 var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
849 pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
850 }
851
852 }
853
854 return {
855 top: (
856 pageY // The absolute mouse position
857 - this.offset.click.top // Click offset (relative to the element)
858 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
859 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
860 + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
861 ),
862 left: (
863 pageX // The absolute mouse position
864 - this.offset.click.left // Click offset (relative to the element)
865 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
866 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
867 + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
868 )
869 };
870
871 },
872
873 _rearrange: function(event, i, a, hardRefresh) {
874
875 a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
876
877 //Various things done here to improve the performance:
878 // 1. we create a setTimeout, that calls refreshPositions
879 // 2. on the instance, we have a counter variable, that get's higher after every append
880 // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
881 // 4. this lets only the last addition to the timeout stack through
882 this.counter = this.counter ? ++this.counter : 1;
883 var self = this, counter = this.counter;
884
885 window.setTimeout(function() {
886 if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
887 },0);
888
889 },
890
891 _clear: function(event, noPropagation) {
892
893 this.reverting = false;
894 // We delay all events that have to be triggered to after the point where the placeholder has been removed and
895 // everything else normalized again
896 var delayedTriggers = [], self = this;
897
898 // We first have to update the dom position of the actual currentItem
899 // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
900 if(!this._noFinalSort && this.currentItem[0].parentNode) this.placeholder.before(this.currentItem);
901 this._noFinalSort = null;
902
903 if(this.helper[0] == this.currentItem[0]) {
904 for(var i in this._storedCSS) {
905 if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
906 }
907 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
908 } else {
909 this.currentItem.show();
910 }
911
912 if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
913 if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
914 if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
915 if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
916 for (var i = this.containers.length - 1; i >= 0; i--){
917 if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
918 delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
919 delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
920 }
921 };
922 };
923
924 //Post events to containers
925 for (var i = this.containers.length - 1; i >= 0; i--){
926 if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
927 if(this.containers[i].containerCache.over) {
928 delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
929 this.containers[i].containerCache.over = 0;
930 }
931 }
932
933 //Do what was originally in plugins
934 if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
935 if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset cursor
936 if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
937
938 this.dragging = false;
939 if(this.cancelHelperRemoval) {
940 if(!noPropagation) {
941 this._trigger("beforeStop", event, this._uiHash());
942 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
943 this._trigger("stop", event, this._uiHash());
944 }
945 return false;
946 }
947
948 if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
949
950 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
951 this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
952
953 if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
954
955 if(!noPropagation) {
956 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
957 this._trigger("stop", event, this._uiHash());
958 }
959
960 this.fromOutside = false;
961 return true;
962
963 },
964
965 _trigger: function() {
966 if ($.widget.prototype._trigger.apply(this, arguments) === false) {
967 this.cancel();
968 }
969 },
970
971 _uiHash: function(inst) {
972 var self = inst || this;
973 return {
974 helper: self.helper,
975 placeholder: self.placeholder || $([]),
976 position: self.position,
977 absolutePosition: self.positionAbs, //deprecated
978 offset: self.positionAbs,
979 item: self.currentItem,
980 sender: inst ? inst.element : null
981 };
982 }
983
984 }));
985
986 $.extend($.ui.sortable, {
987 getter: "serialize toArray",
988 version: "1.7.2",
989 eventPrefix: "sort",
990 defaults: {
991 appendTo: "parent",
992 axis: false,
993 cancel: ":input,option",
994 connectWith: false,
995 containment: false,
996 cursor: 'auto',
997 cursorAt: false,
998 delay: 0,
999 distance: 1,
1000 dropOnEmpty: true,
1001 forcePlaceholderSize: false,
1002 forceHelperSize: false,
1003 grid: false,
1004 handle: false,
1005 helper: "original",
1006 items: '> *',
1007 opacity: false,
1008 placeholder: false,
1009 revert: false,
1010 scroll: true,
1011 scrollSensitivity: 20,
1012 scrollSpeed: 20,
1013 scope: "default",
1014 tolerance: "intersect",
1015 zIndex: 1000
1016 }
1017 });
1018
1019 })(jQuery);
1020