Sortable.js 23.0 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
		expando = 'Sortable' + (new Date).getTime(),
R
RubaXa 已提交
49

R
RubaXa 已提交
50 51 52
		win = window,
		document = win.document,
		parseInt = win.parseInt,
R
RubaXa 已提交
53 54 55

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

R
RubaXa 已提交
56

R
RubaXa 已提交
57
		_silent = false,
R
RubaXa 已提交
58

59
		_dispatchEvent = function (rootEl, name, targetEl, fromEl, startIndex, newIndex) {
R
RubaXa 已提交
60
			var evt = document.createEvent('Event');
R
RubaXa 已提交
61

62
			evt.initEvent(name, true, true);
R
RubaXa 已提交
63

64 65
			evt.item = targetEl || rootEl;
			evt.from = fromEl || rootEl;
R
RubaXa 已提交
66
			evt.clone = cloneEl;
R
RubaXa 已提交
67 68 69

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

71
			rootEl.dispatchEvent(evt);
R
RubaXa 已提交
72
		},
73

R
RubaXa 已提交
74
		_customEvents = 'onAdd onUpdate onRemove onStart onEnd onFilter onSort'.split(' '),
R
RubaXa 已提交
75

R
RubaXa 已提交
76
		noop = function () {},
R
RubaXa 已提交
77 78

		abs = Math.abs,
R
RubaXa 已提交
79
		slice = [].slice,
R
RubaXa 已提交
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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
		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) {
								win.scrollTo(win.scrollX + vx * speed, win.scrollY + vy * speed);
							} else {
								vy && (el.scrollTop += vy * speed);
								vx && (el.scrollLeft += vx * speed);
							}
						}, 24);
					}
				}
			}
		}, 30)
R
RubaXa 已提交
157 158 159
	;


160

R
RubaXa 已提交
161 162 163
	/**
	 * @class  Sortable
	 * @param  {HTMLElement}  el
164
	 * @param  {Object}       [options]
R
RubaXa 已提交
165
	 */
R
RubaXa 已提交
166
	function Sortable(el, options) {
R
RubaXa 已提交
167 168 169 170
		this.el = el; // root element
		this.options = options = (options || {});


R
RubaXa 已提交
171
		// Default options
172 173
		var defaults = {
			group: Math.random(),
R
RubaXa 已提交
174
			sort: true,
R
RubaXa 已提交
175
			disabled: false,
176 177
			store: null,
			handle: null,
R
RubaXa 已提交
178 179 180
			scroll: true,
			scrollSensitivity: 30,
			scrollSpeed: 10,
R
RubaXa 已提交
181
			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
182 183
			ghostClass: 'sortable-ghost',
			ignore: 'a, img',
184
			filter: null,
R
RubaXa 已提交
185 186 187
			animation: 0,
			setData: function (dataTransfer, dragEl) {
				dataTransfer.setData('Text', dragEl.textContent);
188 189 190
			},
			dropBubble: false,
			dragoverBubble: false
R
RubaXa 已提交
191
		};
192

R
RubaXa 已提交
193

194 195
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
196
			!(name in options) && (options[name] = defaults[name]);
197
		}
R
RubaXa 已提交
198

199

R
RubaXa 已提交
200 201
		var group = options.group;

R
RubaXa 已提交
202 203
		if (!group || typeof group != 'object') {
			group = options.group = { name: group };
R
RubaXa 已提交
204 205
		}

R
RubaXa 已提交
206

R
RubaXa 已提交
207
		['pull', 'put'].forEach(function (key) {
R
RubaXa 已提交
208 209
			if (!(key in group)) {
				group[key] = true;
R
RubaXa 已提交
210 211 212 213
			}
		});


214
		// Define events
215
		_customEvents.forEach(function (name) {
216
			options[name] = _bind(this, options[name] || noop);
217
			_on(el, name.substr(2).toLowerCase(), options[name]);
R
RubaXa 已提交
218
		}, this);
R
RubaXa 已提交
219 220


221 222 223
		// Export options
		options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
		el[expando] = options;
R
RubaXa 已提交
224 225


R
* JSDoc  
RubaXa 已提交
226
		// Bind all private methods
R
RubaXa 已提交
227 228
		for (var fn in this) {
			if (fn.charAt(0) === '_') {
R
RubaXa 已提交
229 230 231 232 233 234 235 236 237
				this[fn] = _bind(this, this[fn]);
			}
		}


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

R
RubaXa 已提交
238 239
		_on(el, 'dragover', this);
		_on(el, 'dragenter', this);
R
RubaXa 已提交
240 241

		touchDragOverListeners.push(this._onDragOver);
242 243 244

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
245 246 247
	}


248
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
249 250 251
		constructor: Sortable,


R
RubaXa 已提交
252
		_dragStarted: function () {
R
RubaXa 已提交
253 254 255
			if (rootEl && dragEl) {
				// Apply effect
				_toggleClass(dragEl, this.options.ghostClass, true);
R
RubaXa 已提交
256

R
RubaXa 已提交
257
				Sortable.active = this;
R
RubaXa 已提交
258

R
RubaXa 已提交
259 260 261
				// Drag start event
				_dispatchEvent(rootEl, 'start', dragEl, rootEl, oldIndex);
			}
R
RubaXa 已提交
262 263 264
		},


R
RubaXa 已提交
265
		_onTapStart: function (/**Event|TouchEvent*/evt) {
R
RubaXa 已提交
266 267
			var type = evt.type,
				touch = evt.touches && evt.touches[0],
R
RubaXa 已提交
268
				target = (touch || evt).target,
269
				originalTarget = target,
R
RubaXa 已提交
270 271 272
				options =  this.options,
				el = this.el,
				filter = options.filter;
R
RubaXa 已提交
273

R
RubaXa 已提交
274
			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
R
RubaXa 已提交
275
				return; // only left button or enabled
R
RubaXa 已提交
276 277
			}

278 279 280 281 282 283 284
			if (options.handle) {
				target = _closest(target, options.handle, el);
			}

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

			// get the index of the dragged element within its parent
R
RubaXa 已提交
285
			oldIndex = _index(target);
286

287
			// Check filter
R
RubaXa 已提交
288
			if (typeof filter === 'function') {
R
RubaXa 已提交
289
				if (filter.call(this, evt, target, this)) {
R
RubaXa 已提交
290
					_dispatchEvent(originalTarget, 'filter', target, el, oldIndex);
R
RubaXa 已提交
291
					evt.preventDefault();
R
RubaXa 已提交
292 293
					return; // cancel dnd
				}
294
			}
R
RubaXa 已提交
295
			else if (filter) {
R
RubaXa 已提交
296 297 298 299
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);

					if (criteria) {
R
RubaXa 已提交
300
						_dispatchEvent(criteria, 'filter', target, el, oldIndex);
R
RubaXa 已提交
301 302
						return true;
					}
303 304
				});

R
RubaXa 已提交
305 306
				if (filter) {
					evt.preventDefault();
307 308 309 310
					return; // cancel dnd
				}
			}

R
RubaXa 已提交
311
			// Prepare `dragstart`
R
RubaXa 已提交
312
			if (target && !dragEl && (target.parentNode === el)) {
R
RubaXa 已提交
313
				tapEvt = evt;
314 315 316 317 318 319 320

				rootEl = this.el;
				dragEl = target;
				nextEl = dragEl.nextSibling;
				activeGroup = this.options.group;

				dragEl.draggable = true;
R
RubaXa 已提交
321 322

				// Disable "draggable"
323
				options.ignore.split(',').forEach(function (criteria) {
Z
ziflex 已提交
324 325
					_find(target, criteria.trim(), _disableDraggable);
				});
R
RubaXa 已提交
326

R
RubaXa 已提交
327
				if (touch) {
R
RubaXa 已提交
328 329
					// Touch device support
					tapEvt = {
R
RubaXa 已提交
330 331 332
						target: target,
						clientX: touch.clientX,
						clientY: touch.clientY
R
RubaXa 已提交
333
					};
334

R
RubaXa 已提交
335
					this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
336 337
					evt.preventDefault();
				}
R
RubaXa 已提交
338

339 340 341
				_on(document, 'mouseup', this._onDrop);
				_on(document, 'touchend', this._onDrop);
				_on(document, 'touchcancel', this._onDrop);
R
RubaXa 已提交
342

R
RubaXa 已提交
343
				_on(dragEl, 'dragend', this);
R
RubaXa 已提交
344 345
				_on(rootEl, 'dragstart', this._onDragStart);

R
RubaXa 已提交
346 347 348
				if (!supportDraggable) {
					this._onDragStart(tapEvt, true);
				}
R
RubaXa 已提交
349 350

				try {
R
RubaXa 已提交
351
					if (document.selection) {
R
RubaXa 已提交
352 353
						document.selection.empty();
					} else {
R
RubaXa 已提交
354
						window.getSelection().removeAllRanges();
R
RubaXa 已提交
355
					}
R
RubaXa 已提交
356 357
				} catch (err) {
				}
R
RubaXa 已提交
358 359 360
			}
		},

R
RubaXa 已提交
361 362
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
363 364
				_css(ghostEl, 'display', 'none');

R
RubaXa 已提交
365
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
R
RubaXa 已提交
366
					parent = target,
367
					groupName = ' ' + this.options.group.name + '',
R
RubaXa 已提交
368
					i = touchDragOverListeners.length;
R
RubaXa 已提交
369

R
RubaXa 已提交
370 371
				if (parent) {
					do {
372
						if (parent[expando] && parent[expando].groups.indexOf(groupName) > -1) {
R
RubaXa 已提交
373 374 375 376 377 378 379 380 381 382 383 384 385
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

							break;
						}

						target = parent; // store last element
L
Larry Davis 已提交
386
					}
R
RubaXa 已提交
387 388
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
389 390 391 392 393 394 395
				}

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


R
RubaXa 已提交
396 397
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
R
RubaXa 已提交
398
				var touch = evt.touches ? evt.touches[0] : evt,
R
RubaXa 已提交
399 400
					dx = touch.clientX - tapEvt.clientX,
					dy = touch.clientY - tapEvt.clientY,
R
RubaXa 已提交
401
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
R
RubaXa 已提交
402 403

				touchEvt = touch;
R
RubaXa 已提交
404 405 406 407 408 409

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

M
Marius Petcu 已提交
410
				evt.preventDefault();
R
RubaXa 已提交
411 412 413 414
			}
		},


R
RubaXa 已提交
415
		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
R
RubaXa 已提交
416 417
			var dataTransfer = evt.dataTransfer,
				options = this.options;
R
RubaXa 已提交
418

419
			this._offUpEvents();
R
RubaXa 已提交
420

R
RubaXa 已提交
421 422 423 424 425 426
			if (activeGroup.pull == 'clone') {
				cloneEl = dragEl.cloneNode(true);
				_css(cloneEl, 'display', 'none');
				rootEl.insertBefore(cloneEl, dragEl);
			}

R
RubaXa 已提交
427
			if (useFallback) {
R
RubaXa 已提交
428 429 430
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
					ghostRect;
R
RubaXa 已提交
431

432
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
433 434 435

				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
436 437
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
438 439 440 441
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');

R
RubaXa 已提交
442 443 444 445
				rootEl.appendChild(ghostEl);

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

R
RubaXa 已提交
449 450 451 452 453 454 455 456 457 458
				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 已提交
459

R
RubaXa 已提交
460
				this._loopId = setInterval(this._emulateDragOver, 150);
R
RubaXa 已提交
461 462
			}
			else {
R
RubaXa 已提交
463 464 465 466
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
					options.setData && options.setData.call(this, dataTransfer, dragEl);
				}
R
RubaXa 已提交
467

R
RubaXa 已提交
468
				_on(document, 'drop', this);
R
RubaXa 已提交
469 470
			}

R
RubaXa 已提交
471
			setTimeout(this._dragStarted, 0);
R
RubaXa 已提交
472 473
		},

R
RubaXa 已提交
474
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
475 476 477 478 479 480
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
481
				groupPut = group.put,
482 483
				isOwner = (activeGroup === group),
				canSort = options.sort;
R
RubaXa 已提交
484

485 486 487 488
			if (evt.dataTransfer && evt.dataTransfer.effectAllowed !== 'move') {
				return;
			}

R
RubaXa 已提交
489 490
			if (evt.preventDefault !== void 0) {
				evt.preventDefault();
491
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
492
			}
R
RubaXa 已提交
493

R
RubaXa 已提交
494
			if (activeGroup && !options.disabled &&
495 496
				(isOwner
					? canSort || (revert = !rootEl.contains(dragEl))
R
RubaXa 已提交
497 498 499 500
					: activeGroup.pull && groupPut && (
						(activeGroup.name === group.name) || // by Name
						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
					)
501
				) &&
R
RubaXa 已提交
502
				(evt.rootEl === void 0 || evt.rootEl === this.el)
R
RubaXa 已提交
503
			) {
R
RubaXa 已提交
504 505 506 507 508 509 510
				// Smart auto-scrolling
				_autoScroll(evt, options, this.el);

				if (_silent) {
					return;
				}

R
RubaXa 已提交
511
				target = _closest(evt.target, options.draggable, el);
R
RubaXa 已提交
512 513 514
				dragRect = dragEl.getBoundingClientRect();


515
				if (revert) {
R
RubaXa 已提交
516 517
					_cloneHide(true);

518 519 520 521 522 523 524
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
525 526
					return;
				}
R
RubaXa 已提交
527

R
RubaXa 已提交
528

R
RubaXa 已提交
529
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
R
RubaXa 已提交
530
					(el === evt.target) && (target = _ghostInBottom(el, evt))
R
RubaXa 已提交
531
				) {
R
RubaXa 已提交
532 533 534 535 536 537
					if (target) {
						if (target.animated) {
							return;
						}
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
538

R
RubaXa 已提交
539 540
					_cloneHide(isOwner);

R
RubaXa 已提交
541
					el.appendChild(dragEl);
R
* anim  
RubaXa 已提交
542
					this._animate(dragRect, dragEl);
R
RubaXa 已提交
543
					target && this._animate(targetRect, target);
R
RubaXa 已提交
544
				}
R
RubaXa 已提交
545 546
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
547
						lastEl = target;
R
RubaXa 已提交
548
						lastCSS = _css(target);
R
RubaXa 已提交
549 550 551
					}


R
RubaXa 已提交
552 553 554 555 556 557 558 559 560
					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,
						after
R
RubaXa 已提交
561
					;
R
RubaXa 已提交
562

R
RubaXa 已提交
563 564 565
					_silent = true;
					setTimeout(_unsilent, 30);

R
RubaXa 已提交
566 567
					_cloneHide(isOwner);

R
RubaXa 已提交
568 569
					if (floating) {
						after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
R
RubaXa 已提交
570
					} else {
R
RubaXa 已提交
571
						after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
R
RubaXa 已提交
572 573
					}

R
RubaXa 已提交
574
					if (after && !nextSibling) {
R
RubaXa 已提交
575 576 577
						el.appendChild(dragEl);
					} else {
						target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
R
RubaXa 已提交
578
					}
R
RubaXa 已提交
579

R
RubaXa 已提交
580 581
					this._animate(dragRect, dragEl);
					this._animate(targetRect, target);
R
RubaXa 已提交
582 583 584 585
				}
			}
		},

586 587 588 589 590 591
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

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

R
RubaXa 已提交
592
				_css(target, 'transition', 'none');
593 594 595 596 597 598 599
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
600
				_css(target, 'transition', 'all ' + ms + 'ms');
601 602
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
603 604
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
605
					_css(target, 'transition', '');
606
					_css(target, 'transform', '');
607 608 609 610 611
					target.animated = false;
				}, ms);
			}
		},

612 613 614 615 616 617
		_offUpEvents: function () {
			_off(document, 'mouseup', this._onDrop);
			_off(document, 'touchmove', this._onTouchMove);
			_off(document, 'touchend', this._onDrop);
			_off(document, 'touchcancel', this._onDrop);
		},
R
RubaXa 已提交
618

R
RubaXa 已提交
619
		_onDrop: function (/**Event*/evt) {
620 621
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
622

R
RubaXa 已提交
623
			clearInterval(this._loopId);
R
RubaXa 已提交
624
			clearInterval(autoScroll.pid);
R
RubaXa 已提交
625 626

			// Unbind events
R
RubaXa 已提交
627
			_off(document, 'drop', this);
R
RubaXa 已提交
628
			_off(document, 'mousemove', this._onTouchMove);
R
RubaXa 已提交
629
			_off(el, 'dragstart', this._onDragStart);
R
RubaXa 已提交
630

631
			this._offUpEvents();
R
RubaXa 已提交
632

R
RubaXa 已提交
633
			if (evt) {
R
RubaXa 已提交
634
				evt.preventDefault();
635
				!options.dropBubble && evt.stopPropagation();
R
RubaXa 已提交
636

R
RubaXa 已提交
637
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
638

R
RubaXa 已提交
639
				if (dragEl) {
R
RubaXa 已提交
640 641
					_off(dragEl, 'dragend', this);

642
					_disableDraggable(dragEl);
R
RubaXa 已提交
643 644
					_toggleClass(dragEl, this.options.ghostClass, false);

645
					if (rootEl !== dragEl.parentNode) {
R
RubaXa 已提交
646 647
						newIndex = _index(dragEl);

648
						// drag from one list and drop into another
R
RubaXa 已提交
649 650
						_dispatchEvent(dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
						_dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
651 652

						// Add event
R
RubaXa 已提交
653
						_dispatchEvent(dragEl, 'add', dragEl, rootEl, oldIndex, newIndex);
654 655

						// Remove event
R
RubaXa 已提交
656
						_dispatchEvent(rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
657
					}
R
RubaXa 已提交
658 659
					else {
						// Remove clone
R
RubaXa 已提交
660
						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
R
RubaXa 已提交
661

R
RubaXa 已提交
662 663 664
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
							newIndex = _index(dragEl);
R
RubaXa 已提交
665

R
RubaXa 已提交
666 667 668 669
							// drag & drop within the same list
							_dispatchEvent(rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
							_dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
						}
R
RubaXa 已提交
670
					}
671

R
RubaXa 已提交
672
					// Drag end event
R
RubaXa 已提交
673
					Sortable.active && _dispatchEvent(rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
674 675
				}

676
				// Nulling
R
RubaXa 已提交
677 678 679 680
				rootEl =
				dragEl =
				ghostEl =
				nextEl =
R
RubaXa 已提交
681
				cloneEl =
R
RubaXa 已提交
682

683 684 685
				scrollEl =
				scrollParentEl =

R
RubaXa 已提交
686 687 688 689 690 691
				tapEvt =
				touchEvt =

				lastEl =
				lastCSS =

R
RubaXa 已提交
692 693
				activeGroup =
				Sortable.active = null;
694 695

				// Save sorting
R
RubaXa 已提交
696
				this.save();
R
RubaXa 已提交
697 698 699 700
			}
		},


R
RubaXa 已提交
701 702 703
		handleEvent: function (/**Event*/evt) {
			var type = evt.type;

R
RubaXa 已提交
704 705
			if (type === 'dragover' || type === 'dragenter') {
				this._onDragOver(evt);
R
RubaXa 已提交
706 707
				_globalDragOver(evt);
			}
R
RubaXa 已提交
708
			else if (type === 'drop' || type === 'dragend') {
R
RubaXa 已提交
709 710
				this._onDrop(evt);
			}
R
RubaXa 已提交
711 712 713
		},


714 715 716 717 718 719 720 721 722
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
723
				n = children.length;
724 725 726

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
727 728 729
				if (_closest(el, this.options.draggable, this.el)) {
					order.push(el.getAttribute('data-id') || _generateId(el));
				}
730 731 732 733 734 735 736 737 738 739 740
			}

			return order;
		},


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

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

R
RubaXa 已提交
746
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
747 748 749
					items[id] = el;
				}
			}, this);
750 751 752

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
753 754
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
755 756 757 758 759
				}
			});
		},


R
RubaXa 已提交
760 761 762 763 764 765 766 767 768
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


769 770 771 772 773 774 775 776 777 778 779
		/**
		 * 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);
		},


780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
		/**
		 * 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;
			}
		},


797 798 799 800
		/**
		 * Destroy
		 */
		destroy: function () {
R
RubaXa 已提交
801 802
			var el = this.el, options = this.options;

803 804 805 806
			_customEvents.forEach(function (name) {
				_off(el, name.substr(2).toLowerCase(), options[name]);
			});

R
RubaXa 已提交
807 808 809
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
810 811
			_off(el, 'dragover', this);
			_off(el, 'dragenter', this);
R
RubaXa 已提交
812

813
			//remove draggable attributes
R
RubaXa 已提交
814
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
815 816 817
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
818 819 820 821 822 823 824 825
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

			this.el = null;
		}
	};

826

R
RubaXa 已提交
827 828 829 830 831 832 833 834 835
	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 已提交
836
	function _bind(ctx, fn) {
R
RubaXa 已提交
837
		var args = slice.call(arguments, 2);
R
RubaXa 已提交
838
		return	fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function () {
R
RubaXa 已提交
839 840 841 842 843
			return fn.apply(ctx, args.concat(slice.call(arguments)));
		};
	}


R
RubaXa 已提交
844
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
845
		if (el) {
R
RubaXa 已提交
846 847 848
			ctx = ctx || document;
			selector = selector.split('.');

R
RubaXa 已提交
849 850
			var tag = selector.shift().toUpperCase(),
				re = new RegExp('\\s(' + selector.join('|') + ')\\s', 'g');
R
RubaXa 已提交
851 852

			do {
R
RubaXa 已提交
853
				if (
R
RubaXa 已提交
854
					(tag === '>*' && el.parentNode === ctx) || (
R
raphj 已提交
855
						(tag === '' || el.nodeName.toUpperCase() == tag) &&
R
RubaXa 已提交
856 857
						(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
					)
R
RubaXa 已提交
858 859
				) {
					return el;
R
RubaXa 已提交
860 861
				}
			}
R
RubaXa 已提交
862
			while (el !== ctx && (el = el.parentNode));
R
RubaXa 已提交
863 864
		}

R
RubaXa 已提交
865
		return null;
R
RubaXa 已提交
866 867 868
	}


869
	function _globalDragOver(/**Event*/evt) {
R
RubaXa 已提交
870 871 872 873 874
		evt.dataTransfer.dropEffect = 'move';
		evt.preventDefault();
	}


R
RubaXa 已提交
875
	function _on(el, event, fn) {
R
RubaXa 已提交
876 877 878 879
		el.addEventListener(event, fn, false);
	}


R
RubaXa 已提交
880
	function _off(el, event, fn) {
R
RubaXa 已提交
881 882 883 884
		el.removeEventListener(event, fn, false);
	}


R
RubaXa 已提交
885 886 887
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
888 889 890
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
R
RubaXa 已提交
891 892
				var className = (' ' + el.className + ' ').replace(/\s+/g, ' ').replace(' ' + name + ' ', '');
				el.className = className + (state ? ' ' + name : '');
R
RubaXa 已提交
893 894 895 896 897
			}
		}
	}


R
RubaXa 已提交
898
	function _css(el, prop, val) {
R
RubaXa 已提交
899 900
		var style = el && el.style;

R
RubaXa 已提交
901 902 903
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
904 905
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
906 907
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
908
				}
R
RubaXa 已提交
909 910 911 912 913 914 915 916 917

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

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
918 919 920 921 922
			}
		}
	}


R
RubaXa 已提交
923 924
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
925
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
926

R
RubaXa 已提交
927 928
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
929 930 931
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
932

R
RubaXa 已提交
933
			return list;
R
RubaXa 已提交
934
		}
R
RubaXa 已提交
935 936

		return [];
R
RubaXa 已提交
937 938 939
	}


R
RubaXa 已提交
940
	function _disableDraggable(el) {
R
RubaXa 已提交
941
		el.draggable = false;
R
RubaXa 已提交
942 943 944
	}


R
RubaXa 已提交
945
	function _unsilent() {
R
RubaXa 已提交
946 947 948 949
		_silent = false;
	}


R
RubaXa 已提交
950
	/** @returns {HTMLElement|false} */
R
RubaXa 已提交
951
	function _ghostInBottom(el, evt) {
R
RubaXa 已提交
952 953
		var lastEl = el.lastElementChild, rect = lastEl.getBoundingClientRect();
		return (evt.clientY - (rect.top + rect.height) > 5) && lastEl; // min delta
R
RubaXa 已提交
954 955 956
	}


957 958 959 960 961 962 963
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
964
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
965
			i = str.length,
R
RubaXa 已提交
966
			sum = 0;
967

968 969 970
		while (i--) {
			sum += str.charCodeAt(i);
		}
971

972 973 974
		return sum.toString(36);
	}

975 976 977
	/**
	 * Returns the index of an element within its parent
	 * @param el
978 979
	 * @returns {number}
	 * @private
980 981 982
	 */
	function _index(/**HTMLElement*/el) {
		var index = 0;
983 984 985 986
		while (el && (el = el.previousElementSibling)) {
			if (el.nodeName.toUpperCase() !== 'TEMPLATE') {
				index++;
			}
987 988 989
		}
		return index;
	}
R
RubaXa 已提交
990

R
RubaXa 已提交
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
	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);
			}
		};
	}


R
RubaXa 已提交
1013 1014 1015 1016 1017 1018 1019
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
		bind: _bind,
R
RubaXa 已提交
1020 1021 1022
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
R
RubaXa 已提交
1023
		throttle: _throttle,
R
RubaXa 已提交
1024
		closest: _closest,
1025
		toggleClass: _toggleClass,
1026 1027
		dispatchEvent: _dispatchEvent,
		index: _index
R
RubaXa 已提交
1028 1029 1030
	};


R
RubaXa 已提交
1031
	Sortable.version = '1.0.1';
1032

R
RubaXa 已提交
1033

1034 1035 1036 1037 1038 1039
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1040
		return new Sortable(el, options);
1041
	};
R
RubaXa 已提交
1042 1043

	// Export
1044
	return Sortable;
R
RubaXa 已提交
1045
});