source: main/trunk/greenstone3/web/interfaces/oran/js/ui.nestedSortable.js@ 22516

Last change on this file since 22516 was 22516, checked in by sjb48, 14 years ago

Working on establishing a nested sortable list for drag-and-drop functionality. Established a hierarchy based on gsf so each gsf element has its own list, then similar elements can be linked between.

File size: 43.8 KB
Line 
1/*
2 * based on jQuery UI Sortable 1.7.2
3 *
4 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) and Steven Lots (Lotsofdots bvba)
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 * http://www.b-hind.eu/jquery/
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 //console.log(this.position); //b-hind
203 this.positionAbs = this._convertPositionTo("absolute");
204
205 if (!this.lastPositionAbs) {
206 this.lastPositionAbs = this.positionAbs;
207 }
208
209 //Do scrolling
210 if(this.options.scroll) {
211 var o = this.options, scrolled = false;
212 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
213
214 if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
215 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
216 else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
217 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
218
219 if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
220 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
221 else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
222 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
223
224 } else {
225
226 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
227 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
228 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
229 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
230
231 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
232 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
233 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
234 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
235
236 }
237
238 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
239 $.ui.ddmanager.prepareOffsets(this, event);
240 }
241
242 //Regenerate the absolute position used for position checks
243 this.positionAbs = this._convertPositionTo("absolute");
244
245 //Set the helper position
246 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
247 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
248
249 //Rearrange
250
251 for (var i = this.items.length - 1; i >= 0; i--) {
252
253 //Cache variables and intersection, continue if no intersection
254 var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
255 if (!intersection) continue;
256 if(itemElement != this.currentItem[0] //cannot intersect with itself
257 && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
258 && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
259 && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
260 ) {
261
262 this.direction = intersection == 1 ? "down" : "up";
263 //b-hind : here we must also check
264
265 //var level = this._getLevel(item.item);
266 //console.log($.ui.isOverAxis(this.positionAbs.left, item.left, item.width));
267 //var haschildNode = this._hasChildNodes(item.item);
268 //console.log("haschildNode : "+haschildNode);
269 //moved to _rearrange
270
271
272 if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
273 this._rearrange(event, item);
274 } else {
275 break;
276 }
277
278 this._trigger("change", event, this._uiHash());
279 break;
280 }
281 }
282
283 //Post events to containers
284 this._contactContainers(event);
285
286 //Interconnect with droppables
287 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
288
289 //Call callbacks
290 this._trigger('sort', event, this._uiHash());
291
292 this.lastPositionAbs = this.positionAbs;
293 return false;
294
295 },
296
297 _mouseStop: function(event, noPropagation) {
298
299 if(!event) return;
300
301 //If we are using droppables, inform the manager about the drop
302 if ($.ui.ddmanager && !this.options.dropBehaviour)
303 $.ui.ddmanager.drop(this, event);
304
305 if(this.options.revert) {
306 var self = this;
307 var cur = self.placeholder.offset();
308
309 self.reverting = true;
310
311 $(this.helper).animate({
312 left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
313 top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
314 }, parseInt(this.options.revert, 10) || 500, function() {
315 self._clear(event);
316 });
317 } else {
318 this._clear(event, noPropagation);
319 }
320
321 return false;
322
323 },
324
325 cancel: function() {
326
327 var self = this;
328
329 if(this.dragging) {
330
331 this._mouseUp();
332
333 if(this.options.helper == "original")
334 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
335 else
336 this.currentItem.show();
337
338 //Post deactivating events to containers
339 for (var i = this.containers.length - 1; i >= 0; i--){
340 this.containers[i]._trigger("deactivate", null, self._uiHash(this));
341 if(this.containers[i].containerCache.over) {
342 this.containers[i]._trigger("out", null, self._uiHash(this));
343 this.containers[i].containerCache.over = 0;
344 }
345 }
346
347 }
348
349 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
350 if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
351 if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
352
353 $.extend(this, {
354 helper: null,
355 dragging: false,
356 reverting: false,
357 _noFinalSort: null
358 });
359
360 if(this.domPosition.prev) {
361 $(this.domPosition.prev).after(this.currentItem);
362 } else {
363 $(this.domPosition.parent).prepend(this.currentItem);
364 }
365
366 return true;
367
368 },
369
370 serialize: function(o) {
371
372 var items = this._getItemsAsjQuery(o && o.connected);
373 var str = []; o = o || {};
374 if(this.options.nested){
375 var level = 0;
376 var levels = [];
377 var key = 0;
378 for(var i=0;i<items.length;i++){
379 var itemlevel = this._getLevel($(items[i]));
380 if(level== itemlevel){
381 var res = ($(o.item || items[i]).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
382 if(res) str.push((o.key || res[1]+(itemlevel==1?"":"_"+levels[levels.length-1])+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
383 if(res) key = res[2];
384 }else if(itemlevel > level){
385 level = itemlevel;
386 var res = ($(o.item || items[i]).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
387 if(res) levels[levels.length] = key;
388 if(res) str.push((o.key || res[1]+(itemlevel==1?"":"_"+levels[levels.length-1])+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
389 if(res) key = res[2];
390 }else{
391
392 do{
393 levels.pop();
394 key = levels[levels.length-1];
395 level--;
396
397 }while(level > itemlevel);
398 var res = ($(o.item || items[i]).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
399 if(res) str.push((o.key || res[1]+(itemlevel==1?"":"_"+key)+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
400 if(res) key = res[2];
401 }
402
403 }
404 }else{
405 $(items).each(function() {
406 var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
407 if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
408 });
409 }
410
411 return str.join('&');
412
413 },
414
415 toArray: function(o) {
416
417 var items = this._getItemsAsjQuery(o && o.connected);
418 var ret = []; o = o || {};
419 var self = this;
420 //add first item is always level 1
421
422
423 if(this.options.nested){
424
425 var level = 1;
426 var currentAr = ret;
427 var levels = [];
428
429 for(var i=0;i<items.length;i++){
430 var item = items[i];
431 var itemlevel = this._getLevel($(item));
432
433 if(itemlevel == level){
434 currentAr.push([$(o.item || item).attr(o.attribute || 'id') || '',[]]);
435
436 }else if(itemlevel > level){
437
438 level = itemlevel;
439 levels[levels.length] = currentAr;
440 // get last element
441 currentAr = currentAr[currentAr.length-1][1];
442 currentAr.push([$(o.item || item).attr(o.attribute || 'id') || '',[]]);
443 }else{
444
445 do{
446 currentAr = levels[levels.length-1];
447 levels.pop();
448 level--;
449 }while(level > itemlevel);
450
451 currentAr.push([$(o.item || item).attr(o.attribute || 'id') || '',[]]);
452 }
453 }
454
455
456 }else{
457 items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
458 }
459
460
461 return ret;
462
463 },
464
465 /* Be careful with the following core functions */
466 _intersectsWith: function(item) {
467
468 var x1 = this.positionAbs.left,
469 x2 = x1 + this.helperProportions.width,
470 y1 = this.positionAbs.top,
471 y2 = y1 + this.helperProportions.height;
472
473 var l = item.left,
474 r = l + item.width,
475 t = item.top,
476 b = t + item.height;
477
478 var dyClick = this.offset.click.top,
479 dxClick = this.offset.click.left;
480
481 var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
482
483 if( this.options.tolerance == "pointer"
484 || this.options.forcePointerForContainers
485 || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
486 ) {
487 return isOverElement;
488 } else {
489
490 return (l < x1 + (this.helperProportions.width / 2) // Right Half
491 && x2 - (this.helperProportions.width / 2) < r // Left Half
492 && t < y1 + (this.helperProportions.height / 2) // Bottom Half
493 && y2 - (this.helperProportions.height / 2) < b ); // Top Half
494
495 }
496 },
497 /* check of the pointer is over an other element (while dragging) */
498 _intersectsWithPointer: function(item) {
499
500 var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
501 isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
502 isOverElement = isOverElementHeight && isOverElementWidth,
503 verticalDirection = this._getDragVerticalDirection(),
504 horizontalDirection = this._getDragHorizontalDirection();
505
506 if (!isOverElement)
507 return false;
508
509 return this.floating ?
510 ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
511 : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
512
513 },
514 /* check of the dragged element is over an other element */
515 _intersectsWithSides: function(item) {
516
517 var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
518 isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
519 verticalDirection = this._getDragVerticalDirection(),
520 horizontalDirection = this._getDragHorizontalDirection();
521
522 if (this.floating && horizontalDirection) {
523 return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
524 } else {
525 return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
526 }
527
528 },
529
530 _getDragVerticalDirection: function() {
531 var delta = this.positionAbs.top - this.lastPositionAbs.top;
532 return delta != 0 && (delta > 0 ? "down" : "up");
533 },
534
535 _getDragHorizontalDirection: function() {
536 var delta = this.positionAbs.left - this.lastPositionAbs.left;
537 return delta != 0 && (delta > 0 ? "right" : "left");
538 },
539
540 refresh: function(event) {
541 this._refreshItems(event);
542 this.refreshPositions();
543 },
544
545 _connectWith: function() {
546 var options = this.options;
547 return options.connectWith.constructor == String
548 ? [options.connectWith]
549 : options.connectWith;
550 },
551
552 _getItemsAsjQuery: function(connected) {
553
554 var self = this;
555 var items = [];
556 var queries = [];
557 var connectWith = this._connectWith();
558
559 if(connectWith && connected) {
560 for (var i = connectWith.length - 1; i >= 0; i--){
561 var cur = $(connectWith[i]);
562 for (var j = cur.length - 1; j >= 0; j--){
563 var inst = $.data(cur[j], 'sortable');
564 if(inst && inst != this && !inst.options.disabled) {
565 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper"), inst]);
566 }
567 };
568 };
569 }
570
571 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]);
572
573 for (var i = queries.length - 1; i >= 0; i--){
574 queries[i][0].each(function() {
575 items.push(this);
576 });
577 };
578
579
580 return $(items);
581
582 },
583
584 _removeCurrentsFromItems: function() {
585
586 var list = this.currentItem.find(":data(sortable-item)");
587
588 for (var i=0; i < this.items.length; i++) {
589
590 for (var j=0; j < list.length; j++) {
591 if(list[j] == this.items[i].item[0])
592 this.items.splice(i,1);
593 };
594
595 };
596
597 },
598
599 _refreshItems: function(event) {
600
601 this.items = [];
602 this.containers = [this];
603 var items = this.items;
604 var self = this;
605 var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
606 var connectWith = this._connectWith();
607
608 if(connectWith) {
609 for (var i = connectWith.length - 1; i >= 0; i--){
610 var cur = $(connectWith[i]);
611 for (var j = cur.length - 1; j >= 0; j--){
612 var inst = $.data(cur[j], 'sortable');
613 if(inst && inst != this && !inst.options.disabled) {
614 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
615 this.containers.push(inst);
616 }
617 };
618 };
619 }
620
621 for (var i = queries.length - 1; i >= 0; i--) {
622 var targetData = queries[i][1];
623 var _queries = queries[i][0];
624
625 for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
626 var item = $(_queries[j]);
627
628 item.data('sortable-item', targetData); // Data for target checking (mouse manager)
629
630 items.push({
631 item: item,
632 instance: targetData,
633 width: 0, height: 0,
634 left: 0, top: 0
635 });
636 };
637 };
638
639 },
640
641 refreshPositions: function(fast) {
642
643 //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
644 if(this.offsetParent && this.helper) {
645 this.offset.parent = this._getParentOffset();
646 }
647
648 for (var i = this.items.length - 1; i >= 0; i--){
649 var item = this.items[i];
650
651 //We ignore calculating positions of all connected containers when we're not over them
652 if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
653 continue;
654
655 var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
656
657 if (!fast) {
658 item.width = t.outerWidth();
659 item.height = t.outerHeight();
660 }
661
662 var p = t.offset();
663 item.left = p.left;
664 item.top = p.top;
665 };
666
667 if(this.options.custom && this.options.custom.refreshContainers) {
668 this.options.custom.refreshContainers.call(this);
669 } else {
670 for (var i = this.containers.length - 1; i >= 0; i--){
671 var p = this.containers[i].element.offset();
672 this.containers[i].containerCache.left = p.left;
673 this.containers[i].containerCache.top = p.top;
674 this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
675 this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
676 };
677 }
678
679 },
680
681 _createPlaceholder: function(that) {
682
683 var self = that || this, o = self.options;
684
685 if(!o.placeholder || o.placeholder.constructor == String) {
686 var className = o.placeholder;
687 o.placeholder = {
688 element: function() {
689
690 var el = $(document.createElement(self.currentItem[0].nodeName))
691 .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
692 .removeClass("ui-sortable-helper")[0];
693
694 if(!className)
695 el.style.visibility = "hidden";
696
697 return el;
698 },
699 update: function(container, p) {
700
701 // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
702 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
703 if(className && !o.forcePlaceholderSize) return;
704
705 //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
706 if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
707 if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
708 }
709 };
710 }
711
712 //Create the placeholder
713 self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
714
715 //Append it after the actual current item
716 /**********************************************
717 b-hind patch : append it after or as a child if nested is true
718 check position of element
719
720 problem : on deep nested items what is te depth of the required item to place
721 first solution: create a visible location after each depth -> requires a fyical height and chacges the layout of the nested tree:(
722 second solution : check the indent from the helper, but what if the nesteditems dont have an indent
723
724 We go for the second solution !!!
725
726 so what tot do: check the indent of each read left -padding or left -margin
727 if the helper (left side is between th left side of the parent en itself put it as an extra element of the parent if it is larger than itself put it as a child/
728
729 **********************************************/
730
731 self.currentItem.after(self.placeholder);
732
733 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
734 o.placeholder.update(self, self.placeholder);
735
736 },
737
738 _contactContainers: function(event) {
739 for (var i = this.containers.length - 1; i >= 0; i--){
740
741 if(this._intersectsWith(this.containers[i].containerCache)) {
742 if(!this.containers[i].containerCache.over) {
743
744 if(this.currentContainer != this.containers[i]) {
745
746 //When entering a new container, we will find the item with the least distance and append our item near it
747 var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[i].floating ? 'left' : 'top'];
748 for (var j = this.items.length - 1; j >= 0; j--) {
749 if(!$.ui.contains(this.containers[i].element[0], this.items[j].item[0])) continue;
750 var cur = this.items[j][this.containers[i].floating ? 'left' : 'top'];
751 if(Math.abs(cur - base) < dist) {
752 dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
753 }
754 }
755
756 if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
757 continue;
758
759 this.currentContainer = this.containers[i];
760 itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[i].element, true);
761 this._trigger("change", event, this._uiHash());
762 this.containers[i]._trigger("change", event, this._uiHash(this));
763
764 //Update the placeholder
765 this.options.placeholder.update(this.currentContainer, this.placeholder);
766
767 }
768
769 this.containers[i]._trigger("over", event, this._uiHash(this));
770 this.containers[i].containerCache.over = 1;
771 }
772 } else {
773 if(this.containers[i].containerCache.over) {
774 this.containers[i]._trigger("out", event, this._uiHash(this));
775 this.containers[i].containerCache.over = 0;
776 }
777 }
778
779 };
780 },
781
782 _createHelper: function(event) {
783
784 var o = this.options;
785 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
786
787 if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
788 $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
789
790 if(helper[0] == this.currentItem[0])
791 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") };
792
793 if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
794 if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
795
796 return helper;
797
798 },
799
800 _adjustOffsetFromHelper: function(obj) {
801 if(obj.left != undefined) this.offset.click.left = obj.left + this.margins.left;
802 if(obj.right != undefined) this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
803 if(obj.top != undefined) this.offset.click.top = obj.top + this.margins.top;
804 if(obj.bottom != undefined) this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
805 },
806
807 _getParentOffset: function() {
808
809
810 //Get the offsetParent and cache its position
811 this.offsetParent = this.helper.offsetParent();
812 var po = this.offsetParent.offset();
813
814 // This is a special case where we need to modify a offset calculated on start, since the following happened:
815 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
816 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
817 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
818 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
819 po.left += this.scrollParent.scrollLeft();
820 po.top += this.scrollParent.scrollTop();
821 }
822
823 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
824 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
825 po = { top: 0, left: 0 };
826
827 return {
828 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
829 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
830 };
831
832 },
833
834 _getRelativeOffset: function() {
835
836 if(this.cssPosition == "relative") {
837 var p = this.currentItem.position();
838 return {
839 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
840 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
841 };
842 } else {
843 return { top: 0, left: 0 };
844 }
845
846 },
847
848 _cacheMargins: function() {
849 this.margins = {
850 left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
851 top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
852 };
853 },
854
855 _cacheHelperProportions: function() {
856 this.helperProportions = {
857 width: this.helper.outerWidth(),
858 height: this.helper.outerHeight()
859 };
860 },
861
862 _setContainment: function() {
863
864 var o = this.options;
865 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
866 if(o.containment == 'document' || o.containment == 'window') this.containment = [
867 0 - this.offset.relative.left - this.offset.parent.left,
868 0 - this.offset.relative.top - this.offset.parent.top,
869 $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
870 ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
871 ];
872
873 if(!(/^(document|window|parent)$/).test(o.containment)) {
874 var ce = $(o.containment)[0];
875 var co = $(o.containment).offset();
876 var over = ($(ce).css("overflow") != 'hidden');
877
878 this.containment = [
879 co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
880 co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
881 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,
882 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
883 ];
884 }
885
886 },
887
888 _convertPositionTo: function(d, pos) {
889
890 if(!pos) pos = this.position;
891 var mod = d == "absolute" ? 1 : -1;
892 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);
893
894 return {
895 top: (
896 pos.top // The absolute mouse position
897 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
898 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
899 - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
900 ),
901 left: (
902 pos.left // The absolute mouse position
903 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
904 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
905 - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
906 )
907 };
908
909 },
910
911 _generatePosition: function(event) {
912
913 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);
914
915 // This is another very weird special case that only happens for relative elements:
916 // 1. If the css position is relative
917 // 2. and the scroll parent is the document or similar to the offset parent
918 // we have to refresh the relative offset during the scroll so there are no jumps
919 if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
920 this.offset.relative = this._getRelativeOffset();
921 }
922
923 var pageX = event.pageX;
924 var pageY = event.pageY;
925
926 /*
927 * - Position constraining -
928 * Constrain the position to a mix of grid, containment.
929 */
930
931 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
932
933 if(this.containment) {
934 if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
935 if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
936 if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
937 if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
938 }
939
940 if(o.grid) {
941 var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
942 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;
943
944 var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
945 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;
946 }
947
948 }
949
950 return {
951 top: (
952 pageY // The absolute mouse position
953 - this.offset.click.top // Click offset (relative to the element)
954 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
955 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
956 + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
957 ),
958 left: (
959 pageX // The absolute mouse position
960 - this.offset.click.left // Click offset (relative to the element)
961 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
962 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
963 + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
964 )
965 };
966
967 },
968
969 _rearrange: function(event, i, a, hardRefresh) {
970
971 var haschildNode = this._hasChildNodes(i.item);
972 // do this if is
973 if(this.options.nested && this.error){
974 if(this._storedCursor) $('body').css("cursor", this._storedCursor);
975 this.error = false;
976 }
977 if(!this.options.nested){
978 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));
979 }else{
980 var level = this._getLevel(i.item);
981 var childlevels = this._getChildLevels(this.helper);
982 // aantal items op deze level
983
984
985 if(this.options.maxLevels <= level+childlevels){
986 if(this.error != true){
987 this._storedCursor = $('body').css("cursor");
988 $('body').css("cursor", "not-allowed"); //Reset cursor
989 this.error = true;
990 }
991 }else{
992 if(!$.ui.isOverAxis(this.positionAbs.left, i.left, i.width)){
993
994 if(this.options.maxItems.length <= level-1 || (i.item.closest(this.options.nested).children(this.options.items).not(".ui-sortable-helper,.ui-sortable-placeholder").length < this.options.maxItems[level-1])){ // als maxitems niet berijkt voor level
995 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));
996 }else{
997 this._storedCursor = $('body').css("cursor");
998 $('body').css("cursor", "not-allowed"); //Reset cursor
999 this.error = true;
1000 }
1001 }else{
1002
1003 if(i.item.find(this.options.nested).siblings().length<=0){
1004 var element = document.createElement(this.options.nested);
1005
1006 i.item.append($(element));
1007
1008 }
1009
1010
1011 if(this.options.maxItems.length <= level || (i.item.children(this.options.nested).children(this.options.items).not(".ui-sortable-helper,.ui-sortable-placeholder").length < this.options.maxItems[level])){
1012 //i.item.find(this.options.nested)[0].appendChild(this.placeholder[0],i.item.find(this.options.nested)[0].firstChild);
1013 i.item.find(this.options.nested)[0].insertBefore(this.placeholder[0],i.item.find(this.options.nested)[0].firstChild);
1014 }else{
1015 this._storedCursor = $('body').css("cursor");
1016 $('body').css("cursor", "not-allowed"); //Reset cursor
1017 this.error = true;
1018 }
1019
1020 }
1021 }
1022
1023
1024 //a ? a[0].appendChild(this.placeholder[0])
1025 }
1026 //Various things done here to improve the performance:
1027 // 1. we create a setTimeout, that calls refreshPositions
1028 // 2. on the instance, we have a counter variable, that get's higher after every append
1029 // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
1030 // 4. this lets only the last addition to the timeout stack through
1031 this.counter = this.counter ? ++this.counter : 1;
1032 var self = this, counter = this.counter;
1033
1034 window.setTimeout(function() {
1035 if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
1036 },0);
1037
1038 },
1039 /*b-hind function : get level from element */
1040 _getLevel:function(item){
1041
1042 var level =1;
1043 if(this.options.nested){
1044 var list = item.closest(this.options.nested);
1045 //.ui-sortable
1046 while(!list.is(".ui-sortable") && level < this.options.maxLevels){
1047 level++;
1048 list = list.parent().closest(this.options.nested);
1049 }
1050 }
1051 return level;
1052 },
1053 _getChildLevels:function(item){
1054
1055 levels = item.find(this.options.items).filter(this.options.items+":first-child").length;
1056
1057 return levels;
1058 },
1059 _getItemsOnLevel:function(item){
1060 //console.log(item.closest(this.options.nested).html())
1061 return item.closest(this.options.nested).children(this.options.items).not('.ui-sortable-placeholder').length;
1062 },
1063 _hasChildNodes:function(item){
1064 if(this.options.nested){
1065 if(item.find(this.options.nested) && item.find(this.options.nested).children(this.options.items).length>0){
1066 return true;
1067 }else{
1068 return false;
1069 }
1070 }
1071 },
1072
1073 _clear: function(event, noPropagation) {
1074
1075 this.reverting = false;
1076 // We delay all events that have to be triggered to after the point where the placeholder has been removed and
1077 // everything else normalized again
1078 var delayedTriggers = [], self = this;
1079
1080 // We first have to update the dom position of the actual currentItem
1081 // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
1082 if(!this._noFinalSort && this.currentItem[0].parentNode) this.placeholder.before(this.currentItem);
1083 this._noFinalSort = null;
1084
1085 if(this.helper[0] == this.currentItem[0]) {
1086 for(var i in this._storedCSS) {
1087 if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
1088 }
1089 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
1090 } else {
1091 this.currentItem.show();
1092 }
1093
1094 if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
1095 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
1096 if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
1097 if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
1098 for (var i = this.containers.length - 1; i >= 0; i--){
1099 if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
1100 delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
1101 delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
1102 }
1103 };
1104 };
1105
1106 //Post events to containers
1107 for (var i = this.containers.length - 1; i >= 0; i--){
1108 if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
1109 if(this.containers[i].containerCache.over) {
1110 delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
1111 this.containers[i].containerCache.over = 0;
1112 }
1113 }
1114
1115 //Do what was originally in plugins
1116 if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
1117 if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset cursor
1118 if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
1119
1120 this.dragging = false;
1121 if(this.cancelHelperRemoval) {
1122 if(!noPropagation) {
1123 this._trigger("beforeStop", event, this._uiHash());
1124 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
1125 this._trigger("stop", event, this._uiHash());
1126 }
1127 return false;
1128 }
1129
1130 if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
1131
1132 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
1133 this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
1134
1135 if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
1136
1137 if(!noPropagation) {
1138 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
1139 this._trigger("stop", event, this._uiHash());
1140 }
1141 //b-hind clear empty nested elements
1142 if(this.options.nested){
1143
1144 this.element.find(this.options.nested+":empty").remove();
1145
1146 }
1147 this.fromOutside = false;
1148 return true;
1149
1150 },
1151
1152 _trigger: function() {
1153 if ($.widget.prototype._trigger.apply(this, arguments) === false) {
1154 this.cancel();
1155 }
1156 },
1157
1158 _uiHash: function(inst) {
1159 var self = inst || this;
1160 return {
1161 helper: self.helper,
1162 placeholder: self.placeholder || $([]),
1163 position: self.position,
1164 absolutePosition: self.positionAbs, //deprecated
1165 offset: self.positionAbs,
1166 item: self.currentItem,
1167 sender: inst ? inst.element : null
1168 };
1169 }
1170
1171}));
1172
1173$.extend($.ui.sortable, {
1174 getter: "serialize toArray",
1175 version: "1.7.2",
1176 eventPrefix: "sort",
1177 defaults: {
1178 appendTo: "parent",
1179 axis: false,
1180 cancel: ":input,option",
1181 connectWith: false,
1182 containment: false,
1183 cursor: 'auto',
1184 cursorAt: false,
1185 delay: 0,
1186 distance: 1,
1187
1188 dropOnEmpty: true,
1189 forcePlaceholderSize: false,
1190 forceHelperSize: false,
1191 grid: false,
1192 handle: false,
1193 helper: "original",
1194 items: '> *',
1195 opacity: false,
1196 placeholder: false,
1197 revert: false,
1198 scroll: true,
1199 scrollSensitivity: 20,
1200 scrollSpeed: 20,
1201 scope: "default",
1202 tolerance: "intersect",
1203 zIndex: 1000,
1204 nested:false,
1205 maxLevels: 100,
1206 maxItems : []
1207 }
1208});
1209
1210})(jQuery);
Note: See TracBrowser for help on using the repository browser.