Project

General

Profile

1
/*!
2
 * jQuery UI Sortable 1.8.20
3
 *
4
 * Copyright 2012, 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
	ready: false,
20
	options: {
21
		appendTo: "parent",
22
		axis: false,
23
		connectWith: false,
24
		containment: false,
25
		cursor: 'auto',
26
		cursorAt: false,
27
		dropOnEmpty: true,
28
		forcePlaceholderSize: false,
29
		forceHelperSize: false,
30
		grid: false,
31
		handle: false,
32
		helper: "original",
33
		items: '> *',
34
		opacity: false,
35
		placeholder: false,
36
		revert: false,
37
		scroll: true,
38
		scrollSensitivity: 20,
39
		scrollSpeed: 20,
40
		scope: "default",
41
		tolerance: "intersect",
42
		zIndex: 1000
43
	},
44
	_create: function() {
45

    
46
		var o = this.options;
47
		this.containerCache = {};
48
		this.element.addClass("ui-sortable");
49

    
50
		//Get the items
51
		this.refresh();
52

    
53
		//Let's determine if the items are being displayed horizontally
54
		this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;
55

    
56
		//Let's determine the parent's offset
57
		this.offset = this.element.offset();
58

    
59
		//Initialize mouse events for interaction
60
		this._mouseInit();
61
		
62
		//We're ready to go
63
		this.ready = true
64

    
65
	},
66

    
67
	destroy: function() {
68
		$.Widget.prototype.destroy.call( this );
69
		this.element
70
			.removeClass("ui-sortable ui-sortable-disabled");
71
		this._mouseDestroy();
72

    
73
		for ( var i = this.items.length - 1; i >= 0; i-- )
74
			this.items[i].item.removeData(this.widgetName + "-item");
75

    
76
		return this;
77
	},
78

    
79
	_setOption: function(key, value){
80
		if ( key === "disabled" ) {
81
			this.options[ key ] = value;
82
	
83
			this.widget()
84
				[ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" );
85
		} else {
86
			// Don't call widget base _setOption for disable as it adds ui-state-disabled class
87
			$.Widget.prototype._setOption.apply(this, arguments);
88
		}
89
	},
90

    
91
	_mouseCapture: function(event, overrideHandle) {
92
		var that = this;
93

    
94
		if (this.reverting) {
95
			return false;
96
		}
97

    
98
		if(this.options.disabled || this.options.type == 'static') return false;
99

    
100
		//We have to refresh the items data once first
101
		this._refreshItems(event);
102

    
103
		//Find out if the clicked node (or one of its parents) is a actual item in this.items
104
		var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
105
			if($.data(this, that.widgetName + '-item') == self) {
106
				currentItem = $(this);
107
				return false;
108
			}
109
		});
110
		if($.data(event.target, that.widgetName + '-item') == self) currentItem = $(event.target);
111

    
112
		if(!currentItem) return false;
113
		if(this.options.handle && !overrideHandle) {
114
			var validHandle = false;
115

    
116
			$(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
117
			if(!validHandle) return false;
118
		}
119

    
120
		this.currentItem = currentItem;
121
		this._removeCurrentsFromItems();
122
		return true;
123

    
124
	},
125

    
126
	_mouseStart: function(event, overrideHandle, noActivation) {
127

    
128
		var o = this.options, self = this;
129
		this.currentContainer = this;
130

    
131
		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
132
		this.refreshPositions();
133

    
134
		//Create and append the visible helper
135
		this.helper = this._createHelper(event);
136

    
137
		//Cache the helper size
138
		this._cacheHelperProportions();
139

    
140
		/*
141
		 * - Position generation -
142
		 * This block generates everything position related - it's the core of draggables.
143
		 */
144

    
145
		//Cache the margins of the original element
146
		this._cacheMargins();
147

    
148
		//Get the next scrolling parent
149
		this.scrollParent = this.helper.scrollParent();
150

    
151
		//The element's absolute position on the page minus margins
152
		this.offset = this.currentItem.offset();
153
		this.offset = {
154
			top: this.offset.top - this.margins.top,
155
			left: this.offset.left - this.margins.left
156
		};
157

    
158
		// Only after we got the offset, we can change the helper's position to absolute
159
		// TODO: Still need to figure out a way to make relative sorting possible
160
		this.helper.css("position", "absolute");
161
		this.cssPosition = this.helper.css("position");
162

    
163
		$.extend(this.offset, {
164
			click: { //Where the click happened, relative to the element
165
				left: event.pageX - this.offset.left,
166
				top: event.pageY - this.offset.top
167
			},
168
			parent: this._getParentOffset(),
169
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
170
		});
171

    
172
		//Generate the original position
173
		this.originalPosition = this._generatePosition(event);
174
		this.originalPageX = event.pageX;
175
		this.originalPageY = event.pageY;
176

    
177
		//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
178
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
179

    
180
		//Cache the former DOM position
181
		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
182

    
183
		//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
184
		if(this.helper[0] != this.currentItem[0]) {
185
			this.currentItem.hide();
186
		}
187

    
188
		//Create the placeholder
189
		this._createPlaceholder();
190

    
191
		//Set a containment if given in the options
192
		if(o.containment)
193
			this._setContainment();
194

    
195
		if(o.cursor) { // cursor option
196
			if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
197
			$('body').css("cursor", o.cursor);
198
		}
199

    
200
		if(o.opacity) { // opacity option
201
			if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
202
			this.helper.css("opacity", o.opacity);
203
		}
204

    
205
		if(o.zIndex) { // zIndex option
206
			if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
207
			this.helper.css("zIndex", o.zIndex);
208
		}
209

    
210
		//Prepare scrolling
211
		if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
212
			this.overflowOffset = this.scrollParent.offset();
213

    
214
		//Call callbacks
215
		this._trigger("start", event, this._uiHash());
216

    
217
		//Recache the helper size
218
		if(!this._preserveHelperProportions)
219
			this._cacheHelperProportions();
220

    
221

    
222
		//Post 'activate' events to possible containers
223
		if(!noActivation) {
224
			 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
225
		}
226

    
227
		//Prepare possible droppables
228
		if($.ui.ddmanager)
229
			$.ui.ddmanager.current = this;
230

    
231
		if ($.ui.ddmanager && !o.dropBehaviour)
232
			$.ui.ddmanager.prepareOffsets(this, event);
233

    
234
		this.dragging = true;
235

    
236
		this.helper.addClass("ui-sortable-helper");
237
		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
238
		return true;
239

    
240
	},
241

    
242
	_mouseDrag: function(event) {
243

    
244
		//Compute the helpers position
245
		this.position = this._generatePosition(event);
246
		this.positionAbs = this._convertPositionTo("absolute");
247

    
248
		if (!this.lastPositionAbs) {
249
			this.lastPositionAbs = this.positionAbs;
250
		}
251

    
252
		//Do scrolling
253
		if(this.options.scroll) {
254
			var o = this.options, scrolled = false;
255
			if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
256

    
257
				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
258
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
259
				else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
260
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
261

    
262
				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
263
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
264
				else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
265
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
266

    
267
			} else {
268

    
269
				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
270
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
271
				else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
272
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
273

    
274
				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
275
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
276
				else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
277
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
278

    
279
			}
280

    
281
			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
282
				$.ui.ddmanager.prepareOffsets(this, event);
283
		}
284

    
285
		//Regenerate the absolute position used for position checks
286
		this.positionAbs = this._convertPositionTo("absolute");
287

    
288
		//Set the helper position
289
		if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
290
		if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
291

    
292
		//Rearrange
293
		for (var i = this.items.length - 1; i >= 0; i--) {
294

    
295
			//Cache variables and intersection, continue if no intersection
296
			var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
297
			if (!intersection) continue;
298

    
299
			if(itemElement != this.currentItem[0] //cannot intersect with itself
300
				&&	this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
301
				&&	!$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
302
				&& (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
303
				//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
304
			) {
305

    
306
				this.direction = intersection == 1 ? "down" : "up";
307

    
308
				if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
309
					this._rearrange(event, item);
310
				} else {
311
					break;
312
				}
313

    
314
				this._trigger("change", event, this._uiHash());
315
				break;
316
			}
317
		}
318

    
319
		//Post events to containers
320
		this._contactContainers(event);
321

    
322
		//Interconnect with droppables
323
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
324

    
325
		//Call callbacks
326
		this._trigger('sort', event, this._uiHash());
327

    
328
		this.lastPositionAbs = this.positionAbs;
329
		return false;
330

    
331
	},
332

    
333
	_mouseStop: function(event, noPropagation) {
334

    
335
		if(!event) return;
336

    
337
		//If we are using droppables, inform the manager about the drop
338
		if ($.ui.ddmanager && !this.options.dropBehaviour)
339
			$.ui.ddmanager.drop(this, event);
340

    
341
		if(this.options.revert) {
342
			var self = this;
343
			var cur = self.placeholder.offset();
344

    
345
			self.reverting = true;
346

    
347
			$(this.helper).animate({
348
				left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
349
				top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
350
			}, parseInt(this.options.revert, 10) || 500, function() {
351
				self._clear(event);
352
			});
353
		} else {
354
			this._clear(event, noPropagation);
355
		}
356

    
357
		return false;
358

    
359
	},
360

    
361
	cancel: function() {
362

    
363
		var self = this;
364

    
365
		if(this.dragging) {
366

    
367
			this._mouseUp({ target: null });
368

    
369
			if(this.options.helper == "original")
370
				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
371
			else
372
				this.currentItem.show();
373

    
374
			//Post deactivating events to containers
375
			for (var i = this.containers.length - 1; i >= 0; i--){
376
				this.containers[i]._trigger("deactivate", null, self._uiHash(this));
377
				if(this.containers[i].containerCache.over) {
378
					this.containers[i]._trigger("out", null, self._uiHash(this));
379
					this.containers[i].containerCache.over = 0;
380
				}
381
			}
382

    
383
		}
384

    
385
		if (this.placeholder) {
386
			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
387
			if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
388
			if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
389

    
390
			$.extend(this, {
391
				helper: null,
392
				dragging: false,
393
				reverting: false,
394
				_noFinalSort: null
395
			});
396

    
397
			if(this.domPosition.prev) {
398
				$(this.domPosition.prev).after(this.currentItem);
399
			} else {
400
				$(this.domPosition.parent).prepend(this.currentItem);
401
			}
402
		}
403

    
404
		return this;
405

    
406
	},
407

    
408
	serialize: function(o) {
409

    
410
		var items = this._getItemsAsjQuery(o && o.connected);
411
		var str = []; o = o || {};
412

    
413
		$(items).each(function() {
414
			var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
415
			if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
416
		});
417

    
418
		if(!str.length && o.key) {
419
			str.push(o.key + '=');
420
		}
421

    
422
		return str.join('&');
423

    
424
	},
425

    
426
	toArray: function(o) {
427

    
428
		var items = this._getItemsAsjQuery(o && o.connected);
429
		var ret = []; o = o || {};
430

    
431
		items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
432
		return ret;
433

    
434
	},
435

    
436
	/* Be careful with the following core functions */
437
	_intersectsWith: function(item) {
438

    
439
		var x1 = this.positionAbs.left,
440
			x2 = x1 + this.helperProportions.width,
441
			y1 = this.positionAbs.top,
442
			y2 = y1 + this.helperProportions.height;
443

    
444
		var l = item.left,
445
			r = l + item.width,
446
			t = item.top,
447
			b = t + item.height;
448

    
449
		var dyClick = this.offset.click.top,
450
			dxClick = this.offset.click.left;
451

    
452
		var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
453

    
454
		if(	   this.options.tolerance == "pointer"
455
			|| this.options.forcePointerForContainers
456
			|| (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
457
		) {
458
			return isOverElement;
459
		} else {
460

    
461
			return (l < x1 + (this.helperProportions.width / 2) // Right Half
462
				&& x2 - (this.helperProportions.width / 2) < r // Left Half
463
				&& t < y1 + (this.helperProportions.height / 2) // Bottom Half
464
				&& y2 - (this.helperProportions.height / 2) < b ); // Top Half
465

    
466
		}
467
	},
468

    
469
	_intersectsWithPointer: function(item) {
470

    
471
		var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
472
			isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
473
			isOverElement = isOverElementHeight && isOverElementWidth,
474
			verticalDirection = this._getDragVerticalDirection(),
475
			horizontalDirection = this._getDragHorizontalDirection();
476

    
477
		if (!isOverElement)
478
			return false;
479

    
480
		return this.floating ?
481
			( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
482
			: ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
483

    
484
	},
485

    
486
	_intersectsWithSides: function(item) {
487

    
488
		var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
489
			isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
490
			verticalDirection = this._getDragVerticalDirection(),
491
			horizontalDirection = this._getDragHorizontalDirection();
492

    
493
		if (this.floating && horizontalDirection) {
494
			return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
495
		} else {
496
			return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
497
		}
498

    
499
	},
500

    
501
	_getDragVerticalDirection: function() {
502
		var delta = this.positionAbs.top - this.lastPositionAbs.top;
503
		return delta != 0 && (delta > 0 ? "down" : "up");
504
	},
505

    
506
	_getDragHorizontalDirection: function() {
507
		var delta = this.positionAbs.left - this.lastPositionAbs.left;
508
		return delta != 0 && (delta > 0 ? "right" : "left");
509
	},
510

    
511
	refresh: function(event) {
512
		this._refreshItems(event);
513
		this.refreshPositions();
514
		return this;
515
	},
516

    
517
	_connectWith: function() {
518
		var options = this.options;
519
		return options.connectWith.constructor == String
520
			? [options.connectWith]
521
			: options.connectWith;
522
	},
523
	
524
	_getItemsAsjQuery: function(connected) {
525

    
526
		var self = this;
527
		var items = [];
528
		var queries = [];
529
		var connectWith = this._connectWith();
530

    
531
		if(connectWith && connected) {
532
			for (var i = connectWith.length - 1; i >= 0; i--){
533
				var cur = $(connectWith[i]);
534
				for (var j = cur.length - 1; j >= 0; j--){
535
					var inst = $.data(cur[j], this.widgetName);
536
					if(inst && inst != this && !inst.options.disabled) {
537
						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]);
538
					}
539
				};
540
			};
541
		}
542

    
543
		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]);
544

    
545
		for (var i = queries.length - 1; i >= 0; i--){
546
			queries[i][0].each(function() {
547
				items.push(this);
548
			});
549
		};
550

    
551
		return $(items);
552

    
553
	},
554

    
555
	_removeCurrentsFromItems: function() {
556

    
557
		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
558

    
559
		for (var i=0; i < this.items.length; i++) {
560

    
561
			for (var j=0; j < list.length; j++) {
562
				if(list[j] == this.items[i].item[0])
563
					this.items.splice(i,1);
564
			};
565

    
566
		};
567

    
568
	},
569

    
570
	_refreshItems: function(event) {
571

    
572
		this.items = [];
573
		this.containers = [this];
574
		var items = this.items;
575
		var self = this;
576
		var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
577
		var connectWith = this._connectWith();
578

    
579
		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
580
			for (var i = connectWith.length - 1; i >= 0; i--){
581
				var cur = $(connectWith[i]);
582
				for (var j = cur.length - 1; j >= 0; j--){
583
					var inst = $.data(cur[j], this.widgetName);
584
					if(inst && inst != this && !inst.options.disabled) {
585
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
586
						this.containers.push(inst);
587
					}
588
				};
589
			};
590
		}
591

    
592
		for (var i = queries.length - 1; i >= 0; i--) {
593
			var targetData = queries[i][1];
594
			var _queries = queries[i][0];
595

    
596
			for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
597
				var item = $(_queries[j]);
598

    
599
				item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)
600

    
601
				items.push({
602
					item: item,
603
					instance: targetData,
604
					width: 0, height: 0,
605
					left: 0, top: 0
606
				});
607
			};
608
		};
609

    
610
	},
611

    
612
	refreshPositions: function(fast) {
613

    
614
		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
615
		if(this.offsetParent && this.helper) {
616
			this.offset.parent = this._getParentOffset();
617
		}
618

    
619
		for (var i = this.items.length - 1; i >= 0; i--){
620
			var item = this.items[i];
621

    
622
			//We ignore calculating positions of all connected containers when we're not over them
623
			if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
624
				continue;
625

    
626
			var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
627

    
628
			if (!fast) {
629
				item.width = t.outerWidth();
630
				item.height = t.outerHeight();
631
			}
632

    
633
			var p = t.offset();
634
			item.left = p.left;
635
			item.top = p.top;
636
		};
637

    
638
		if(this.options.custom && this.options.custom.refreshContainers) {
639
			this.options.custom.refreshContainers.call(this);
640
		} else {
641
			for (var i = this.containers.length - 1; i >= 0; i--){
642
				var p = this.containers[i].element.offset();
643
				this.containers[i].containerCache.left = p.left;
644
				this.containers[i].containerCache.top = p.top;
645
				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
646
				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
647
			};
648
		}
649

    
650
		return this;
651
	},
652

    
653
	_createPlaceholder: function(that) {
654

    
655
		var self = that || this, o = self.options;
656

    
657
		if(!o.placeholder || o.placeholder.constructor == String) {
658
			var className = o.placeholder;
659
			o.placeholder = {
660
				element: function() {
661

    
662
					var el = $(document.createElement(self.currentItem[0].nodeName))
663
						.addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
664
						.removeClass("ui-sortable-helper")[0];
665

    
666
					if(!className)
667
						el.style.visibility = "hidden";
668

    
669
					return el;
670
				},
671
				update: function(container, p) {
672

    
673
					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
674
					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
675
					if(className && !o.forcePlaceholderSize) return;
676

    
677
					//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
678
					if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
679
					if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
680
				}
681
			};
682
		}
683

    
684
		//Create the placeholder
685
		self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
686

    
687
		//Append it after the actual current item
688
		self.currentItem.after(self.placeholder);
689

    
690
		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
691
		o.placeholder.update(self, self.placeholder);
692

    
693
	},
694

    
695
	_contactContainers: function(event) {
696
		
697
		// get innermost container that intersects with item 
698
		var innermostContainer = null, innermostIndex = null;		
699
		
700
		
701
		for (var i = this.containers.length - 1; i >= 0; i--){
702

    
703
			// never consider a container that's located within the item itself 
704
			if($.ui.contains(this.currentItem[0], this.containers[i].element[0]))
705
				continue;
706

    
707
			if(this._intersectsWith(this.containers[i].containerCache)) {
708

    
709
				// if we've already found a container and it's more "inner" than this, then continue 
710
				if(innermostContainer && $.ui.contains(this.containers[i].element[0], innermostContainer.element[0]))
711
					continue;
712

    
713
				innermostContainer = this.containers[i]; 
714
				innermostIndex = i;
715
					
716
			} else {
717
				// container doesn't intersect. trigger "out" event if necessary 
718
				if(this.containers[i].containerCache.over) {
719
					this.containers[i]._trigger("out", event, this._uiHash(this));
720
					this.containers[i].containerCache.over = 0;
721
				}
722
			}
723

    
724
		}
725
		
726
		// if no intersecting containers found, return 
727
		if(!innermostContainer) return; 
728

    
729
		// move the item into the container if it's not there already
730
		if(this.containers.length === 1) {
731
			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
732
			this.containers[innermostIndex].containerCache.over = 1;
733
		} else if(this.currentContainer != this.containers[innermostIndex]) { 
734

    
735
			//When entering a new container, we will find the item with the least distance and append our item near it 
736
			var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; 
737
			for (var j = this.items.length - 1; j >= 0; j--) { 
738
				if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; 
739
				var cur = this.items[j][this.containers[innermostIndex].floating ? 'left' : 'top']; 
740
				if(Math.abs(cur - base) < dist) { 
741
					dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; 
742
				} 
743
			} 
744

    
745
			if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled 
746
				return; 
747

    
748
			this.currentContainer = this.containers[innermostIndex]; 
749
			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); 
750
			this._trigger("change", event, this._uiHash()); 
751
			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); 
752

    
753
			//Update the placeholder 
754
			this.options.placeholder.update(this.currentContainer, this.placeholder); 
755
		
756
			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); 
757
			this.containers[innermostIndex].containerCache.over = 1;
758
		} 
759
	
760
		
761
	},
762

    
763
	_createHelper: function(event) {
764

    
765
		var o = this.options;
766
		var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
767

    
768
		if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
769
			$(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
770

    
771
		if(helper[0] == this.currentItem[0])
772
			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") };
773

    
774
		if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
775
		if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
776

    
777
		return helper;
778

    
779
	},
780

    
781
	_adjustOffsetFromHelper: function(obj) {
782
		if (typeof obj == 'string') {
783
			obj = obj.split(' ');
784
		}
785
		if ($.isArray(obj)) {
786
			obj = {left: +obj[0], top: +obj[1] || 0};
787
		}
788
		if ('left' in obj) {
789
			this.offset.click.left = obj.left + this.margins.left;
790
		}
791
		if ('right' in obj) {
792
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
793
		}
794
		if ('top' in obj) {
795
			this.offset.click.top = obj.top + this.margins.top;
796
		}
797
		if ('bottom' in obj) {
798
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
799
		}
800
	},
801

    
802
	_getParentOffset: function() {
803

    
804

    
805
		//Get the offsetParent and cache its position
806
		this.offsetParent = this.helper.offsetParent();
807
		var po = this.offsetParent.offset();
808

    
809
		// This is a special case where we need to modify a offset calculated on start, since the following happened:
810
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
811
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
812
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
813
		if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
814
			po.left += this.scrollParent.scrollLeft();
815
			po.top += this.scrollParent.scrollTop();
816
		}
817

    
818
		if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
819
		|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
820
			po = { top: 0, left: 0 };
821

    
822
		return {
823
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
824
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
825
		};
826

    
827
	},
828

    
829
	_getRelativeOffset: function() {
830

    
831
		if(this.cssPosition == "relative") {
832
			var p = this.currentItem.position();
833
			return {
834
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
835
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
836
			};
837
		} else {
838
			return { top: 0, left: 0 };
839
		}
840

    
841
	},
842

    
843
	_cacheMargins: function() {
844
		this.margins = {
845
			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
846
			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
847
		};
848
	},
849

    
850
	_cacheHelperProportions: function() {
851
		this.helperProportions = {
852
			width: this.helper.outerWidth(),
853
			height: this.helper.outerHeight()
854
		};
855
	},
856

    
857
	_setContainment: function() {
858

    
859
		var o = this.options;
860
		if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
861
		if(o.containment == 'document' || o.containment == 'window') this.containment = [
862
			0 - this.offset.relative.left - this.offset.parent.left,
863
			0 - this.offset.relative.top - this.offset.parent.top,
864
			$(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
865
			($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
866
		];
867

    
868
		if(!(/^(document|window|parent)$/).test(o.containment)) {
869
			var ce = $(o.containment)[0];
870
			var co = $(o.containment).offset();
871
			var over = ($(ce).css("overflow") != 'hidden');
872

    
873
			this.containment = [
874
				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
875
				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
876
				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,
877
				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
878
			];
879
		}
880

    
881
	},
882

    
883
	_convertPositionTo: function(d, pos) {
884

    
885
		if(!pos) pos = this.position;
886
		var mod = d == "absolute" ? 1 : -1;
887
		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);
888

    
889
		return {
890
			top: (
891
				pos.top																	// The absolute mouse position
892
				+ this.offset.relative.top * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
893
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
894
				- ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
895
			),
896
			left: (
897
				pos.left																// The absolute mouse position
898
				+ this.offset.relative.left * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
899
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
900
				- ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
901
			)
902
		};
903

    
904
	},
905

    
906
	_generatePosition: function(event) {
907

    
908
		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);
909

    
910
		// This is another very weird special case that only happens for relative elements:
911
		// 1. If the css position is relative
912
		// 2. and the scroll parent is the document or similar to the offset parent
913
		// we have to refresh the relative offset during the scroll so there are no jumps
914
		if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
915
			this.offset.relative = this._getRelativeOffset();
916
		}
917

    
918
		var pageX = event.pageX;
919
		var pageY = event.pageY;
920

    
921
		/*
922
		 * - Position constraining -
923
		 * Constrain the position to a mix of grid, containment.
924
		 */
925

    
926
		if(this.originalPosition) { //If we are not dragging yet, we won't check for options
927

    
928
			if(this.containment) {
929
				if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
930
				if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
931
				if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
932
				if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
933
			}
934

    
935
			if(o.grid) {
936
				var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
937
				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;
938

    
939
				var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
940
				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;
941
			}
942

    
943
		}
944

    
945
		return {
946
			top: (
947
				pageY																// The absolute mouse position
948
				- this.offset.click.top													// Click offset (relative to the element)
949
				- this.offset.relative.top												// Only for relative positioned nodes: Relative offset from element to offset parent
950
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
951
				+ ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
952
			),
953
			left: (
954
				pageX																// The absolute mouse position
955
				- this.offset.click.left												// Click offset (relative to the element)
956
				- this.offset.relative.left												// Only for relative positioned nodes: Relative offset from element to offset parent
957
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
958
				+ ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
959
			)
960
		};
961

    
962
	},
963

    
964
	_rearrange: function(event, i, a, hardRefresh) {
965

    
966
		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));
967

    
968
		//Various things done here to improve the performance:
969
		// 1. we create a setTimeout, that calls refreshPositions
970
		// 2. on the instance, we have a counter variable, that get's higher after every append
971
		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
972
		// 4. this lets only the last addition to the timeout stack through
973
		this.counter = this.counter ? ++this.counter : 1;
974
		var self = this, counter = this.counter;
975

    
976
		window.setTimeout(function() {
977
			if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
978
		},0);
979

    
980
	},
981

    
982
	_clear: function(event, noPropagation) {
983

    
984
		this.reverting = false;
985
		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
986
		// everything else normalized again
987
		var delayedTriggers = [], self = this;
988

    
989
		// We first have to update the dom position of the actual currentItem
990
		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
991
		if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
992
		this._noFinalSort = null;
993

    
994
		if(this.helper[0] == this.currentItem[0]) {
995
			for(var i in this._storedCSS) {
996
				if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
997
			}
998
			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
999
		} else {
1000
			this.currentItem.show();
1001
		}
1002

    
1003
		if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
1004
		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
1005
		if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
1006
			if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
1007
			for (var i = this.containers.length - 1; i >= 0; i--){
1008
				if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
1009
					delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
1010
					delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.containers[i]));
1011
				}
1012
			};
1013
		};
1014

    
1015
		//Post events to containers
1016
		for (var i = this.containers.length - 1; i >= 0; i--){
1017
			if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
1018
			if(this.containers[i].containerCache.over) {
1019
				delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
1020
				this.containers[i].containerCache.over = 0;
1021
			}
1022
		}
1023

    
1024
		//Do what was originally in plugins
1025
		if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
1026
		if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
1027
		if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
1028

    
1029
		this.dragging = false;
1030
		if(this.cancelHelperRemoval) {
1031
			if(!noPropagation) {
1032
				this._trigger("beforeStop", event, this._uiHash());
1033
				for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
1034
				this._trigger("stop", event, this._uiHash());
1035
			}
1036
			return false;
1037
		}
1038

    
1039
		if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
1040

    
1041
		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
1042
		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
1043

    
1044
		if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
1045

    
1046
		if(!noPropagation) {
1047
			for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
1048
			this._trigger("stop", event, this._uiHash());
1049
		}
1050

    
1051
		this.fromOutside = false;
1052
		return true;
1053

    
1054
	},
1055

    
1056
	_trigger: function() {
1057
		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
1058
			this.cancel();
1059
		}
1060
	},
1061

    
1062
	_uiHash: function(inst) {
1063
		var self = inst || this;
1064
		return {
1065
			helper: self.helper,
1066
			placeholder: self.placeholder || $([]),
1067
			position: self.position,
1068
			originalPosition: self.originalPosition,
1069
			offset: self.positionAbs,
1070
			item: self.currentItem,
1071
			sender: inst ? inst.element : null
1072
		};
1073
	}
1074

    
1075
});
1076

    
1077
$.extend($.ui.sortable, {
1078
	version: "1.8.20"
1079
});
1080

    
1081
})(jQuery);
(30-30/32)