/** * @ignore * dd support for kissy, dd objects central management module * @author yiminghe@gmail.com */ KISSY.add('dd/ddm', function (S, Node, Base, undefined) { var UA = S.UA, $ = Node.all, logger= S.getLogger('dd/ddm'), win = S.Env.host, doc = win.document, $doc = $(doc), $win = $(win), ie6 = UA['ie'] === 6, // prevent collision with click , only start when move PIXEL_THRESH = 3, // or start when mousedown for 1 second BUFFER_TIME = 1, MOVE_DELAY = 30, SHIM_Z_INDEX = 999999; var Gesture = Node.Gesture, DRAG_MOVE_EVENT = Gesture.move, DRAG_END_EVENT = Gesture.end; /* 负责拖动涉及的全局事件: 1.全局统一的鼠标移动监控 2.全局统一的鼠标弹起监控,用来通知当前拖动对象停止 3.为了跨越 iframe 而统一在底下的遮罩层 */ /** * @class KISSY.DD.DDM * @singleton * @private * @extends KISSY.Base * Manager for Drag and Drop. */ var DDM = Base.extend({ /* 可能要进行拖放的对象,需要通过 buffer/pixelThresh 考验 */ __activeToDrag: 0, /** * @ignore */ _regDrop: function (d) { this.get('drops').push(d); }, /** * @ignore */ _unRegDrop: function (d) { var self = this, drops = self.get('drops'), index = S.indexOf(d, drops); if (index != -1) { drops.splice(index, 1); } }, /** * 注册可能将要拖放的节点 * @param drag * @ignore */ _regToDrag: function (drag) { var self = this; // 事件先要注册好,防止点击,导致 mouseup 时还没注册事件 self.__activeToDrag = drag; registerEvent(self); }, /** * 真正开始 drag * 当前拖动对象通知全局:我要开始啦 * 全局设置当前拖动对象 * @ignore */ _start: function () { var self = this, drops = self.get('drops'), drag = self.__activeToDrag; if (!drag) { return; } self.setInternal('activeDrag', drag); // 预备役清掉 self.__activeToDrag = 0; // 真正开始移动了才激活垫片 if (drag.get('shim')) { activeShim(self); } // avoid unnecessary drop check self.__needDropCheck = 0; if (drag.get('groups')) { _activeDrops(self); if (self.get('validDrops').length) { cacheWH(drag.get('node')); self.__needDropCheck = 1; } } }, /** * @ignore */ _addValidDrop: function (drop) { this.get('validDrops').push(drop); }, /** * 全局通知当前拖动对象:结束拖动了! * @ignore */ _end: function (e) { var self = this, __activeToDrag = self.__activeToDrag, activeDrag = self.get('activeDrag'), activeDrop = self.get('activeDrop'); if (e) { if (__activeToDrag) { __activeToDrag._move(e); } if (activeDrag) { activeDrag._move(e); } } unRegisterEvent(self); // 预备役清掉 , click 情况下 mousedown->mouseup 极快过渡 if (__activeToDrag) { __activeToDrag._end(e); self.__activeToDrag = 0; } if (self._shim) { self._shim.hide(); } if (!activeDrag) { return; } activeDrag._end(e); _deActiveDrops(self); if (activeDrop) { activeDrop._end(e); } self.setInternal('activeDrag', null); self.setInternal('activeDrop', null); } }, { ATTRS: { /** * cursor style when dragging,if shimmed the shim will get the cursor. * Defaults to: 'move'. * @property dragCursor * @type {String} */ /** * @ignore */ dragCursor: { value: 'move' }, /*** * the number of pixels to move to start a drag operation,default is 3. * Defaults to: 3. * @property clickPixelThresh * @type {Number} */ /** * @ignore */ clickPixelThresh: { value: PIXEL_THRESH }, /** * the number of milliseconds to start a drag operation after mousedown,unit second. * Defaults to: 1. * @property bufferTime * @type {Number} */ /** * @ignore */ bufferTime: { value: BUFFER_TIME }, /** * currently active draggable object * @type {KISSY.DD.Draggable} * @readonly * @property activeDrag */ /** * @ignore */ activeDrag: {}, /** * currently active droppable object * @type {KISSY.DD.Droppable} * @readonly * @property activeDrop */ /** * @ignore */ activeDrop: {}, /** * an array of drop targets. * @property drops * @type {KISSY.DD.Droppable[]} * @private */ /** * @ignore */ drops: { value: [] }, /** * a array of the valid drop targets for this interaction * @property validDrops * @type {KISSY.DD.Droppable[]} * @private */ /** * @ignore */ validDrops: { value: [] } } }); /* 全局鼠标移动事件通知当前拖动对象正在移动 注意:chrome8: click 时 mousedown-mousemove-mouseup-click 也会触发 mousemove */ function move(ev) { var self = this, drag, __activeToDrag , activeDrag; if (ev.touches && ev.touches.length > 1) { ddm._end(); return; } // 先处理预备役,效率! if (__activeToDrag = self.__activeToDrag) { __activeToDrag._move(ev); } else if (activeDrag = self.get('activeDrag')) { activeDrag._move(ev); // for drop-free draggable performance if (self.__needDropCheck) { notifyDropsMove(self, ev, activeDrag); } } drag = __activeToDrag || activeDrag; // 防止 ie 选择到字 // touch need direction if (drag && drag.get('preventDefaultOnMove')) { ev.preventDefault(); } } // 同一时刻只可能有个 drag 元素,只能有一次 move 被注册,不需要每个实例一个 throttle // 一个应用一个 document 只需要注册一个 move // 2013-01-24 更灵敏 for scroller in webkit var throttleMove = UA.ie ? S.throttle(move, MOVE_DELAY) : move; function notifyDropsMove(self, ev, activeDrag) { var drops = self.get('validDrops'), mode = activeDrag.get('mode'), activeDrop = 0, oldDrop, vArea = 0, dragRegion = region(activeDrag.get('node')), dragArea = area(dragRegion); S.each(drops, function (drop) { if (drop.get('disabled')) { return undefined; } var a, node = drop['getNodeFromTarget'](ev, // node activeDrag.get('dragNode')[0], // proxy node activeDrag.get('node')[0]); if (!node // 当前 drop 区域已经包含 activeDrag.get('node') // 不要返回,可能想调整位置 ) { return undefined; } if (mode == 'point') { //取鼠标所在的 drop 区域 if (inNodeByPointer(node, activeDrag.mousePos)) { a = area(region(node)); if (!activeDrop) { activeDrop = drop; vArea = a; } else { // 当前得到的可放置元素范围更小,取范围小的那个 if (a < vArea) { activeDrop = drop; vArea = a; } } } } else if (mode == 'intersect') { //取一个和activeDrag交集最大的drop区域 a = area(intersect(dragRegion, region(node))); if (a > vArea) { vArea = a; activeDrop = drop; } } else if (mode == 'strict') { //drag 全部在 drop 里面 a = area(intersect(dragRegion, region(node))); if (a == dragArea) { activeDrop = drop; return false; } } return undefined; }); oldDrop = self.get('activeDrop'); if (oldDrop && oldDrop != activeDrop) { oldDrop._handleOut(ev); activeDrag._handleOut(ev); } self.setInternal('activeDrop', activeDrop); if (activeDrop) { if (oldDrop != activeDrop) { activeDrop._handleEnter(ev); } else { // 注意处理代理时内部节点变化导致的 out、enter activeDrop._handleOver(ev); } } } /* 垫片只需创建一次 */ function activeShim(self) { //创造垫片,防止进入iframe,外面document监听不到 mousedown/up/move self._shim = $('<div ' + 'style="' + //red for debug 'background-color:red;' + 'position:' + (ie6 ? 'absolute' : 'fixed') + ';' + 'left:0;' + 'width:100%;' + 'height:100%;' + 'top:0;' + 'cursor:' + ddm.get('dragCursor') + ';' + 'z-index:' + //覆盖iframe上面即可 SHIM_Z_INDEX + ';' + '"><' + '/div>') .prependTo(doc.body || doc.documentElement) //0.5 for debug .css('opacity', 0); activeShim = showShim; if (ie6) { // ie6 不支持 fixed 以及 width/height 100% // support dd-scroll // prevent empty when scroll outside initial window $win.on('resize scroll', adjustShimSize, self); } showShim(self); } var adjustShimSize = S.throttle(function () { var self = this, activeDrag; if ((activeDrag = self.get('activeDrag')) && activeDrag.get('shim')) { self._shim.css({ width: $doc.width(), height: $doc.height() }); } }, MOVE_DELAY); function showShim(self) { // determine cursor according to activeHandler and dragCursor var ah = self.get('activeDrag').get('activeHandler'), cur = 'auto'; if (ah) { cur = ah.css('cursor'); } if (cur == 'auto') { cur = self.get('dragCursor'); } self._shim.css({ cursor: cur, display: 'block' }); if (ie6) { adjustShimSize.call(self); } } /* 开始时注册全局监听事件 */ function registerEvent(self) { $doc.on(DRAG_END_EVENT, self._end, self); $doc.on(DRAG_MOVE_EVENT, throttleMove, self); // http://stackoverflow.com/questions/1685326/responding-to-the-onmousemove-event-outside-of-the-browser-window-in-ie // ie6 will not response to event when cursor is out of window. if (doc.body.setCapture) { doc.body.setCapture(); } } /* 结束时需要取消掉,防止平时无谓的监听 */ function unRegisterEvent(self) { $doc.detach(DRAG_MOVE_EVENT, throttleMove, self); $doc.detach(DRAG_END_EVENT, self._end, self); if (doc.body.releaseCapture) { doc.body.releaseCapture(); } } function _activeDrops(self) { var drops = self.get('drops'); self.setInternal('validDrops', []); if (drops.length) { S.each(drops, function (d) { d._active(); }); } } function _deActiveDrops(self) { var drops = self.get('drops'); self.setInternal('validDrops', []); if (drops.length) { S.each(drops, function (d) { d._deActive(); }); } } function region(node) { var offset = node.offset(); if (!node.__dd_cached_width) { logger.debug('no cache in dd!'); logger.debug(node[0]); } return { left: offset.left, right: offset.left + (node.__dd_cached_width || node.outerWidth()), top: offset.top, bottom: offset.top + (node.__dd_cached_height || node.outerHeight()) }; } function inRegion(region, pointer) { return region.left <= pointer.left && region.right >= pointer.left && region.top <= pointer.top && region.bottom >= pointer.top; } function area(region) { if (region.top >= region.bottom || region.left >= region.right) { return 0; } return (region.right - region.left) * (region.bottom - region.top); } function intersect(r1, r2) { var t = Math.max(r1['top'], r2.top), r = Math.min(r1.right, r2.right), b = Math.min(r1['bottom'], r2.bottom), l = Math.max(r1.left, r2.left); return { left: l, right: r, top: t, bottom: b }; } function inNodeByPointer(node, point) { return inRegion(region(node), point); } function cacheWH(node) { if (node) { node.__dd_cached_width = node.outerWidth(); node.__dd_cached_height = node.outerHeight(); } } var ddm = new DDM(); ddm.inRegion = inRegion; ddm.region = region; ddm.area = area; ddm.cacheWH = cacheWH; ddm.PREFIX_CLS = 'ks-dd-'; return ddm; }, { requires: ['node', 'base'] });