Sortable.js 25.4 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

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

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

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

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

R
RubaXa 已提交
59
		_silent = false,
R
RubaXa 已提交
60

R
RubaXa 已提交
61
		abs = Math.abs,
R
RubaXa 已提交
62
		slice = [].slice,
R
RubaXa 已提交
63

64 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
		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) {
130
								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
131 132 133 134 135 136 137 138 139
							} else {
								vy && (el.scrollTop += vy * speed);
								vx && (el.scrollLeft += vx * speed);
							}
						}, 24);
					}
				}
			}
		}, 30)
R
RubaXa 已提交
140 141 142
	;


143

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


154 155 156 157
		// Export instance
		el[expando] = this;


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

R
RubaXa 已提交
182

183 184
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
185
			!(name in options) && (options[name] = defaults[name]);
186
		}
R
RubaXa 已提交
187

188

R
RubaXa 已提交
189 190
		var group = options.group;

R
RubaXa 已提交
191 192
		if (!group || typeof group != 'object') {
			group = options.group = { name: group };
R
RubaXa 已提交
193 194
		}

R
RubaXa 已提交
195

R
RubaXa 已提交
196
		['pull', 'put'].forEach(function (key) {
R
RubaXa 已提交
197 198
			if (!(key in group)) {
				group[key] = true;
R
RubaXa 已提交
199 200 201 202
			}
		});


203
		options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
R
RubaXa 已提交
204 205


R
* JSDoc  
RubaXa 已提交
206
		// Bind all private methods
R
RubaXa 已提交
207 208
		for (var fn in this) {
			if (fn.charAt(0) === '_') {
R
RubaXa 已提交
209 210 211 212 213 214 215 216 217
				this[fn] = _bind(this, this[fn]);
			}
		}


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

R
RubaXa 已提交
218 219
		_on(el, 'dragover', this);
		_on(el, 'dragenter', this);
R
RubaXa 已提交
220 221

		touchDragOverListeners.push(this._onDragOver);
222 223 224

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
225 226 227
	}


228
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
229 230
		constructor: Sortable,

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


242 243
			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
				return; // only left button or enabled
R
RubaXa 已提交
244
			}
R
RubaXa 已提交
245

246
			target = _closest(target, options.draggable, el);
247

248 249
			if (!target) {
				return;
250
			}
251 252 253 254 255 256 257

			// 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)) {
258
					_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
259 260 261
					evt.preventDefault();
					return; // cancel dnd
				}
262
			}
263 264 265
			else if (filter) {
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);
266

267
					if (criteria) {
268
						_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
269 270 271 272 273 274 275
						return true;
					}
				});

				if (filter) {
					evt.preventDefault();
					return; // cancel dnd
276 277 278 279
				}
			}


280 281 282
			if (options.handle && !_closest(originalTarget, options.handle, el)) {
				return;
			}
283

284 285 286

			// Prepare `dragstart`
			this._prepareDragStart(evt, touch, target);
287 288
		},

289 290 291 292 293 294
		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
			var _this = this,
				el = _this.el,
				options = _this.options,
				ownerDocument = el.ownerDocument,
				dragStartFn;
295 296 297 298

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

299
				rootEl = el;
300 301
				dragEl = target;
				nextEl = dragEl.nextSibling;
302 303 304 305 306 307
				activeGroup = options.group;

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

309 310
					// Make the element draggable
					dragEl.draggable = true;
311

312 313 314 315 316 317 318 319 320 321 322 323 324 325
					// 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) {
326 327
					// If the user moves the pointer before the delay has been reached:
					// disable the delayed drag
328 329 330 331 332 333
					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);

					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
				} else {
					dragStartFn();
334 335 336
				}
			}
		},
R
RubaXa 已提交
337

338 339
		_disableDelayedDrag: function () {
			var ownerDocument = this.el.ownerDocument;
340

341
			clearTimeout(this._dragStartTimer);
R
RubaXa 已提交
342

343 344 345
			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
		},
R
RubaXa 已提交
346

347 348 349 350 351 352 353 354
		_triggerDragStart: function (/** Touch */touch) {
			if (touch) {
				// Touch device support
				tapEvt = {
					target: dragEl,
					clientX: touch.clientX,
					clientY: touch.clientY
				};
355

356
				this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
357
			}
358 359 360 361 362 363
			else if (!supportDraggable) {
				this._onDragStart(tapEvt, true);
			}
			else {
				_on(dragEl, 'dragend', this);
				_on(rootEl, 'dragstart', this._onDragStart);
364 365
			}

366 367 368 369 370
			try {
				if (document.selection) {
					document.selection.empty();
				} else {
					window.getSelection().removeAllRanges();
371
				}
372
			} catch (err) {
373
			}
374
		},
375

376 377 378 379
		_dragStarted: function () {
			if (rootEl && dragEl) {
				// Apply effect
				_toggleClass(dragEl, this.options.ghostClass, true);
R
RubaXa 已提交
380

381
				Sortable.active = this;
382

383
				// Drag start event
384
				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
385
			}
R
RubaXa 已提交
386 387
		},

R
RubaXa 已提交
388 389
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
390 391
				_css(ghostEl, 'display', 'none');

R
RubaXa 已提交
392
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
R
RubaXa 已提交
393
					parent = target,
394
					groupName = ' ' + this.options.group.name + '',
R
RubaXa 已提交
395
					i = touchDragOverListeners.length;
R
RubaXa 已提交
396

R
RubaXa 已提交
397 398
				if (parent) {
					do {
399
						if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
R
RubaXa 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

							break;
						}

						target = parent; // store last element
L
Larry Davis 已提交
413
					}
R
RubaXa 已提交
414 415
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
416 417 418 419 420 421 422
				}

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


R
RubaXa 已提交
423 424
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
R
RubaXa 已提交
425
				var touch = evt.touches ? evt.touches[0] : evt,
R
RubaXa 已提交
426 427
					dx = touch.clientX - tapEvt.clientX,
					dy = touch.clientY - tapEvt.clientY,
R
RubaXa 已提交
428
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
R
RubaXa 已提交
429 430

				touchEvt = touch;
R
RubaXa 已提交
431 432 433 434 435 436

				_css(ghostEl, 'webkitTransform', translate3d);
				_css(ghostEl, 'mozTransform', translate3d);
				_css(ghostEl, 'msTransform', translate3d);
				_css(ghostEl, 'transform', translate3d);

M
Marius Petcu 已提交
437
				evt.preventDefault();
R
RubaXa 已提交
438 439 440 441
			}
		},


R
RubaXa 已提交
442
		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
R
RubaXa 已提交
443 444
			var dataTransfer = evt.dataTransfer,
				options = this.options;
R
RubaXa 已提交
445

446
			this._offUpEvents();
R
RubaXa 已提交
447

R
RubaXa 已提交
448 449 450 451 452 453
			if (activeGroup.pull == 'clone') {
				cloneEl = dragEl.cloneNode(true);
				_css(cloneEl, 'display', 'none');
				rootEl.insertBefore(cloneEl, dragEl);
			}

R
RubaXa 已提交
454
			if (useFallback) {
R
RubaXa 已提交
455 456 457
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
					ghostRect;
R
RubaXa 已提交
458

459
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
460 461 462

				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
463 464
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
465 466 467 468
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');

R
RubaXa 已提交
469 470 471 472
				rootEl.appendChild(ghostEl);

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
R
RubaXa 已提交
473 474
				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
R
RubaXa 已提交
475

R
RubaXa 已提交
476 477 478 479 480 481 482 483 484 485
				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 已提交
486

R
RubaXa 已提交
487
				this._loopId = setInterval(this._emulateDragOver, 150);
R
RubaXa 已提交
488 489
			}
			else {
R
RubaXa 已提交
490 491 492 493
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
					options.setData && options.setData.call(this, dataTransfer, dragEl);
				}
R
RubaXa 已提交
494

R
RubaXa 已提交
495
				_on(document, 'drop', this);
R
RubaXa 已提交
496 497
			}

R
RubaXa 已提交
498
			setTimeout(this._dragStarted, 0);
R
RubaXa 已提交
499 500
		},

R
RubaXa 已提交
501
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
502 503 504 505 506 507
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
508
				groupPut = group.put,
509 510
				isOwner = (activeGroup === group),
				canSort = options.sort;
R
RubaXa 已提交
511

R
RubaXa 已提交
512 513
			if (evt.preventDefault !== void 0) {
				evt.preventDefault();
514
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
515
			}
R
RubaXa 已提交
516

R
RubaXa 已提交
517
			if (activeGroup && !options.disabled &&
518
				(isOwner
R
RubaXa 已提交
519
					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
R
RubaXa 已提交
520 521 522 523
					: activeGroup.pull && groupPut && (
						(activeGroup.name === group.name) || // by Name
						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
					)
524
				) &&
R
RubaXa 已提交
525
				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
R
RubaXa 已提交
526
			) {
R
RubaXa 已提交
527 528 529 530 531 532 533
				// Smart auto-scrolling
				_autoScroll(evt, options, this.el);

				if (_silent) {
					return;
				}

R
RubaXa 已提交
534
				target = _closest(evt.target, options.draggable, el);
R
RubaXa 已提交
535 536 537
				dragRect = dragEl.getBoundingClientRect();


538
				if (revert) {
R
RubaXa 已提交
539 540
					_cloneHide(true);

541 542 543 544 545 546 547
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
548 549
					return;
				}
R
RubaXa 已提交
550

R
RubaXa 已提交
551

R
RubaXa 已提交
552
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
R
RubaXa 已提交
553
					(el === evt.target) && (target = _ghostInBottom(el, evt))
R
RubaXa 已提交
554
				) {
R
RubaXa 已提交
555 556 557 558 559 560
					if (target) {
						if (target.animated) {
							return;
						}
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
561

R
RubaXa 已提交
562 563
					_cloneHide(isOwner);

R
RubaXa 已提交
564
					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
R
RubaXa 已提交
565 566 567 568
						el.appendChild(dragEl);
						this._animate(dragRect, dragEl);
						target && this._animate(targetRect, target);
					}
R
RubaXa 已提交
569
				}
R
RubaXa 已提交
570 571
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
572
						lastEl = target;
R
RubaXa 已提交
573
						lastCSS = _css(target);
R
RubaXa 已提交
574 575 576
					}


R
RubaXa 已提交
577 578 579 580 581 582 583 584
					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 已提交
585
						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
R
RubaXa 已提交
586
						after
R
RubaXa 已提交
587
					;
R
RubaXa 已提交
588

R
RubaXa 已提交
589
					if (moveVector !== false) {
R
RubaXa 已提交
590 591
						_silent = true;
						setTimeout(_unsilent, 30);
R
RubaXa 已提交
592

R
RubaXa 已提交
593
						_cloneHide(isOwner);
R
RubaXa 已提交
594

R
RubaXa 已提交
595 596 597 598
						if (moveVector === 1 || moveVector === -1) {
							after = (moveVector === 1);
						}
						else if (floating) {
R
RubaXa 已提交
599 600 601 602
							after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
						} else {
							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
						}
R
RubaXa 已提交
603

R
RubaXa 已提交
604 605 606 607 608
						if (after && !nextSibling) {
							el.appendChild(dragEl);
						} else {
							target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
						}
R
RubaXa 已提交
609

R
RubaXa 已提交
610 611 612
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
					}
R
RubaXa 已提交
613 614 615 616
				}
			}
		},

617 618 619 620 621 622
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

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

R
RubaXa 已提交
623
				_css(target, 'transition', 'none');
624 625 626 627 628 629 630
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
631
				_css(target, 'transition', 'all ' + ms + 'ms');
632 633
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
634 635
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
636
					_css(target, 'transition', '');
637
					_css(target, 'transform', '');
638 639 640 641 642
					target.animated = false;
				}, ms);
			}
		},

643
		_offUpEvents: function () {
644 645
			var ownerDocument = this.el.ownerDocument;

646
			_off(document, 'touchmove', this._onTouchMove);
647 648 649
			_off(ownerDocument, 'mouseup', this._onDrop);
			_off(ownerDocument, 'touchend', this._onDrop);
			_off(ownerDocument, 'touchcancel', this._onDrop);
650
		},
R
RubaXa 已提交
651

R
RubaXa 已提交
652
		_onDrop: function (/**Event*/evt) {
653 654
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
655

R
RubaXa 已提交
656
			clearInterval(this._loopId);
R
RubaXa 已提交
657
			clearInterval(autoScroll.pid);
R
RubaXa 已提交
658

659 660
			clearTimeout(this.dragStartTimer);

R
RubaXa 已提交
661
			// Unbind events
R
RubaXa 已提交
662
			_off(document, 'drop', this);
R
RubaXa 已提交
663
			_off(document, 'mousemove', this._onTouchMove);
R
RubaXa 已提交
664
			_off(el, 'dragstart', this._onDragStart);
R
RubaXa 已提交
665

666
			this._offUpEvents();
R
RubaXa 已提交
667

R
RubaXa 已提交
668
			if (evt) {
R
RubaXa 已提交
669
				evt.preventDefault();
670
				!options.dropBubble && evt.stopPropagation();
R
RubaXa 已提交
671

R
RubaXa 已提交
672
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
673

R
RubaXa 已提交
674
				if (dragEl) {
R
RubaXa 已提交
675 676
					_off(dragEl, 'dragend', this);

677
					_disableDraggable(dragEl);
R
RubaXa 已提交
678 679
					_toggleClass(dragEl, this.options.ghostClass, false);

680
					if (rootEl !== dragEl.parentNode) {
R
RubaXa 已提交
681 682
						newIndex = _index(dragEl);

683
						// drag from one list and drop into another
684 685
						_dispatchEvent(null, dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
						_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
686 687

						// Add event
688
						_dispatchEvent(null, dragEl.parentNode, 'add', dragEl, rootEl, oldIndex, newIndex);
689 690

						// Remove event
691
						_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
692
					}
R
RubaXa 已提交
693 694
					else {
						// Remove clone
R
RubaXa 已提交
695
						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
R
RubaXa 已提交
696

R
RubaXa 已提交
697 698 699
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
							newIndex = _index(dragEl);
R
RubaXa 已提交
700

R
RubaXa 已提交
701
							// drag & drop within the same list
702 703
							_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
							_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
704
						}
R
RubaXa 已提交
705
					}
706

R
RubaXa 已提交
707 708 709 710 711 712 713
					if (Sortable.active) {
						// Drag end event
						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);

						// Save sorting
						this.save();
					}
R
RubaXa 已提交
714 715
				}

716
				// Nulling
R
RubaXa 已提交
717 718 719 720
				rootEl =
				dragEl =
				ghostEl =
				nextEl =
R
RubaXa 已提交
721
				cloneEl =
R
RubaXa 已提交
722

723 724 725
				scrollEl =
				scrollParentEl =

R
RubaXa 已提交
726 727 728 729 730 731
				tapEvt =
				touchEvt =

				lastEl =
				lastCSS =

R
RubaXa 已提交
732 733
				activeGroup =
				Sortable.active = null;
R
RubaXa 已提交
734 735 736 737
			}
		},


R
RubaXa 已提交
738 739 740
		handleEvent: function (/**Event*/evt) {
			var type = evt.type;

R
RubaXa 已提交
741
			if (type === 'dragover' || type === 'dragenter') {
R
RubaXa 已提交
742 743 744 745
				if (dragEl) {
					this._onDragOver(evt);
					_globalDragOver(evt);
				}
R
RubaXa 已提交
746
			}
R
RubaXa 已提交
747
			else if (type === 'drop' || type === 'dragend') {
R
RubaXa 已提交
748 749
				this._onDrop(evt);
			}
R
RubaXa 已提交
750 751 752
		},


753 754 755 756 757 758 759 760 761
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
762 763
				n = children.length,
				options = this.options;
764 765 766

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
767
				if (_closest(el, options.draggable, this.el)) {
R
RubaXa 已提交
768
					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
R
RubaXa 已提交
769
				}
770 771 772 773 774 775 776 777 778 779 780
			}

			return order;
		},


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

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

R
RubaXa 已提交
786
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
787 788 789
					items[id] = el;
				}
			}, this);
790 791 792

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
793 794
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
795 796 797 798 799
				}
			});
		},


R
RubaXa 已提交
800 801 802 803 804 805 806 807 808
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


809 810 811 812 813 814 815 816 817 818 819
		/**
		 * 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);
		},


820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836
		/**
		 * 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;
			}
		},


837 838 839 840
		/**
		 * Destroy
		 */
		destroy: function () {
841
			var el = this.el;
R
RubaXa 已提交
842

843
			el[expando] = null;
844

R
RubaXa 已提交
845 846 847
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
848 849
			_off(el, 'dragover', this);
			_off(el, 'dragenter', this);
R
RubaXa 已提交
850

851
			// Remove draggable attributes
R
RubaXa 已提交
852
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
853 854 855
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
856 857 858 859
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

860
			this.el = el = null;
R
RubaXa 已提交
861 862 863
		}
	};

864

R
RubaXa 已提交
865 866 867 868 869 870 871 872 873
	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 已提交
874
	function _bind(ctx, fn) {
R
RubaXa 已提交
875
		var args = slice.call(arguments, 2);
R
RubaXa 已提交
876
		return	fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function () {
R
RubaXa 已提交
877 878 879 880 881
			return fn.apply(ctx, args.concat(slice.call(arguments)));
		};
	}


R
RubaXa 已提交
882
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
883
		if (el) {
R
RubaXa 已提交
884 885 886
			ctx = ctx || document;
			selector = selector.split('.');

R
RubaXa 已提交
887
			var tag = selector.shift().toUpperCase(),
888
				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
R
RubaXa 已提交
889 890

			do {
R
RubaXa 已提交
891
				if (
R
RubaXa 已提交
892
					(tag === '>*' && el.parentNode === ctx) || (
R
raphj 已提交
893
						(tag === '' || el.nodeName.toUpperCase() == tag) &&
R
RubaXa 已提交
894 895
						(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
					)
R
RubaXa 已提交
896 897
				) {
					return el;
R
RubaXa 已提交
898 899
				}
			}
R
RubaXa 已提交
900
			while (el !== ctx && (el = el.parentNode));
R
RubaXa 已提交
901 902
		}

R
RubaXa 已提交
903
		return null;
R
RubaXa 已提交
904 905 906
	}


907
	function _globalDragOver(/**Event*/evt) {
R
RubaXa 已提交
908 909 910 911 912
		evt.dataTransfer.dropEffect = 'move';
		evt.preventDefault();
	}


R
RubaXa 已提交
913
	function _on(el, event, fn) {
R
RubaXa 已提交
914 915 916 917
		el.addEventListener(event, fn, false);
	}


R
RubaXa 已提交
918
	function _off(el, event, fn) {
R
RubaXa 已提交
919 920 921 922
		el.removeEventListener(event, fn, false);
	}


R
RubaXa 已提交
923 924 925
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
926 927 928
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
B
Bogdan Mustiata 已提交
929 930
				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
R
RubaXa 已提交
931 932 933 934 935
			}
		}
	}


R
RubaXa 已提交
936
	function _css(el, prop, val) {
R
RubaXa 已提交
937 938
		var style = el && el.style;

R
RubaXa 已提交
939 940 941
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
942 943
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
944 945
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
946
				}
R
RubaXa 已提交
947 948 949 950 951 952 953 954 955

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

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
956 957 958 959 960
			}
		}
	}


R
RubaXa 已提交
961 962
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
963
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
964

R
RubaXa 已提交
965 966
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
967 968 969
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
970

R
RubaXa 已提交
971
			return list;
R
RubaXa 已提交
972
		}
R
RubaXa 已提交
973 974

		return [];
R
RubaXa 已提交
975 976 977
	}


R
RubaXa 已提交
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993

	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 已提交
994 995
		rootEl.dispatchEvent(evt);

R
RubaXa 已提交
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
		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;

		if (onMoveFn) {
			evt = document.createEvent('Event');
			evt.initEvent('move', true, true);

			evt.to = toEl;
			evt.from = fromEl;
			evt.dragged = dragEl;
			evt.draggedRect = dragRect;
			evt.related = targetEl || toEl;
			evt.relatedRect = targetRect || toEl.getBoundingClientRect();

			retVal = onMoveFn.call(sortable, evt);
		}

R
RubaXa 已提交
1022
		return retVal;
R
RubaXa 已提交
1023 1024 1025
	}


R
RubaXa 已提交
1026
	function _disableDraggable(el) {
R
RubaXa 已提交
1027
		el.draggable = false;
R
RubaXa 已提交
1028 1029 1030
	}


R
RubaXa 已提交
1031
	function _unsilent() {
R
RubaXa 已提交
1032 1033 1034 1035
		_silent = false;
	}


R
RubaXa 已提交
1036
	/** @returns {HTMLElement|false} */
R
RubaXa 已提交
1037
	function _ghostInBottom(el, evt) {
R
RubaXa 已提交
1038 1039 1040
		var lastEl = el.lastElementChild,
			rect = lastEl.getBoundingClientRect();

R
RubaXa 已提交
1041
		return (evt.clientY - (rect.top + rect.height) > 5) && lastEl; // min delta
R
RubaXa 已提交
1042 1043 1044
	}


1045 1046 1047 1048 1049 1050 1051
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
1052
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
1053
			i = str.length,
R
RubaXa 已提交
1054
			sum = 0;
1055

1056 1057 1058
		while (i--) {
			sum += str.charCodeAt(i);
		}
1059

1060 1061 1062
		return sum.toString(36);
	}

1063 1064 1065
	/**
	 * Returns the index of an element within its parent
	 * @param el
1066 1067
	 * @returns {number}
	 * @private
1068 1069 1070
	 */
	function _index(/**HTMLElement*/el) {
		var index = 0;
1071 1072 1073 1074
		while (el && (el = el.previousElementSibling)) {
			if (el.nodeName.toUpperCase() !== 'TEMPLATE') {
				index++;
			}
1075 1076 1077
		}
		return index;
	}
R
RubaXa 已提交
1078

R
RubaXa 已提交
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
	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);
			}
		};
	}

1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111
	function _extend(dst, src) {
		if (dst && src) {
			for (var key in src) {
				if (src.hasOwnProperty(key)) {
					dst[key] = src[key];
				}
			}
		}

		return dst;
	}

R
RubaXa 已提交
1112

R
RubaXa 已提交
1113 1114 1115 1116 1117 1118 1119
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
		bind: _bind,
R
RubaXa 已提交
1120 1121 1122
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
1123
		extend: _extend,
R
RubaXa 已提交
1124
		throttle: _throttle,
R
RubaXa 已提交
1125
		closest: _closest,
1126
		toggleClass: _toggleClass,
1127
		index: _index
R
RubaXa 已提交
1128 1129 1130
	};


1131
	Sortable.version = '1.2.0';
1132

R
RubaXa 已提交
1133

1134 1135 1136 1137 1138 1139
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1140
		return new Sortable(el, options);
1141
	};
R
RubaXa 已提交
1142 1143

	// Export
1144
	return Sortable;
R
RubaXa 已提交
1145
});