source: trunk/themes/default/js/ui/jquery.ui.sortable.js @ 9559

Last change on this file since 9559 was 9559, checked in by patdenice, 13 years ago

Update jQuery UI to 1.8.10.
Improve jquery ui management in template class.

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