/** * @ignore * dd support for kissy, drag for dd * @author yiminghe@gmail.com */ KISSY.add('dd/base/draggable', function (S, Node, RichBase, DDM, Event) { var UA = S.UA, each = S.each, DRAG_START_EVENT = Event.Gesture.start, ie = UA['ie'], Features = S.Features, NULL = null, PREFIX_CLS = DDM.PREFIX_CLS, doc = S.Env.host.document; /** * @class KISSY.DD.Draggable * @extends KISSY.RichBase * Provide abilities to make specified node draggable */ var Draggable = RichBase.extend({ initializer: function () { var self = this; self.addTarget(DDM); /** * fired when need to compute draggable 's position during dragging * @event dragalign * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired when need to get draggable 's position during dragging * @event dragalign * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired during dragging * @event drag * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.left node 's supposed position left * @param e.top node 's supposed position top * @param e.pageX mouse position left * @param e.pageY mouse position top */ /** * fired during dragging * @event drag * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.left node 's supposed position left * @param e.top node 's supposed position top * @param e.pageX mouse position left * @param e.pageY mouse position top */ /** * fired after drop a draggable onto a droppable object * @event dragdrophit * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after drop a draggable onto a droppable object * @event dragdrophit * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after drag * @event dragend * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired after drag * @event dragend * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired after drop a draggable onto nothing * @event dragdropmiss * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired after drop a draggable onto nothing * @event dragdropmiss * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired after a draggable leaves a droppable * @event dragexit * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after a draggable leaves a droppable * @event dragexit * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after a draggable object mouseenter a droppable object * @event dragenter * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after a draggable object mouseenter a droppable object * @event dragenter * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after a draggable object mouseover a droppable object * @event dragover * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after a draggable object mouseover a droppable object * @event dragover * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object * @param e.drop current droppable object */ /** * fired after a draggable object start to drag * @event dragstart * @member KISSY.DD.DDM * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ /** * fired after a draggable object start to drag * @event dragstart * @member KISSY.DD.Draggable * @param {KISSY.Event.CustomEventObject} e * @param e.drag current draggable object */ }, '_onSetNode': function (n) { var self=this; // dragNode is equal to node in single mode self.setInternal('dragNode', n); self.bindDragEvent(); }, bindDragEvent: function () { var self = this, node = self.get('node'); node.on(DRAG_START_EVENT, handlePreDragStart, self) .on('dragstart', self._fixDragStart); }, detachDragEvent: function () { var self = this, node = self.get('node'); node.detach(DRAG_START_EVENT, handlePreDragStart, self) .detach('dragstart', self._fixDragStart); }, /** * mousedown 1秒后自动开始拖的定时器 * @ignore */ _bufferTimer: NULL, _onSetDisabledChange: function (d) { this.get('dragNode')[d ? 'addClass' : 'removeClass'](PREFIX_CLS + '-disabled'); }, _fixDragStart: fixDragStart, _checkHandler: function (t) { var self = this, handlers = self.get('handlers'), ret = 0; each(handlers, function (handler) { //子区域内点击也可以启动 if (handler.contains(t) || handler[0] == t) { ret = 1; self.setInternal('activeHandler', handler); return false; } }); return ret; }, _checkDragStartValid: function (ev) { var self = this; if (self.get('primaryButtonOnly') && ev.which != 1 || self.get('disabled')) { return 0; } return 1; }, _prepare: function (ev) { if (!ev) { return; } var self = this; if (ie) { fixIEMouseDown(); } // http://blogs.msdn.com/b/ie/archive/2011/10/19/handling-multi-touch-and-mouse-input-in-all-browsers.aspx // stop panning and zooming so we can draw for win8? // if (ev.originalEvent['preventManipulation']) { // ev.originalEvent.preventManipulation(); // } // 防止 firefox/chrome 选中 text // 非 ie,阻止了 html dd 的默认行为 if (self.get('halt')) { ev.stopPropagation(); } // in touch device // prevent touchdown // will prevent text selection and link click if (!Features.isTouchSupported()) { ev.preventDefault(); } var node = self.get('node'), mx = ev.pageX, my = ev.pageY, nxy = node.offset(); self.setInternal('startMousePos', self.mousePos = { left: mx, top: my }); self.setInternal('startNodePos', nxy); self.setInternal('deltaPos', { left: mx - nxy.left, top: my - nxy.top }); DDM._regToDrag(self); var bufferTime = self.get('bufferTime'); // 是否中央管理,强制限制拖放延迟 if (bufferTime) { self._bufferTimer = setTimeout(function () { // 事件到了,仍然是 mousedown 触发! //S.log('drag start by timeout'); self._start(); }, bufferTime * 1000); } }, _clearBufferTimer: function () { var self = this; if (self._bufferTimer) { clearTimeout(self._bufferTimer); self._bufferTimer = 0; } }, _move: function (ev) { var self = this, ret, pageX = ev.pageX, pageY = ev.pageY; if (!self.get('dragging')) { var startMousePos = self.get('startMousePos'), clickPixelThresh = self.get('clickPixelThresh'); // 鼠标经过了一定距离,立即开始 if (Math.abs(pageX - startMousePos.left) >= clickPixelThresh || Math.abs(pageY - startMousePos.top) >= clickPixelThresh) { //S.log('start drag by pixel : ' + l1 + ' : ' + l2); self._start(); } // 开始后,下轮 move 开始触发 drag 事件 return; } var diff = self.get('deltaPos'), left = pageX - diff.left, top = pageY - diff.top; self.mousePos = { left: pageX, top: pageY }; ret = { left: left, top: top, pageX: pageX, pageY: pageY, drag: self }; self.setInternal('actualPos', { left: left, top: top }); self.fire('dragalign', ret); var def = 1; if (self.fire('drag', ret) === false) { def = 0; } if (def && self.get('move')) { // 取 'node' , 改 node 可能是代理哦 self.get('node').offset(self.get('actualPos')); } }, /** * force to stop this drag operation * @member KISSY.DD.Draggable */ stopDrag: function () { DDM._end(); }, _end: function () { var self = this, activeDrop; // 否则清除定时器即可 self._clearBufferTimer(); if (ie) { fixIEMouseUp(); } // 如果已经开始,收尾工作 if (self.get('dragging')) { self.get('node') .removeClass(PREFIX_CLS + 'drag-over'); if (activeDrop = DDM.get('activeDrop')) { self.fire('dragdrophit', { drag: self, drop: activeDrop }); } else { self.fire('dragdropmiss', { drag: self }); } self.setInternal('dragging', 0); self.fire('dragend', { drag: self }); } }, _handleOut: function () { var self = this; self.get('node').removeClass(PREFIX_CLS + 'drag-over'); // html5 => dragleave self.fire('dragexit', { drag: self, drop: DDM.get('activeDrop') }); }, _handleEnter: function (e) { var self = this; self.get('node').addClass(PREFIX_CLS + 'drag-over'); //第一次先触发 dropenter, dragenter self.fire('dragenter', e); }, _handleOver: function (e) { this.fire('dragover', e); }, _start: function () { var self = this; self._clearBufferTimer(); self.setInternal('dragging', 1); DDM._start(); self.fire('dragstart', { drag: self }); }, /** * make the drag node undraggable * @member KISSY.DD.Draggable * @private */ destructor: function () { var self = this; self.detachDragEvent(); self.detach(); } }, { ATTRS: { /** * the dragged node. maybe a proxy node. * @property node * @type {HTMLElement|KISSY.NodeList} * @readonly */ /** * the dragged node. * @cfg {HTMLElement|KISSY.NodeList} node */ /** * @ignore */ node: { setter: function (v) { if (!(v instanceof Node)) { return Node.one(v); } } }, /** * the number of pixels to move to start a drag operation * * Defaults to: {@link KISSY.DD.DDM#clickPixelThresh}. * * @cfg {Number} clickPixelThresh */ /** * @ignore */ clickPixelThresh: { valueFn: function () { return DDM.get('clickPixelThresh'); } }, /** * the number of milliseconds to start a drag operation after mousedown. * * Defaults to: {@link KISSY.DD.DDM#bufferTime}. * * @cfg {Number} bufferTime */ /** * @ignore */ bufferTime: { valueFn: function () { return DDM.get('bufferTime'); } }, /** * the draggable element. * @property dragNode * @type {HTMLElement} * @readonly */ /** * @ignore */ dragNode: {}, /** * use protective shim to cross iframe. * * Defaults to: true * * @cfg {Boolean} shim * */ /** * @ignore */ shim: { value: true }, /** * valid handlers to initiate a drag operation. * * Default same with {@link KISSY.DD.Draggable#cfg-node} config. * * @cfg {HTMLElement[]|Function[]|String[]} handlers */ /** * @ignore */ handlers: { value: [], getter: function (vs) { var self = this; if (!vs.length) { vs[0] = self.get('node'); } each(vs, function (v, i) { if (S.isFunction(v)) { v = v.call(self); } // search inside node if (typeof v == 'string') { v = self.get('node').one(v); } if (v.nodeType) { v = Node.one(v); } vs[i] = v; }); self.setInternal('handlers', vs); return vs; } }, /** * the handler which fired the drag event. * @type {KISSY.NodeList} * @property activeHandler * @readonly */ /** * @ignore */ activeHandler: {}, /** * indicate whether this draggable object is being dragged * @type {Boolean} * @property dragging * @readonly */ /** * @ignore */ dragging: { value: false, setter: function (d) { var self = this; self.get('dragNode')[d ? 'addClass' : 'removeClass'] (PREFIX_CLS + 'dragging'); } }, /** * drop mode. * @cfg {KISSY.DD.Draggable.DropMode} mode */ /** * @ignore */ mode: { value: 'point' }, /** * set to disable this draggable so that it can not be dragged. * * Defaults to: false * * @type {Boolean} * @property disabled */ /** * @ignore */ disabled: { value: false }, /** * whether the drag node moves with cursor, can be used to resize element. * * Defaults to: false * * @cfg {Boolean} move */ /** * @ignore */ move: { value: false }, /** * whether a drag operation can only be trigged by primary(left) mouse button. * Setting false will allow for all mousedown events to trigger drag. * @cfg {Boolean} primaryButtonOnly */ /** * @ignore */ primaryButtonOnly: { value: true }, /** * whether halt mousedown event. * * Defaults to: true * * @cfg {Boolean} halt */ /** * @ignore */ halt: { value: true }, /** * groups this draggable object belongs to * for example: * @example * { * 'group1':1, * 'group2':1 * } * * @cfg {Object} groups */ /** * @ignore */ groups: { value: {} }, /** * mouse position at drag start * for example: * @example * { * left: 100, * top: 200 * } * * @property startMousePos * @type {Object} * @readonly */ /** * @ignore */ startMousePos: { }, /** * node position ar drag start * for example: * @example * { * left: 100, * top: 200 * } * * @property startNodePos * @type {Object} * @readonly */ /** * @ignore */ startNodePos: { }, /** * The offset of the mouse position to the element's position * @property deltaPos * @type {Object} * @readonly */ /** * @ignore */ deltaPos: { }, /** * The xy that the node will be set to. Changing this will alter the position as it's dragged. * @property actualPos * @type {Object} * @readonly */ /** * @ignore */ actualPos: { } } }); /** * drag drop mode enum. * @enum {String} KISSY.DD.Draggable.DropMode */ Draggable.DropMode = { /** * In point mode, a Drop is targeted by the cursor being over the Target */ 'POINT': 'point', /** * In intersect mode, a Drop is targeted by 'part' of the drag node being over the Target */ INTERSECT: 'intersect', /** * In strict mode, a Drop is targeted by the 'entire' drag node being over the Target */ STRICT: 'strict' }; var _ieSelectBack; function fixIEMouseUp() { doc.body.onselectstart = _ieSelectBack; } // prevent select text in ie function fixIEMouseDown() { _ieSelectBack = doc.body.onselectstart; doc.body.onselectstart = fixIESelect; } /* 1. keeps IE from blowing up on images as drag handlers. IE 在 img 上拖动时默认不能拖动(不触发 mousemove,mouseup 事件,mouseup 后接着触发 mousemove ...) 2. 防止 html5 draggable 元素的拖放默认行为 (选中文字拖放) 3. 防止默认的选择文本行为(??场景?) */ function fixDragStart(e) { e.preventDefault(); } /* keeps IE from selecting text */ function fixIESelect() { return false; } /* 鼠标按下时,查看触发源是否是属于 handler 集合, 保存当前状态 通知全局管理器开始作用 */ var handlePreDragStart = function (ev) { ev = DDM._normalEvent(ev); if (!ev) { return; } var self = this, t = ev.target; if (self._checkDragStartValid(ev)) { if (!self._checkHandler(t)) { return; } self._prepare(ev); } }; return Draggable; }, { requires: ['node', 'rich-base', './ddm', 'event'] });