Sortable.js 26.2 KB
Newer Older
R
RubaXa 已提交
1 2 3 4 5 6 7
/**!
 * Sortable
 * @author	RubaXa   <trash@rubaxa.org>
 * @license MIT
 */


R
RubaXa 已提交
8
(function (factory) {
R
RubaXa 已提交
9 10
	"use strict";

R
RubaXa 已提交
11
	if (typeof define === "function" && define.amd) {
R
RubaXa 已提交
12
		define(factory);
R
RubaXa 已提交
13
	}
R
RubaXa 已提交
14
	else if (typeof module != "undefined" && typeof module.exports != "undefined") {
S
Scott Nelson 已提交
15 16
		module.exports = factory();
	}
R
RubaXa 已提交
17
	else if (typeof Package !== "undefined") {
18 19
		Sortable = factory();  // export for Meteor.js
	}
R
RubaXa 已提交
20
	else {
R
RubaXa 已提交
21
		/* jshint sub:true */
R
RubaXa 已提交
22 23
		window["Sortable"] = factory();
	}
R
RubaXa 已提交
24
})(function () {
R
RubaXa 已提交
25 26
	"use strict";

R
RubaXa 已提交
27 28 29 30 31
	var dragEl,
		ghostEl,
		cloneEl,
		rootEl,
		nextEl,
R
RubaXa 已提交
32

33 34 35
		scrollEl,
		scrollParentEl,

R
RubaXa 已提交
36 37
		lastEl,
		lastCSS,
R
RubaXa 已提交
38

R
RubaXa 已提交
39 40 41
		oldIndex,
		newIndex,

R
RubaXa 已提交
42
		activeGroup,
R
RubaXa 已提交
43
		autoScroll = {},
R
RubaXa 已提交
44

R
RubaXa 已提交
45 46
		tapEvt,
		touchEvt,
R
RubaXa 已提交
47

C
ChiefORZ 已提交
48 49
		moved,

R
RubaXa 已提交
50 51 52
		/** @const */
		RSPACE = /\s+/g,

R
RubaXa 已提交
53
		expando = 'Sortable' + (new Date).getTime(),
R
RubaXa 已提交
54

R
RubaXa 已提交
55 56 57
		win = window,
		document = win.document,
		parseInt = win.parseInt,
R
RubaXa 已提交
58 59 60

		supportDraggable = !!('draggable' in document.createElement('div')),

R
RubaXa 已提交
61
		_silent = false,
R
RubaXa 已提交
62

R
RubaXa 已提交
63
		abs = Math.abs,
R
RubaXa 已提交
64
		slice = [].slice,
R
RubaXa 已提交
65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
		touchDragOverListeners = [],

		_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
			// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
			if (rootEl && options.scroll) {
				var el,
					rect,
					sens = options.scrollSensitivity,
					speed = options.scrollSpeed,

					x = evt.clientX,
					y = evt.clientY,

					winWidth = window.innerWidth,
					winHeight = window.innerHeight,

					vx,
					vy
				;

				// Delect scrollEl
				if (scrollParentEl !== rootEl) {
					scrollEl = options.scroll;
					scrollParentEl = rootEl;

					if (scrollEl === true) {
						scrollEl = rootEl;

						do {
							if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
								(scrollEl.offsetHeight < scrollEl.scrollHeight)
							) {
								break;
							}
							/* jshint boss:true */
						} while (scrollEl = scrollEl.parentNode);
					}
				}

				if (scrollEl) {
					el = scrollEl;
					rect = scrollEl.getBoundingClientRect();
					vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
					vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
				}


				if (!(vx || vy)) {
					vx = (winWidth - x <= sens) - (x <= sens);
					vy = (winHeight - y <= sens) - (y <= sens);

					/* jshint expr:true */
					(vx || vy) && (el = win);
				}


				if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
					autoScroll.el = el;
					autoScroll.vx = vx;
					autoScroll.vy = vy;

					clearInterval(autoScroll.pid);

					if (el) {
						autoScroll.pid = setInterval(function () {
							if (el === win) {
132
								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
133 134 135 136 137 138 139 140 141
							} else {
								vy && (el.scrollTop += vy * speed);
								vx && (el.scrollLeft += vx * speed);
							}
						}, 24);
					}
				}
			}
		}, 30)
R
RubaXa 已提交
142 143 144
	;


145

R
RubaXa 已提交
146 147 148
	/**
	 * @class  Sortable
	 * @param  {HTMLElement}  el
149
	 * @param  {Object}       [options]
R
RubaXa 已提交
150
	 */
R
RubaXa 已提交
151
	function Sortable(el, options) {
R
RubaXa 已提交
152
		this.el = el; // root element
153
		this.options = options = _extend({}, options);
R
RubaXa 已提交
154 155


156 157 158 159
		// Export instance
		el[expando] = this;


R
RubaXa 已提交
160
		// Default options
161 162
		var defaults = {
			group: Math.random(),
R
RubaXa 已提交
163
			sort: true,
R
RubaXa 已提交
164
			disabled: false,
165 166
			store: null,
			handle: null,
R
RubaXa 已提交
167 168 169
			scroll: true,
			scrollSensitivity: 30,
			scrollSpeed: 10,
R
RubaXa 已提交
170
			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
171 172
			ghostClass: 'sortable-ghost',
			ignore: 'a, img',
173
			filter: null,
R
RubaXa 已提交
174 175 176
			animation: 0,
			setData: function (dataTransfer, dragEl) {
				dataTransfer.setData('Text', dragEl.textContent);
177 178
			},
			dropBubble: false,
R
RubaXa 已提交
179
			dragoverBubble: false,
180
			dataIdAttr: 'data-id',
181
			delay: 0,
C
ChiefORZ 已提交
182 183 184
			forceFallback: false,
			fallbackClass: 'sortable-fallback',
			fallbackOnBody: false
R
RubaXa 已提交
185
		};
186

R
RubaXa 已提交
187

188 189
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
190
			!(name in options) && (options[name] = defaults[name]);
191
		}
R
RubaXa 已提交
192

193

R
RubaXa 已提交
194 195
		var group = options.group;

R
RubaXa 已提交
196 197
		if (!group || typeof group != 'object') {
			group = options.group = { name: group };
R
RubaXa 已提交
198 199
		}

R
RubaXa 已提交
200

R
RubaXa 已提交
201
		['pull', 'put'].forEach(function (key) {
R
RubaXa 已提交
202 203
			if (!(key in group)) {
				group[key] = true;
R
RubaXa 已提交
204 205 206 207
			}
		});


208
		options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
R
RubaXa 已提交
209 210


R
* JSDoc  
RubaXa 已提交
211
		// Bind all private methods
R
RubaXa 已提交
212 213
		for (var fn in this) {
			if (fn.charAt(0) === '_') {
214
				this[fn] = this[fn].bind(this);
R
RubaXa 已提交
215 216 217 218 219 220 221 222
			}
		}


		// Bind events
		_on(el, 'mousedown', this._onTapStart);
		_on(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
223 224
		_on(el, 'dragover', this);
		_on(el, 'dragenter', this);
R
RubaXa 已提交
225 226

		touchDragOverListeners.push(this._onDragOver);
227 228 229

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
230 231 232
	}


233
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
234 235
		constructor: Sortable,

236
		_onTapStart: function (/** Event|TouchEvent */evt) {
237 238
			var _this = this,
				el = this.el,
239 240 241 242 243 244
				options = this.options,
				type = evt.type,
				touch = evt.touches && evt.touches[0],
				target = (touch || evt).target,
				originalTarget = target,
				filter = options.filter;
R
RubaXa 已提交
245 246


247 248
			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
				return; // only left button or enabled
R
RubaXa 已提交
249
			}
R
RubaXa 已提交
250

251
			target = _closest(target, options.draggable, el);
252

253 254
			if (!target) {
				return;
255
			}
256 257 258 259 260 261 262

			// get the index of the dragged element within its parent
			oldIndex = _index(target);

			// Check filter
			if (typeof filter === 'function') {
				if (filter.call(this, evt, target, this)) {
263
					_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
264 265 266
					evt.preventDefault();
					return; // cancel dnd
				}
267
			}
268 269 270
			else if (filter) {
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);
271

272
					if (criteria) {
273
						_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
274 275 276 277 278 279 280
						return true;
					}
				});

				if (filter) {
					evt.preventDefault();
					return; // cancel dnd
281 282 283 284
				}
			}


285 286 287
			if (options.handle && !_closest(originalTarget, options.handle, el)) {
				return;
			}
288

289 290 291

			// Prepare `dragstart`
			this._prepareDragStart(evt, touch, target);
292 293
		},

294 295 296 297 298 299
		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
			var _this = this,
				el = _this.el,
				options = _this.options,
				ownerDocument = el.ownerDocument,
				dragStartFn;
300 301 302 303

			if (target && !dragEl && (target.parentNode === el)) {
				tapEvt = evt;

304
				rootEl = el;
305 306
				dragEl = target;
				nextEl = dragEl.nextSibling;
307 308 309 310 311 312
				activeGroup = options.group;

				dragStartFn = function () {
					// Delayed drag has been triggered
					// we can re-enable the events: touchmove/mousemove
					_this._disableDelayedDrag();
313

314 315
					// Make the element draggable
					dragEl.draggable = true;
316

317 318 319 320 321 322 323 324 325 326 327 328 329 330
					// Disable "draggable"
					options.ignore.split(',').forEach(function (criteria) {
						_find(dragEl, criteria.trim(), _disableDraggable);
					});

					// Bind the events: dragstart/dragend
					_this._triggerDragStart(touch);
				};

				_on(ownerDocument, 'mouseup', _this._onDrop);
				_on(ownerDocument, 'touchend', _this._onDrop);
				_on(ownerDocument, 'touchcancel', _this._onDrop);

				if (options.delay) {
331 332
					// If the user moves the pointer or let go the click or touch
					// before the delay has been reached:
333
					// disable the delayed drag
334 335 336
					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
337 338 339 340 341 342
					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);

					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
				} else {
					dragStartFn();
343 344 345
				}
			}
		},
R
RubaXa 已提交
346

347 348
		_disableDelayedDrag: function () {
			var ownerDocument = this.el.ownerDocument;
349

350
			clearTimeout(this._dragStartTimer);
351 352 353
			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
354 355 356
			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
		},
R
RubaXa 已提交
357

358 359 360 361 362 363 364 365
		_triggerDragStart: function (/** Touch */touch) {
			if (touch) {
				// Touch device support
				tapEvt = {
					target: dragEl,
					clientX: touch.clientX,
					clientY: touch.clientY
				};
366

367
				this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
368
			}
C
ChiefORZ 已提交
369
			else if (!supportDraggable || this.options.forceFallback) {
370 371 372 373 374
				this._onDragStart(tapEvt, true);
			}
			else {
				_on(dragEl, 'dragend', this);
				_on(rootEl, 'dragstart', this._onDragStart);
375 376
			}

377 378 379 380 381
			try {
				if (document.selection) {
					document.selection.empty();
				} else {
					window.getSelection().removeAllRanges();
382
				}
383
			} catch (err) {
384
			}
385
		},
386

387 388 389 390
		_dragStarted: function () {
			if (rootEl && dragEl) {
				// Apply effect
				_toggleClass(dragEl, this.options.ghostClass, true);
R
RubaXa 已提交
391

392
				Sortable.active = this;
393

394
				// Drag start event
395
				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
396
			}
R
RubaXa 已提交
397 398
		},

R
RubaXa 已提交
399 400
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
401 402
				_css(ghostEl, 'display', 'none');

R
RubaXa 已提交
403
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
R
RubaXa 已提交
404
					parent = target,
405
					groupName = ' ' + this.options.group.name + '',
R
RubaXa 已提交
406
					i = touchDragOverListeners.length;
R
RubaXa 已提交
407

R
RubaXa 已提交
408 409
				if (parent) {
					do {
410
						if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
R
RubaXa 已提交
411 412 413 414 415 416 417 418 419 420 421 422 423
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

							break;
						}

						target = parent; // store last element
L
Larry Davis 已提交
424
					}
R
RubaXa 已提交
425 426
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
427 428 429 430 431 432 433
				}

				_css(ghostEl, 'display', '');
			}
		},


R
RubaXa 已提交
434 435
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
436 437 438 439 440 441
				// only set the status to dragging, when we are actually dragging
				if(!Sortable.active) {
					this._dragStarted();
				}
				// as well as creating the ghost element on the document body
				this._appendGhost();
R
RubaXa 已提交
442
				var touch = evt.touches ? evt.touches[0] : evt,
R
RubaXa 已提交
443 444
					dx = touch.clientX - tapEvt.clientX,
					dy = touch.clientY - tapEvt.clientY,
R
RubaXa 已提交
445
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
R
RubaXa 已提交
446 447

				touchEvt = touch;
R
RubaXa 已提交
448

C
ChiefORZ 已提交
449 450
				moved = true;

R
RubaXa 已提交
451 452 453 454 455
				_css(ghostEl, 'webkitTransform', translate3d);
				_css(ghostEl, 'mozTransform', translate3d);
				_css(ghostEl, 'msTransform', translate3d);
				_css(ghostEl, 'transform', translate3d);

M
Marius Petcu 已提交
456
				evt.preventDefault();
R
RubaXa 已提交
457 458 459
			}
		},

460 461
		_appendGhost: function() {
			if(!ghostEl) {
R
RubaXa 已提交
462 463 464
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
					ghostRect;
R
RubaXa 已提交
465

466
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
467

C
ChiefORZ 已提交
468 469 470
				_toggleClass(ghostEl, this.options.ghostClass, false);
				_toggleClass(ghostEl, this.options.fallbackClass, true);

R
RubaXa 已提交
471 472
				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
473 474
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
475 476 477 478
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');

C
ChiefORZ 已提交
479
				this.options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
R
RubaXa 已提交
480 481 482

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
R
RubaXa 已提交
483 484
				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
			}
		},

		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
			var dataTransfer = evt.dataTransfer,
				options = this.options;

			this._offUpEvents();

			if (activeGroup.pull == 'clone') {
				cloneEl = dragEl.cloneNode(true);
				_css(cloneEl, 'display', 'none');
				rootEl.insertBefore(cloneEl, dragEl);
			}

			if (useFallback) {
R
RubaXa 已提交
501

R
RubaXa 已提交
502 503 504 505 506 507 508 509 510 511
				if (useFallback === 'touch') {
					// Bind touch events
					_on(document, 'touchmove', this._onTouchMove);
					_on(document, 'touchend', this._onDrop);
					_on(document, 'touchcancel', this._onDrop);
				} else {
					// Old brwoser
					_on(document, 'mousemove', this._onTouchMove);
					_on(document, 'mouseup', this._onDrop);
				}
R
RubaXa 已提交
512

R
RubaXa 已提交
513
				this._loopId = setInterval(this._emulateDragOver, 150);
R
RubaXa 已提交
514 515
			}
			else {
R
RubaXa 已提交
516 517 518 519
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
					options.setData && options.setData.call(this, dataTransfer, dragEl);
				}
R
RubaXa 已提交
520

R
RubaXa 已提交
521
				_on(document, 'drop', this);
522
				setTimeout(this._dragStarted, 0);
R
RubaXa 已提交
523
			}
524
			
R
RubaXa 已提交
525 526
		},

R
RubaXa 已提交
527
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
528 529 530 531 532 533
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
534
				groupPut = group.put,
535 536
				isOwner = (activeGroup === group),
				canSort = options.sort;
R
RubaXa 已提交
537

R
RubaXa 已提交
538 539
			if (evt.preventDefault !== void 0) {
				evt.preventDefault();
540
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
541
			}
R
RubaXa 已提交
542

R
RubaXa 已提交
543
			if (activeGroup && !options.disabled &&
544
				(isOwner
R
RubaXa 已提交
545
					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
R
RubaXa 已提交
546 547 548 549
					: activeGroup.pull && groupPut && (
						(activeGroup.name === group.name) || // by Name
						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
					)
550
				) &&
R
RubaXa 已提交
551
				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
R
RubaXa 已提交
552
			) {
R
RubaXa 已提交
553 554 555 556 557 558 559
				// Smart auto-scrolling
				_autoScroll(evt, options, this.el);

				if (_silent) {
					return;
				}

R
RubaXa 已提交
560
				target = _closest(evt.target, options.draggable, el);
R
RubaXa 已提交
561 562 563
				dragRect = dragEl.getBoundingClientRect();


564
				if (revert) {
R
RubaXa 已提交
565 566
					_cloneHide(true);

567 568 569 570 571 572 573
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
574 575
					return;
				}
R
RubaXa 已提交
576

R
RubaXa 已提交
577

R
RubaXa 已提交
578
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
R
RubaXa 已提交
579
					(el === evt.target) && (target = _ghostInBottom(el, evt))
R
RubaXa 已提交
580
				) {
R
RubaXa 已提交
581 582 583 584 585 586
					if (target) {
						if (target.animated) {
							return;
						}
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
587

R
RubaXa 已提交
588 589
					_cloneHide(isOwner);

R
RubaXa 已提交
590
					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
R
RubaXa 已提交
591 592 593 594
						el.appendChild(dragEl);
						this._animate(dragRect, dragEl);
						target && this._animate(targetRect, target);
					}
R
RubaXa 已提交
595
				}
R
RubaXa 已提交
596 597
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
598
						lastEl = target;
R
RubaXa 已提交
599
						lastCSS = _css(target);
R
RubaXa 已提交
600 601 602
					}


R
RubaXa 已提交
603 604 605 606 607 608 609 610
					var targetRect = target.getBoundingClientRect(),
						width = targetRect.right - targetRect.left,
						height = targetRect.bottom - targetRect.top,
						floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display),
						isWide = (target.offsetWidth > dragEl.offsetWidth),
						isLong = (target.offsetHeight > dragEl.offsetHeight),
						halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
						nextSibling = target.nextElementSibling,
R
RubaXa 已提交
611
						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
R
RubaXa 已提交
612
						after
R
RubaXa 已提交
613
					;
R
RubaXa 已提交
614

R
RubaXa 已提交
615
					if (moveVector !== false) {
R
RubaXa 已提交
616 617
						_silent = true;
						setTimeout(_unsilent, 30);
R
RubaXa 已提交
618

R
RubaXa 已提交
619
						_cloneHide(isOwner);
R
RubaXa 已提交
620

R
RubaXa 已提交
621 622 623 624
						if (moveVector === 1 || moveVector === -1) {
							after = (moveVector === 1);
						}
						else if (floating) {
R
RubaXa 已提交
625 626 627 628
							after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
						} else {
							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
						}
R
RubaXa 已提交
629

R
RubaXa 已提交
630 631 632 633 634
						if (after && !nextSibling) {
							el.appendChild(dragEl);
						} else {
							target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
						}
R
RubaXa 已提交
635

R
RubaXa 已提交
636 637 638
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
					}
R
RubaXa 已提交
639 640 641 642
				}
			}
		},

643 644 645 646 647 648
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

			if (ms) {
				var currentRect = target.getBoundingClientRect();

R
RubaXa 已提交
649
				_css(target, 'transition', 'none');
650 651 652 653 654 655 656
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
657
				_css(target, 'transition', 'all ' + ms + 'ms');
658 659
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
660 661
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
662
					_css(target, 'transition', '');
663
					_css(target, 'transform', '');
664 665 666 667 668
					target.animated = false;
				}, ms);
			}
		},

669
		_offUpEvents: function () {
670 671
			var ownerDocument = this.el.ownerDocument;

672
			_off(document, 'touchmove', this._onTouchMove);
673 674 675
			_off(ownerDocument, 'mouseup', this._onDrop);
			_off(ownerDocument, 'touchend', this._onDrop);
			_off(ownerDocument, 'touchcancel', this._onDrop);
676
		},
R
RubaXa 已提交
677

R
RubaXa 已提交
678
		_onDrop: function (/**Event*/evt) {
679 680
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
681

R
RubaXa 已提交
682
			clearInterval(this._loopId);
R
RubaXa 已提交
683
			clearInterval(autoScroll.pid);
R
RubaXa 已提交
684
			clearTimeout(this._dragStartTimer);
685

R
RubaXa 已提交
686
			// Unbind events
R
RubaXa 已提交
687
			_off(document, 'drop', this);
R
RubaXa 已提交
688
			_off(document, 'mousemove', this._onTouchMove);
R
RubaXa 已提交
689
			_off(el, 'dragstart', this._onDragStart);
R
RubaXa 已提交
690

691
			this._offUpEvents();
R
RubaXa 已提交
692

R
RubaXa 已提交
693
			if (evt) {
C
ChiefORZ 已提交
694 695 696 697
				if(moved) {
					evt.preventDefault();
					!options.dropBubble && evt.stopPropagation();
				}
R
RubaXa 已提交
698
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
699

R
RubaXa 已提交
700
				if (dragEl) {
R
RubaXa 已提交
701 702
					_off(dragEl, 'dragend', this);

703
					_disableDraggable(dragEl);
R
RubaXa 已提交
704 705
					_toggleClass(dragEl, this.options.ghostClass, false);

706
					if (rootEl !== dragEl.parentNode) {
R
RubaXa 已提交
707 708
						newIndex = _index(dragEl);

709
						// drag from one list and drop into another
710 711
						_dispatchEvent(null, dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
						_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
712 713

						// Add event
714
						_dispatchEvent(null, dragEl.parentNode, 'add', dragEl, rootEl, oldIndex, newIndex);
715 716

						// Remove event
717
						_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
718
					}
R
RubaXa 已提交
719 720
					else {
						// Remove clone
R
RubaXa 已提交
721
						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
R
RubaXa 已提交
722

R
RubaXa 已提交
723 724 725
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
							newIndex = _index(dragEl);
R
RubaXa 已提交
726

R
RubaXa 已提交
727
							// drag & drop within the same list
728 729
							_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
							_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
730
						}
R
RubaXa 已提交
731
					}
732

R
RubaXa 已提交
733 734 735 736 737 738 739
					if (Sortable.active) {
						// Drag end event
						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);

						// Save sorting
						this.save();
					}
R
RubaXa 已提交
740 741
				}

742
				// Nulling
R
RubaXa 已提交
743 744 745 746
				rootEl =
				dragEl =
				ghostEl =
				nextEl =
R
RubaXa 已提交
747
				cloneEl =
R
RubaXa 已提交
748

749 750 751
				scrollEl =
				scrollParentEl =

R
RubaXa 已提交
752 753 754
				tapEvt =
				touchEvt =

C
ChiefORZ 已提交
755 756
				moved =

R
RubaXa 已提交
757 758 759
				lastEl =
				lastCSS =

R
RubaXa 已提交
760 761
				activeGroup =
				Sortable.active = null;
R
RubaXa 已提交
762 763 764 765
			}
		},


R
RubaXa 已提交
766 767 768
		handleEvent: function (/**Event*/evt) {
			var type = evt.type;

R
RubaXa 已提交
769
			if (type === 'dragover' || type === 'dragenter') {
R
RubaXa 已提交
770 771 772 773
				if (dragEl) {
					this._onDragOver(evt);
					_globalDragOver(evt);
				}
R
RubaXa 已提交
774
			}
R
RubaXa 已提交
775
			else if (type === 'drop' || type === 'dragend') {
R
RubaXa 已提交
776 777
				this._onDrop(evt);
			}
R
RubaXa 已提交
778 779 780
		},


781 782 783 784 785 786 787 788 789
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
790 791
				n = children.length,
				options = this.options;
792 793 794

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
795
				if (_closest(el, options.draggable, this.el)) {
R
RubaXa 已提交
796
					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
R
RubaXa 已提交
797
				}
798 799 800 801 802 803 804 805 806 807 808
			}

			return order;
		},


		/**
		 * Sorts the elements according to the array.
		 * @param  {String[]}  order  order of the items
		 */
		sort: function (order) {
R
RubaXa 已提交
809
			var items = {}, rootEl = this.el;
810 811

			this.toArray().forEach(function (id, i) {
R
RubaXa 已提交
812 813
				var el = rootEl.children[i];

R
RubaXa 已提交
814
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
815 816 817
					items[id] = el;
				}
			}, this);
818 819 820

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
821 822
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
823 824 825 826 827
				}
			});
		},


R
RubaXa 已提交
828 829 830 831 832 833 834 835 836
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


837 838 839 840 841 842 843 844 845 846 847
		/**
		 * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
		 * @param   {HTMLElement}  el
		 * @param   {String}       [selector]  default: `options.draggable`
		 * @returns {HTMLElement|null}
		 */
		closest: function (el, selector) {
			return _closest(el, selector || this.options.draggable, this.el);
		},


848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
		/**
		 * Set/get option
		 * @param   {string} name
		 * @param   {*}      [value]
		 * @returns {*}
		 */
		option: function (name, value) {
			var options = this.options;

			if (value === void 0) {
				return options[name];
			} else {
				options[name] = value;
			}
		},


865 866 867 868
		/**
		 * Destroy
		 */
		destroy: function () {
869
			var el = this.el;
R
RubaXa 已提交
870

871
			el[expando] = null;
872

R
RubaXa 已提交
873 874 875
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
876 877
			_off(el, 'dragover', this);
			_off(el, 'dragenter', this);
R
RubaXa 已提交
878

879
			// Remove draggable attributes
R
RubaXa 已提交
880
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
881 882 883
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
884 885 886 887
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

888
			this.el = el = null;
R
RubaXa 已提交
889 890 891
		}
	};

892

R
RubaXa 已提交
893 894 895 896 897 898 899 900 901
	function _cloneHide(state) {
		if (cloneEl && (cloneEl.state !== state)) {
			_css(cloneEl, 'display', state ? 'none' : '');
			!state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
			cloneEl.state = state;
		}
	}


R
RubaXa 已提交
902
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
903
		if (el) {
R
RubaXa 已提交
904 905 906
			ctx = ctx || document;
			selector = selector.split('.');

R
RubaXa 已提交
907
			var tag = selector.shift().toUpperCase(),
908
				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
R
RubaXa 已提交
909 910

			do {
R
RubaXa 已提交
911
				if (
R
RubaXa 已提交
912
					(tag === '>*' && el.parentNode === ctx) || (
R
raphj 已提交
913
						(tag === '' || el.nodeName.toUpperCase() == tag) &&
R
RubaXa 已提交
914 915
						(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
					)
R
RubaXa 已提交
916 917
				) {
					return el;
R
RubaXa 已提交
918 919
				}
			}
R
RubaXa 已提交
920
			while (el !== ctx && (el = el.parentNode));
R
RubaXa 已提交
921 922
		}

R
RubaXa 已提交
923
		return null;
R
RubaXa 已提交
924 925 926
	}


927
	function _globalDragOver(/**Event*/evt) {
R
RubaXa 已提交
928 929 930 931 932
		evt.dataTransfer.dropEffect = 'move';
		evt.preventDefault();
	}


R
RubaXa 已提交
933
	function _on(el, event, fn) {
R
RubaXa 已提交
934 935 936 937
		el.addEventListener(event, fn, false);
	}


R
RubaXa 已提交
938
	function _off(el, event, fn) {
R
RubaXa 已提交
939 940 941 942
		el.removeEventListener(event, fn, false);
	}


R
RubaXa 已提交
943 944 945
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
946 947 948
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
B
Bogdan Mustiata 已提交
949 950
				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
R
RubaXa 已提交
951 952 953 954 955
			}
		}
	}


R
RubaXa 已提交
956
	function _css(el, prop, val) {
R
RubaXa 已提交
957 958
		var style = el && el.style;

R
RubaXa 已提交
959 960 961
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
962 963
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
964 965
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
966
				}
R
RubaXa 已提交
967 968 969 970 971 972 973 974 975

				return prop === void 0 ? val : val[prop];
			}
			else {
				if (!(prop in style)) {
					prop = '-webkit-' + prop;
				}

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
976 977 978 979 980
			}
		}
	}


R
RubaXa 已提交
981 982
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
983
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
984

R
RubaXa 已提交
985 986
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
987 988 989
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
990

R
RubaXa 已提交
991
			return list;
R
RubaXa 已提交
992
		}
R
RubaXa 已提交
993 994

		return [];
R
RubaXa 已提交
995 996 997
	}


R
RubaXa 已提交
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013

	function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
		var evt = document.createEvent('Event'),
			options = (sortable || rootEl[expando]).options,
			onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);

		evt.initEvent(name, true, true);

		evt.to = rootEl;
		evt.from = fromEl || rootEl;
		evt.item = targetEl || rootEl;
		evt.clone = cloneEl;

		evt.oldIndex = startIndex;
		evt.newIndex = newIndex;

R
RubaXa 已提交
1014 1015
		rootEl.dispatchEvent(evt);

R
RubaXa 已提交
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
		if (options[onName]) {
			options[onName].call(sortable, evt);
		}
	}


	function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
		var evt,
			sortable = fromEl[expando],
			onMoveFn = sortable.options.onMove,
			retVal;

C
ChiefORZ 已提交
1028 1029
		evt = document.createEvent('Event');
		evt.initEvent('move', true, true);
R
RubaXa 已提交
1030

C
ChiefORZ 已提交
1031 1032 1033 1034 1035 1036
		evt.to = toEl;
		evt.from = fromEl;
		evt.dragged = dragEl;
		evt.draggedRect = dragRect;
		evt.related = targetEl || toEl;
		evt.relatedRect = targetRect || toEl.getBoundingClientRect();
R
RubaXa 已提交
1037

C
ChiefORZ 已提交
1038
		fromEl.dispatchEvent(evt);
R
RubaXa 已提交
1039

C
ChiefORZ 已提交
1040
		if (onMoveFn) {
R
RubaXa 已提交
1041 1042 1043
			retVal = onMoveFn.call(sortable, evt);
		}

R
RubaXa 已提交
1044
		return retVal;
R
RubaXa 已提交
1045 1046 1047
	}


R
RubaXa 已提交
1048
	function _disableDraggable(el) {
R
RubaXa 已提交
1049
		el.draggable = false;
R
RubaXa 已提交
1050 1051 1052
	}


R
RubaXa 已提交
1053
	function _unsilent() {
R
RubaXa 已提交
1054 1055 1056 1057
		_silent = false;
	}


R
RubaXa 已提交
1058
	/** @returns {HTMLElement|false} */
R
RubaXa 已提交
1059
	function _ghostInBottom(el, evt) {
R
RubaXa 已提交
1060 1061 1062
		var lastEl = el.lastElementChild,
			rect = lastEl.getBoundingClientRect();

R
RubaXa 已提交
1063
		return (evt.clientY - (rect.top + rect.height) > 5) && lastEl; // min delta
R
RubaXa 已提交
1064 1065 1066
	}


1067 1068 1069 1070 1071 1072 1073
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
1074
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
1075
			i = str.length,
R
RubaXa 已提交
1076
			sum = 0;
1077

1078 1079 1080
		while (i--) {
			sum += str.charCodeAt(i);
		}
1081

1082 1083 1084
		return sum.toString(36);
	}

1085 1086 1087
	/**
	 * Returns the index of an element within its parent
	 * @param el
1088 1089
	 * @returns {number}
	 * @private
1090 1091 1092
	 */
	function _index(/**HTMLElement*/el) {
		var index = 0;
1093 1094 1095 1096
		while (el && (el = el.previousElementSibling)) {
			if (el.nodeName.toUpperCase() !== 'TEMPLATE') {
				index++;
			}
1097 1098 1099
		}
		return index;
	}
R
RubaXa 已提交
1100

R
RubaXa 已提交
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
	function _throttle(callback, ms) {
		var args, _this;

		return function () {
			if (args === void 0) {
				args = arguments;
				_this = this;

				setTimeout(function () {
					if (args.length === 1) {
						callback.call(_this, args[0]);
					} else {
						callback.apply(_this, args);
					}

					args = void 0;
				}, ms);
			}
		};
	}

1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133
	function _extend(dst, src) {
		if (dst && src) {
			for (var key in src) {
				if (src.hasOwnProperty(key)) {
					dst[key] = src[key];
				}
			}
		}

		return dst;
	}

R
RubaXa 已提交
1134

R
RubaXa 已提交
1135 1136 1137 1138 1139 1140
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
R
RubaXa 已提交
1141 1142 1143
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
1144
		extend: _extend,
R
RubaXa 已提交
1145
		throttle: _throttle,
R
RubaXa 已提交
1146
		closest: _closest,
1147
		toggleClass: _toggleClass,
1148
		index: _index
R
RubaXa 已提交
1149 1150 1151
	};


A
Adam Fleming 已提交
1152
	Sortable.version = '1.2.1';
1153

R
RubaXa 已提交
1154

1155 1156 1157 1158 1159 1160
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1161
		return new Sortable(el, options);
1162
	};
R
RubaXa 已提交
1163 1164

	// Export
1165
	return Sortable;
R
RubaXa 已提交
1166
});