drag.js 6.72 KB
define(["jquery"], function () {
    var _void = void 0;

    function drag(o) {
        var dragula = document.createElement("div");
        var $dragula = $(dragula); // 便于使用jquery的事件处理机制

        var _dragging = false;
        var _moveX;
        var _moveY;
        var _item;
        var _over; // 在拖动组件下方的容器
        var _custom; // 自定义的元素
        var _mirror; // 被拖动元素的镜像
        var _grabbed; // 存放mousedown的context

        if(o.moves === _void) { o.moves = always; }
        if(o.custom === _void) { o.custom = never; }
        if(o.mirrorContainer === _void) { o.mirrorContainer = $("body")[0]; }

        o.containers = o.containers.map(function(container) {
            return $(container)[0];
        });

        function always() { return true; }
        function never() { return false; }

        var events = {
            "mousedown": function(e) {
                _moveX = e.clientX;
                _moveY = e.clientY;
    
                var ignore = whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
    
                if(ignore) return;
                
                var item = e.target;
                var context = canStart(item);
    
                if(!context) return;
    
                _grabbed = context;
                _dragging = true;
                nouserselect(true);
            },
            "mousemove": function(e) {
                if(!_grabbed || !_dragging) return;

                var point = {
                    x: e.clientX,
                    y: e.clientY
                };

                if(_moveX == point.x && _moveY == point.y) {
                    _dragging = false;
                }

                var grabbed = start(_grabbed);

                renderMirror(grabbed);
                dragmove(e);

                var overContainer;

                if(o.overContainer) {
                    overContainer = o.overContainer(_item, grabbed.source, e);
                } else {
                    overContainer = isOverContainers(grabbed, e);
                }

                if(overContainer) {
                    $dragula.trigger("over", [_item, overContainer, point]);
                    _over = overContainer;
                } else if(_over) {
                    $dragula.trigger("leave", [_item, _over, point]);
                    _over = null;
                }
            },
            "mouseup": function(e) {
                if(!_dragging) return;
                
                var source = _over;

                removeMirror();
                cleanup();
                nouserselect(false);
                if(!source) return $dragula.trigger("end", [_item, source]);;

                $dragula.trigger("dropping", [_item, source]);

                if(o.animation) {
                    $dragula.trigger("animate", [_item, source, function() {
                        $(source).append(_item);
                        $dragula.trigger("dropped", [_item, source]);
                    }]);
                } else {
                    $(source).append(_item);
                    $dragula.trigger("dropped", [_item, source]);
                }

            }
        };

        var $doc = $(document);
        var namespace = ".drag", evtname;
        
        for(evtname in events) {
            $doc.off(evtname + namespace).on(evtname + namespace, events[evtname]);
        }

        function nouserselect(flag) {
            $("body").toggleClass("no-userselect", flag);
        }

        function start(context) {
            _custom = o.custom();
            _item = context.item;
            _dragging = true;

            return {
                item: _custom || context.item,
                source: context.source
            };
        }

        function cleanup() {
            _dragging = false;
            _grabbed = null;
            _over = null;
        }

        function renderMirror(context) {
            if(_mirror) return;

            _mirror = context.item;
            $(o.mirrorContainer).append(_mirror);
        }

        function removeMirror() {
            if(_custom) {
                $(o.mirrorContainer).find(_mirror).remove();
                _mirror = null;
            }
        }

        function dragmove(e) {
            if(!_mirror) return;

            e.preventDefault();

            var $mirror = $(_mirror);
            var clientX = e.clientX;
            var clientY = e.clientY;
            
            if(_custom) {
               clientX -= $mirror.width() / 2;
               clientY -= $mirror.height() / 2;
            }

            $(_mirror).css({
                left: clientX,
                top: clientY
            });
        }

        function isOverContainers(context, e) {
            var isOver = false, $container, offset;

            o.containers.every(function(container) {
                if(context.source == container) return true;

                $container = $(container);
                offset = $container.offset();
                
                if( offset.left <= e.clientX && 
                    e.clientX <= $container.width() + offset.left &&
                    offset.top <= e.clientY &&
                    e.clientY <= $container.height() + offset.top 
                )  {
                    isOver = true;
                }
                return false;
            });

            return isOver && $container[0];
        }

        function canStart(item) {
            if(_dragging) return;

            if(isContainer(item)) return;

            var movable = o.moves(item);

            if(!movable) return;

            var context = getContext(item);

            if(!context.source) return;

            return {
                item: context.item,
                source: context.source
            };
        }

        function isContainer(item) {
            return ~o.containers.indexOf(item);
        }

        function getContext(item) {
            var $item = $(item), 
                container = $item.parent()[0];

            while(container && !~o.containers.indexOf(container)) {
                $item = $item.parent();
                container = $item.parent()[0];
                container = container.tagName == 'BODY' ? null : container;
            }

            return {
                item: $item[0],
                source: container
            };
        }

        function whichMouseButton(e) {
            if (e.which !== _void && e.which !== 0) { return e.which; }
            if (e.buttons !== _void) { return e.buttons; }
            var button = e.button;
            if (button !== _void) {
                return button & 1 ? 1 : button & 2 ? 3 : (button & 4 ? 2 : 0);
            }
        }

        return $dragula;
    }

    return drag;
});