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


(function (factory){
	"use strict";

	if( typeof define === "function" && define.amd ){
R
RubaXa 已提交
12
		define(factory);
R
RubaXa 已提交
13
	}
S
Scott Nelson 已提交
14 15 16
	else if( typeof module != "undefined" && typeof module.exports != "undefined" ){
		module.exports = factory();
	}
R
RubaXa 已提交
17 18 19 20 21 22 23
	else {
		window["Sortable"] = factory();
	}
})(function (){
	"use strict";

	var
M
Marius Petcu 已提交
24
		  dragEl
R
RubaXa 已提交
25
		, ghostEl
R
RubaXa 已提交
26
		, cloneEl
R
RubaXa 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
		, rootEl
		, nextEl

		, lastEl
		, lastCSS

		, activeGroup

		, tapEvt
		, touchEvt

		, expando = 'Sortable' + (new Date).getTime()

		, win = window
		, document = win.document
		, parseInt = win.parseInt
R
RubaXa 已提交
43 44
		, supportIEdnd = !!document.createElement('div').dragDrop

R
RubaXa 已提交
45
		, _silent = false
R
RubaXa 已提交
46

R
RubaXa 已提交
47 48 49 50 51 52
		, _createEvent = function (event/**String*/, item/**HTMLElement*/){
			var evt = document.createEvent('Event');
			evt.initEvent(event, true, true);
			evt.item = item;
			return evt;
		}
R
RubaXa 已提交
53

54 55 56 57 58 59
		, _dispatchEvent = function (rootEl, name, targetEl) {
			rootEl.dispatchEvent(_createEvent(name, targetEl || rootEl));
		}

		, _customEvents = 'onAdd onUpdate onRemove onStart onEnd onFilter'.split(' ')

R
RubaXa 已提交
60 61 62 63 64 65 66
		, noop = function (){}
		, slice = [].slice

		, touchDragOverListeners = []
	;


67

R
RubaXa 已提交
68 69 70
	/**
	 * @class  Sortable
	 * @param  {HTMLElement}  el
71
	 * @param  {Object}       [options]
R
RubaXa 已提交
72 73 74 75 76 77
	 */
	function Sortable(el, options){
		this.el = el; // root element
		this.options = options = (options || {});


R
RubaXa 已提交
78
		// Default options
79 80
		var defaults = {
			group: Math.random(),
R
RubaXa 已提交
81
			sort: true,
82 83 84 85 86
			store: null,
			handle: null,
			draggable: el.children[0] && el.children[0].nodeName || (/[uo]l/i.test(el.nodeName) ? 'li' : '*'),
			ghostClass: 'sortable-ghost',
			ignore: 'a, img',
87
			filter: null,
R
RubaXa 已提交
88
			animation: 0
89 90
		};

R
RubaXa 已提交
91

92 93
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
94
			!(name in options) && (options[name] = defaults[name]);
95
		}
R
RubaXa 已提交
96

97

R
RubaXa 已提交
98 99 100 101
		if (!options.group.name) {
			options.group = { name: options.group };
		}

R
RubaXa 已提交
102

R
RubaXa 已提交
103 104 105 106 107 108 109
		['pull', 'put'].forEach(function (key) {
			if (!(key in options.group)) {
				options.group[key] = true;
			}
		});


110
		// Define events
111
		_customEvents.forEach(function (name) {
112
			options[name] = _bind(this, options[name] || noop);
113
			_on(el, name.substr(2).toLowerCase(), options[name]);
R
RubaXa 已提交
114
		}, this);
R
RubaXa 已提交
115 116


R
* JSDoc  
RubaXa 已提交
117
		// Export group name
R
RubaXa 已提交
118
		el[expando] = options.group.name;
R
RubaXa 已提交
119 120


R
* JSDoc  
RubaXa 已提交
121
		// Bind all private methods
R
RubaXa 已提交
122 123 124 125 126 127 128 129 130 131
		for( var fn in this ){
			if( fn.charAt(0) === '_' ){
				this[fn] = _bind(this, this[fn]);
			}
		}


		// Bind events
		_on(el, 'mousedown', this._onTapStart);
		_on(el, 'touchstart', this._onTapStart);
R
RubaXa 已提交
132
		supportIEdnd && _on(el, 'selectstart', this._onTapStart);
R
RubaXa 已提交
133 134 135 136 137

		_on(el, 'dragover', this._onDragOver);
		_on(el, 'dragenter', this._onDragOver);

		touchDragOverListeners.push(this._onDragOver);
138 139 140

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
141 142 143
	}


144
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
145 146 147 148 149 150 151 152
		constructor: Sortable,


		_applyEffects: function (){
			_toggleClass(dragEl, this.options.ghostClass, true);
		},


R
RubaXa 已提交
153
		_onTapStart: function (evt/**Event|TouchEvent*/){
R
RubaXa 已提交
154
			var
M
Marius Petcu 已提交
155
				  touch = evt.touches && evt.touches[0]
R
RubaXa 已提交
156 157
				, target = (touch || evt).target
				, options =  this.options
R
RubaXa 已提交
158
				, el = this.el
159
				, filter = options.filter
R
RubaXa 已提交
160 161
			;

R
RubaXa 已提交
162 163 164 165
			if( evt.type === 'mousedown' && evt.button !== 0 ) {
				return; // only left button
			}

166
			// Check filter
R
RubaXa 已提交
167 168 169 170 171
			if( typeof filter === 'function' ){
				if( filter.call(this, target, this) ){
					_dispatchEvent(el, 'filter', target);
					return; // cancel dnd
				}
172 173 174 175 176 177 178 179 180 181 182 183
			}
			else if( filter ){
				filter = filter.split(',').filter(function (criteria) {
					return _closest(target, criteria.trim(), el);
				});

				if (filter.length) {
					_dispatchEvent(el, 'filter', target);
					return; // cancel dnd
				}
			}

R
RubaXa 已提交
184
			if( options.handle ){
R
RubaXa 已提交
185
				target = _closest(target, options.handle, el);
R
RubaXa 已提交
186 187
			}

R
RubaXa 已提交
188
			target = _closest(target, options.draggable, el);
R
RubaXa 已提交
189

R
RubaXa 已提交
190
			// IE 9 Support
191 192 193 194 195
			if( target && evt.type == 'selectstart' ){
				if( target.tagName != 'A' && target.tagName != 'IMG'){
					target.dragDrop();
				}
			}
N
Nicolas 已提交
196

R
RubaXa 已提交
197
			if( target && !dragEl && (target.parentNode === el) ){
R
RubaXa 已提交
198
				tapEvt = evt;
199 200 201 202 203 204

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

R
RubaXa 已提交
205 206 207 208
				cloneEl = dragEl.cloneNode(true);
				_css(cloneEl, 'display', 'none');
				rootEl.insertBefore(cloneEl, dragEl);

209
				dragEl.draggable = true;
R
RubaXa 已提交
210 211

				// Disable "draggable"
212
				options.ignore.split(',').forEach(function (criteria) {
Z
ziflex 已提交
213 214
					_find(target, criteria.trim(), _disableDraggable);
				});
R
RubaXa 已提交
215 216 217 218

				if( touch ){
					// Touch device support
					tapEvt = {
M
Marius Petcu 已提交
219
						  target:  target
R
RubaXa 已提交
220 221 222
						, clientX: touch.clientX
						, clientY: touch.clientY
					};
223

R
RubaXa 已提交
224 225 226
					this._onDragStart(tapEvt, true);
					evt.preventDefault();
				}
R
RubaXa 已提交
227

228 229 230
				_on(document, 'mouseup', this._onDrop);
				_on(document, 'touchend', this._onDrop);
				_on(document, 'touchcancel', this._onDrop);
R
RubaXa 已提交
231 232

				_on(this.el, 'dragstart', this._onDragStart);
R
RubaXa 已提交
233
				_on(this.el, 'dragend', this._onDrop);
R
RubaXa 已提交
234 235 236 237 238 239 240 241 242 243
				_on(document, 'dragover', _globalDragOver);


				try {
					if( document.selection ){
						document.selection.empty();
					} else {
						window.getSelection().removeAllRanges()
					}
				} catch (err){ }
244 245


246
				_dispatchEvent(dragEl, 'start');
R
RubaXa 已提交
247 248 249 250 251 252 253 254
			}
		},

		_emulateDragOver: function (){
			if( touchEvt ){
				_css(ghostEl, 'display', 'none');

				var
M
Marius Petcu 已提交
255
					  target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY)
R
RubaXa 已提交
256
					, parent = target
R
RubaXa 已提交
257
					, groupName = this.options.group.name
R
RubaXa 已提交
258 259 260
					, i = touchDragOverListeners.length
				;

L
Larry Davis 已提交
261 262
				if( parent ){
					do {
R
RubaXa 已提交
263
						if( parent[expando] === groupName ){
L
Larry Davis 已提交
264 265 266 267 268 269 270 271 272
							while( i-- ){
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}
							break;
R
RubaXa 已提交
273
						}
R
RubaXa 已提交
274

L
Larry Davis 已提交
275 276 277
						target = parent; // store last element
					}
					while( parent = parent.parentNode );
R
RubaXa 已提交
278 279 280 281 282 283 284
				}

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


R
RubaXa 已提交
285
		_onTouchMove: function (evt/**TouchEvent*/){
R
RubaXa 已提交
286 287
			if( tapEvt ){
				var
M
Marius Petcu 已提交
288
					  touch = evt.touches[0]
R
RubaXa 已提交
289 290
					, dx = touch.clientX - tapEvt.clientX
					, dy = touch.clientY - tapEvt.clientY
R
RubaXa 已提交
291
					, translate3d = 'translate3d(' + dx + 'px,' + dy + 'px,0)'
R
RubaXa 已提交
292 293 294
				;

				touchEvt = touch;
R
RubaXa 已提交
295 296 297 298 299 300

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

M
Marius Petcu 已提交
301
				evt.preventDefault();
R
RubaXa 已提交
302 303 304 305
			}
		},


R
* JSDoc  
RubaXa 已提交
306
		_onDragStart: function (evt/**Event*/, isTouch/**Boolean*/){
307
			var dataTransfer = evt.dataTransfer;
R
RubaXa 已提交
308

309
			this._offUpEvents();
R
RubaXa 已提交
310 311

			if( isTouch ){
R
RubaXa 已提交
312
				var
313 314
					  rect = dragEl.getBoundingClientRect()
					, css = _css(dragEl)
R
RubaXa 已提交
315 316
					, ghostRect
				;
R
RubaXa 已提交
317

318
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
319 320 321

				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
322 323
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
324 325 326 327
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');

R
RubaXa 已提交
328 329 330 331 332 333
				rootEl.appendChild(ghostEl);

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
				_css(ghostEl, 'width', rect.width*2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height*2 - ghostRect.height);
R
RubaXa 已提交
334 335 336 337

				// Bind touch events
				_on(document, 'touchmove', this._onTouchMove);
				_on(document, 'touchend', this._onDrop);
M
Marius Petcu 已提交
338
				_on(document, 'touchcancel', this._onDrop);
R
RubaXa 已提交
339

R
RubaXa 已提交
340
				this._loopId = setInterval(this._emulateDragOver, 150);
R
RubaXa 已提交
341 342 343
			}
			else {
				dataTransfer.effectAllowed = 'move';
344
				dataTransfer.setData('Text', dragEl.textContent);
R
RubaXa 已提交
345 346 347 348 349 350 351 352

				_on(document, 'drop', this._onDrop);
			}

			setTimeout(this._applyEffects);
		},


R
* JSDoc  
RubaXa 已提交
353
		_onDragOver: function (evt/**Event*/){
R
RubaXa 已提交
354 355 356 357 358 359
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
360
				groupPut = group.put,
R
RubaXa 已提交
361 362 363
				isOwner = (activeGroup === group);

			if( !_silent &&
R
RubaXa 已提交
364 365
				(activeGroup.name === group.name || groupPut && groupPut.indexOf && groupPut.indexOf(activeGroup.name) > -1) &&
				(isOwner && (options.sort || (revert = !rootEl.contains(dragEl))) || groupPut && activeGroup.pull) &&
R
RubaXa 已提交
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
				(evt.rootEl === void 0 || evt.rootEl === this.el)
			){
				target = _closest(evt.target, this.options.draggable, el);
				dragRect = dragEl.getBoundingClientRect();

				if ((activeGroup.pull == 'clone') && (cloneEl.state !== isOwner)) {
					_css(cloneEl, 'display', isOwner ? 'none' : '');
					!isOwner && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
					cloneEl.state = isOwner;
				}

				if (revert) {
					rootEl.insertBefore(dragEl, cloneEl);
					return;
				}
R
RubaXa 已提交
381

R
RubaXa 已提交
382
				if( el.children.length === 0 || el.children[0] === ghostEl || (el === evt.target) && _ghostInBottom(el, evt) ){
R
RubaXa 已提交
383
					el.appendChild(dragEl);
R
* anim  
RubaXa 已提交
384
					this._animate(dragRect, dragEl);
R
RubaXa 已提交
385
				}
386
				else if( target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0) ){
R
RubaXa 已提交
387 388
					if( lastEl !== target ){
						lastEl = target;
R
RubaXa 已提交
389
						lastCSS = _css(target);
R
RubaXa 已提交
390 391 392
					}


R
* anim  
RubaXa 已提交
393
					var   targetRect = target.getBoundingClientRect()
394 395
						, width = targetRect.right - targetRect.left
						, height = targetRect.bottom - targetRect.top
R
RubaXa 已提交
396 397 398
						, floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
						, isWide = (target.offsetWidth > dragEl.offsetWidth)
						, isLong = (target.offsetHeight > dragEl.offsetHeight)
399
						, halfway = (floating ? (evt.clientX - targetRect.left)/width : (evt.clientY - targetRect.top)/height) > .5
D
Dmitry Minkovsky 已提交
400
						, nextSibling = target.nextElementSibling
R
RubaXa 已提交
401 402
						, after
					;
R
RubaXa 已提交
403

R
RubaXa 已提交
404 405 406 407
					_silent = true;
					setTimeout(_unsilent, 30);

					if( floating ){
R
RubaXa 已提交
408
						after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide
R
RubaXa 已提交
409
					} else {
R
RubaXa 已提交
410
						after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
R
RubaXa 已提交
411 412 413 414
					}

					if( after && !nextSibling ){
						el.appendChild(dragEl);
415
						this._animate(dragRect, dragEl);
R
RubaXa 已提交
416 417
					} else {
						target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
418 419
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
R
RubaXa 已提交
420 421 422 423 424
					}
				}
			}
		},

425 426 427 428 429 430
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

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

R
RubaXa 已提交
431
				_css(target, 'transition', 'none');
432 433 434 435 436 437 438
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
439
				_css(target, 'transition', 'all ' + ms + 'ms');
440 441
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
442 443
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
444 445 446 447 448 449
					_css(target, 'transition', '');
					target.animated = false;
				}, ms);
			}
		},

450 451 452 453 454 455
		_offUpEvents: function () {
			_off(document, 'mouseup', this._onDrop);
			_off(document, 'touchmove', this._onTouchMove);
			_off(document, 'touchend', this._onDrop);
			_off(document, 'touchcancel', this._onDrop);
		},
R
RubaXa 已提交
456 457 458 459 460 461 462 463

		_onDrop: function (evt/**Event*/){
			clearInterval(this._loopId);

			// Unbind events
			_off(document, 'drop', this._onDrop);
			_off(document, 'dragover', _globalDragOver);

R
RubaXa 已提交
464
			_off(this.el, 'dragend', this._onDrop);
R
RubaXa 已提交
465
			_off(this.el, 'dragstart', this._onDragStart);
N
Nicolas 已提交
466
			_off(this.el, 'selectstart', this._onTapStart);
R
RubaXa 已提交
467

468
			this._offUpEvents();
R
RubaXa 已提交
469 470 471

			if( evt ){
				evt.preventDefault();
R
RubaXa 已提交
472
				evt.stopPropagation();
R
RubaXa 已提交
473

R
RubaXa 已提交
474 475
				cloneEl.parentNode.removeChild(cloneEl);
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
476

R
RubaXa 已提交
477
				if( dragEl ){
478
					_disableDraggable(dragEl);
R
RubaXa 已提交
479 480 481 482
					_toggleClass(dragEl, this.options.ghostClass, false);

					if( !rootEl.contains(dragEl) ){
						// Remove event
483
						_dispatchEvent(rootEl, 'remove', dragEl);
R
RubaXa 已提交
484 485

						// Add event
486
						_dispatchEvent(dragEl, 'add');
R
RubaXa 已提交
487 488 489
					}
					else if( dragEl.nextSibling !== nextEl ){
						// Update event
490
						_dispatchEvent(dragEl, 'update');
R
RubaXa 已提交
491
					}
492

493
					_dispatchEvent(dragEl, 'end');
R
RubaXa 已提交
494 495 496 497 498 499 500
				}

				// Set NULL
				rootEl =
				dragEl =
				ghostEl =
				nextEl =
R
RubaXa 已提交
501
				cloneEl =
R
RubaXa 已提交
502 503 504 505 506 507 508 509

				tapEvt =
				touchEvt =

				lastEl =
				lastCSS =

				activeGroup = null;
510 511 512

				// Save sorting
				this.options.store && this.options.store.set(this);
R
RubaXa 已提交
513 514 515 516
			}
		},


517 518 519 520 521 522 523 524 525 526 527 528 529 530
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
				n = children.length
			;

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
531 532 533
				if (_closest(el, this.options.draggable, this.el)) {
					order.push(el.getAttribute('data-id') || _generateId(el));
				}
534 535 536 537 538 539 540 541 542 543 544
			}

			return order;
		},


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

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

R
RubaXa 已提交
550
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
551 552 553
					items[id] = el;
				}
			}, this);
554 555 556 557


			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
558 559
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
560 561 562 563 564
				}
			});
		},


565 566 567 568 569 570 571 572 573 574 575
		/**
		 * 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);
		},


576 577 578 579
		/**
		 * Destroy
		 */
		destroy: function () {
R
RubaXa 已提交
580 581
			var el = this.el, options = this.options;

582 583 584 585
			_customEvents.forEach(function (name) {
				_off(el, name.substr(2).toLowerCase(), options[name]);
			});

R
RubaXa 已提交
586 587
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);
N
Nicolas 已提交
588
			_off(el, 'selectstart', this._onTapStart);
R
RubaXa 已提交
589 590 591 592

			_off(el, 'dragover', this._onDragOver);
			_off(el, 'dragenter', this._onDragOver);

593 594 595 596 597
			//remove draggable attributes
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function(el) {
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
598 599 600 601 602 603 604 605
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

			this.el = null;
		}
	};

606

R
RubaXa 已提交
607 608 609 610 611 612 613 614 615
	function _bind(ctx, fn){
		var args = slice.call(arguments, 2);
		return	fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function (){
			return fn.apply(ctx, args.concat(slice.call(arguments)));
		};
	}


	function _closest(el, selector, ctx){
R
RubaXa 已提交
616 617 618 619
		if( selector === '*' ){
			return el;
		}
		else if( el ){
R
RubaXa 已提交
620 621 622 623
			ctx = ctx || document;
			selector = selector.split('.');

			var
M
Marius Petcu 已提交
624
				  tag = selector.shift().toUpperCase()
R
RubaXa 已提交
625
				, re = new RegExp('\\s('+selector.join('|')+')\\s', 'g')
R
RubaXa 已提交
626 627 628 629
			;

			do {
				if(
M
Marius Petcu 已提交
630
					   (tag === '' || el.nodeName == tag)
R
RubaXa 已提交
631
					&& (!selector.length || ((' '+el.className+' ').match(re) || []).length == selector.length)
R
RubaXa 已提交
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
				){
					return	el;
				}
			}
			while( el !== ctx && (el = el.parentNode) );
		}

		return	null;
	}


	function _globalDragOver(evt){
		evt.dataTransfer.dropEffect = 'move';
		evt.preventDefault();
	}


	function _on(el, event, fn){
		el.addEventListener(event, fn, false);
	}


	function _off(el, event, fn){
		el.removeEventListener(event, fn, false);
	}


	function _toggleClass(el, name, state){
		if( el ){
			if( el.classList ){
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
R
RubaXa 已提交
665
				var className = (' '+el.className+' ').replace(/\s+/g, ' ').replace(' '+name+' ', '');
R
RubaXa 已提交
666 667 668 669 670 671 672
				el.className = className + (state ? ' '+name : '')
			}
		}
	}


	function _css(el, prop, val){
R
RubaXa 已提交
673 674 675
		var style = el && el.style;

		if( style ){
R
RubaXa 已提交
676 677 678 679 680 681 682
			if( val === void 0 ){
				if( document.defaultView && document.defaultView.getComputedStyle ){
					val = document.defaultView.getComputedStyle(el, '');
				}
				else if( el.currentStyle ){
					val	= el.currentStyle;
				}
R
RubaXa 已提交
683 684 685 686 687 688 689 690 691

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

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
			}
		}
	}


	function _find(ctx, tagName, iterator){
		if( ctx ){
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
			if( iterator ){
				for( ; i < n; i++ ){
					iterator(list[i], i);
				}
			}
			return	list;
		}
		return	[];
	}


	function _disableDraggable(el){
		return el.draggable = false;
	}


R
RubaXa 已提交
716 717 718 719 720
	function _unsilent(){
		_silent = false;
	}


R
RubaXa 已提交
721 722 723 724 725 726
	function _ghostInBottom(el, evt){
		var last = el.lastElementChild.getBoundingClientRect();
		return evt.clientY - (last.top + last.height) > 5; // min delta
	}


727 728 729 730 731 732 733
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
734
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
735 736 737
			i = str.length,
			sum = 0
		;
738

739 740 741
		while (i--) {
			sum += str.charCodeAt(i);
		}
742

743 744 745
		return sum.toString(36);
	}

R
RubaXa 已提交
746 747 748 749 750 751 752 753 754

	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
		bind: _bind,
		closest: _closest,
755 756 757
		toggleClass: _toggleClass,
		createEvent: _createEvent,
		dispatchEvent: _dispatchEvent
R
RubaXa 已提交
758 759 760
	};


761
	Sortable.version = '0.6.0';
R
RubaXa 已提交
762

763

764 765 766 767 768 769 770 771
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
		return new Sortable(el, options)
	};
R
RubaXa 已提交
772 773

	// Export
774
	return Sortable;
R
RubaXa 已提交
775
});