zui
Version:
一个基于 Bootstrap 深度定制开源前端实践方案,帮助你快速构建现代跨屏应用。
336 lines (289 loc) • 11.6 kB
JavaScript
/* ========================================================================
* ZUI: droppable.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014-2016 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($, document, Math) {
'use strict';
var NAME = 'zui.droppable',
DEFAULTS = {
// container: '',
// selector: '',
// handle: '',
// flex: false,
// nested: false,
target: '.droppable-target',
deviation: 5,
sensorOffsetX: 0,
sensorOffsetY: 0,
dropToClass: 'drop-to',
// mouseButton: -1 // 0, 1, 2, -1, all, left, right, middle
};
var idIncrementer = 0;
var Droppable = function(element, options) {
var that = this;
that.id = idIncrementer++;
that.$ = $(element);
that.options = $.extend({}, DEFAULTS, that.$.data(), options);
that.init();
};
Droppable.DEFAULTS = DEFAULTS;
Droppable.NAME = NAME;
Droppable.prototype.trigger = function(name, params) {
return $.zui.callEvent(this.options[name], params, this);
};
Droppable.prototype.init = function() {
var that = this,
$root = that.$,
setting = that.options,
deviation = setting.deviation,
eventSuffix = '.' + NAME + '.' + that.id,
mouseDownEvent = 'mousedown' + eventSuffix,
mouseUpEvent = 'mouseup' + eventSuffix,
mouseMoveEvent = 'mousemove' + eventSuffix,
selector = setting.selector,
handle = setting.handle,
flex = setting.flex,
container = setting.container,
canMoveHere = setting.canMoveHere,
dropToClass = setting.dropToClass,
$ele = $root,
isMouseDown = false,
$container = container ? $(setting.container).first() : (selector ? $root : $('body')),
$targets,
$target,
$shadow,
isIn,
isSelf,
oldCssPosition,
startOffset,
startMouseOffset,
containerOffset,
clickOffset,
mouseOffset,
lastMouseOffset,
mouseDownBackEventCall;
var mouseMove = function(event) {
if(!isMouseDown) return;
mouseOffset = {left: event.pageX, top: event.pageY};
// ignore small move
if(Math.abs(mouseOffset.left - startMouseOffset.left) < deviation && Math.abs(mouseOffset.top - startMouseOffset.top) < deviation) return;
if($shadow === null) // create shadow
{
var cssPosition = $container.css('position');
if(cssPosition != 'absolute' && cssPosition != 'relative' && cssPosition != 'fixed') {
oldCssPosition = cssPosition;
$container.css('position', 'relative');
}
$shadow = $ele.clone().removeClass('drag-from').addClass('drag-shadow').css({
position: 'absolute',
width: $ele.outerWidth(),
transition: 'none'
}).appendTo($container);
$ele.addClass('dragging');
that.trigger('start', {
event: event,
element: $ele,
targets: $targets
});
}
var offset = {
left: mouseOffset.left - clickOffset.left,
top: mouseOffset.top - clickOffset.top
};
var position = {
left: offset.left - containerOffset.left,
top: offset.top - containerOffset.top
};
$shadow.css(position);
$.extend(lastMouseOffset, mouseOffset);
var isNew = false;
isIn = false;
if(!flex) {
$targets.removeClass(dropToClass);
}
var $newTarget = null;
$targets.each(function() {
var t = $(this),
tPos = t.offset(),
tW = t.outerWidth(),
tH = t.outerHeight(),
tX = tPos.left + setting.sensorOffsetX,
tY = tPos.top + setting.sensorOffsetY;
if(mouseOffset.left > tX && mouseOffset.top > tY && mouseOffset.left < (tX + tW) && mouseOffset.top < (tY + tH)) {
if($newTarget) $newTarget.removeClass(dropToClass);
$newTarget = t;
if(!setting.nested) return false;
}
});
if($newTarget) {
isIn = true;
var id = $newTarget.data('id');
if($ele.data('id') != id) isSelf = false;
if($target === null || ($target.data('id') !== id && (!isSelf))) isNew = true;
$target = $newTarget;
if(flex) {
$targets.removeClass(dropToClass);
}
$target.addClass(dropToClass);
}
if(!flex) {
$ele.toggleClass('drop-in', isIn);
$shadow.toggleClass('drop-in', isIn);
} else if($target !== null && $target.length) {
isIn = true;
}
if(!canMoveHere || canMoveHere($ele, $target) !== false) {
that.trigger('drag', {
event: event,
isIn: isIn,
target: $target,
element: $ele,
isNew: isNew,
selfTarget: isSelf,
clickOffset: clickOffset,
offset: offset,
position: {
left: offset.left - containerOffset.left,
top: offset.top - containerOffset.top
},
mouseOffset: mouseOffset
});
}
event.preventDefault();
};
var mouseUp = function(event) {
$(document).off(eventSuffix);
clearTimeout(mouseDownBackEventCall);
if(!isMouseDown) return;
isMouseDown = false;
if(oldCssPosition) {
$container.css('position', oldCssPosition);
}
if($shadow === null) {
$ele.removeClass('drag-from');
that.trigger('always', {
event: event,
cancel: true
});
return;
}
if(!isIn) $target = null;
var isSure = true;
mouseOffset = event ? {
left: event.pageX,
top: event.pageY
} : lastMouseOffset;
var offset = {
left: mouseOffset.left - clickOffset.left,
top: mouseOffset.top - clickOffset.top
};
var moveOffset = {
left: mouseOffset.left - lastMouseOffset.left,
top: mouseOffset.top - lastMouseOffset.top
};
lastMouseOffset.left = mouseOffset.left;
lastMouseOffset.top = mouseOffset.top;
var eventOptions = {
event: event,
isIn: isIn,
target: $target,
element: $ele,
isNew: (!isSelf) && $target !== null,
selfTarget: isSelf,
offset: offset,
mouseOffset: mouseOffset,
position: {
left: offset.left - containerOffset.left,
top: offset.top - containerOffset.top
},
lastMouseOffset: lastMouseOffset,
moveOffset: moveOffset
};
isSure = that.trigger('beforeDrop', eventOptions);
if(isSure && isIn) {
that.trigger('drop', eventOptions);
}
$targets.removeClass(dropToClass);
$ele.removeClass('dragging').removeClass('drag-from');
$shadow.remove();
$shadow = null;
that.trigger('finish', eventOptions);
that.trigger('always', eventOptions);
if(event) event.preventDefault();
};
var mouseDown = function(event) {
var mouseButton = $.zui.getMouseButtonCode(setting.mouseButton);
if(mouseButton > -1 && event.button !== mouseButton) {
return;
}
var $mouseDownEle = $(this);
if(selector) {
$ele = handle ? $mouseDownEle.closest(selector) : $mouseDownEle;
}
if($ele.hasClass('drag-shadow')) {
return;
}
if(setting['before']) {
if(setting['before']({
event: event,
element: $ele
}) === false) return;
}
isMouseDown = true;
$targets = $.isFunction(setting.target) ? setting.target($ele, $root) : $container.find(setting.target),
$target = null,
$shadow = null,
isIn = false,
isSelf = true,
oldCssPosition = null,
startOffset = $ele.offset(),
containerOffset = $container.offset();
containerOffset.top = containerOffset.top - $container.scrollTop();
containerOffset.left = containerOffset.left - $container.scrollLeft();
startMouseOffset = {left: event.pageX, top: event.pageY};
lastMouseOffset = $.extend({}, startMouseOffset);
clickOffset = {
left: startMouseOffset.left - startOffset.left,
top: startMouseOffset.top - startOffset.top
};
$ele.addClass('drag-from');
$(document).on(mouseMoveEvent, mouseMove).on(mouseUpEvent, mouseUp);
mouseDownBackEventCall = setTimeout(function() {
$(document).on(mouseDownEvent, mouseUp);
}, 10);
event.preventDefault();
if(setting.stopPropagation) {
event.stopPropagation();
}
};
if(handle) {
$root.on(mouseDownEvent, handle, mouseDown);
} else if(selector) {
$root.on(mouseDownEvent, selector, mouseDown);
} else {
$root.on(mouseDownEvent, mouseDown);
}
};
Droppable.prototype.destroy = function() {
var eventSuffix = '.' + NAME + '.' + this.id;
this.$.off(eventSuffix);
$(document).off(eventSuffix);
this.$.data(NAME, null);
};
Droppable.prototype.reset = function() {
this.destroy();
this.init();
};
$.fn.droppable = function(option) {
return this.each(function() {
var $this = $(this);
var data = $this.data(NAME);
var options = typeof option == 'object' && option;
if(!data) $this.data(NAME, (data = new Droppable(this, options)));
if(typeof option == 'string') data[option]();
});
};
$.fn.droppable.Constructor = Droppable;
}(jQuery, document, Math));