1 /**
  2  * @fileOverview dd support for kissy , dd objects central management module
  3  * @author yiminghe@gmail.com
  4  */
  5 KISSY.add('dd/ddm', function (S, UA, DOM, Event, Node, Base) {
  6 
  7     var win = S.Env.host,
  8         doc = win.document,
  9         ie6 = UA['ie'] === 6,
 10 
 11     // prevent collision with click , only start when move
 12         PIXEL_THRESH = 3,
 13     // or start when mousedown for 1 second
 14         BUFFER_TIME = 1000,
 15 
 16         MOVE_DELAY = 30,
 17         _showShimMove = S.throttle(move,
 18             MOVE_DELAY),
 19         SHIM_ZINDEX = 999999;
 20 
 21     /**
 22      * @memberOf DD
 23      * @field
 24      * @namespace Manager for Drag and Drop.
 25      */
 26     function DDM() {
 27         var self = this;
 28         DDM.superclass.constructor.apply(self, arguments);
 29     }
 30 
 31     DDM.ATTRS =
 32     /**
 33      * @lends DD.DDM
 34      */
 35     {
 36         /**
 37          * cursor style when dragging,if shimmed the shim will get the cursor.
 38          * @type String
 39          */
 40         dragCursor:{
 41             value:'move'
 42         },
 43 
 44         /***
 45          * the number of pixels to move to start a drag operation,default is 3.
 46          * @type Number
 47          */
 48         clickPixelThresh:{
 49             value:PIXEL_THRESH
 50         },
 51 
 52         /**
 53          * the number of milliseconds to start a drag operation after mousedown,default is 1000
 54          * @type Number
 55          */
 56         bufferTime:{ value:BUFFER_TIME },
 57 
 58         /**
 59          * currently active draggable object
 60          * @type DD.Draggable
 61          */
 62         activeDrag:{},
 63 
 64         /**
 65          * currently active droppable object
 66          * @type DD.Droppable
 67          */
 68         activeDrop:{},
 69 
 70         /**
 71          * a array of drop targets
 72          * @type DD.Droppable[]
 73          */
 74         drops:{
 75             value:[]
 76         },
 77 
 78         /**
 79          * a array of the valid drop targets for this interaction
 80          * @type DD.Droppable[]
 81          */
 82         validDrops:{
 83             value:[]
 84         }
 85     };
 86 
 87     /*
 88      全局鼠标移动事件通知当前拖动对象正在移动
 89      注意:chrome8: click 时 mousedown-mousemove-mouseup-click 也会触发 mousemove
 90      */
 91     function move(ev) {
 92         var self = this,
 93             __activeToDrag ,
 94             activeDrag;
 95         // 先处理预备役,效率!
 96         if (__activeToDrag = self.__activeToDrag) {
 97             //防止 ie 选择到字
 98             ev.preventDefault();
 99             __activeToDrag._move(ev);
100 
101         } else if (activeDrag = self.get('activeDrag')) {
102             //防止 ie 选择到字
103             ev.preventDefault();
104             activeDrag._move(ev);
105             /**
106              * 获得当前的激活drop
107              */
108             notifyDropsMove(self, ev, activeDrag);
109         }
110     }
111 
112 
113     function notifyDropsMove(self, ev, activeDrag) {
114         var mode = activeDrag.get("mode"),
115             drops = self.get("validDrops"),
116             activeDrop = 0,
117             oldDrop,
118             vArea = 0,
119             dragRegion = region(activeDrag.get("node")),
120             dragArea = area(dragRegion);
121 
122         S.each(drops, function (drop) {
123             var a,
124                 node = drop.getNodeFromTarget(ev,
125                     // node
126                     activeDrag.get("dragNode")[0],
127                     // proxy node
128                     activeDrag.get("node")[0]);
129 
130             if (!node
131             // 当前 drop 区域已经包含  activeDrag.get("node")
132             // 不要返回,可能想调整位置
133                 ) {
134                 return;
135             }
136 
137             if (mode == "point") {
138                 //取鼠标所在的 drop 区域
139                 if (inNodeByPointer(node, activeDrag.mousePos)) {
140                     a = area(region(node));
141                     if (!activeDrop) {
142                         activeDrop = drop;
143                         vArea = a;
144                     } else {
145                         // 当前得到的可放置元素范围更小,取范围小的那个
146                         if (a < vArea) {
147                             activeDrop = drop;
148                             vArea = a;
149                         }
150                     }
151                 }
152             } else if (mode == "intersect") {
153                 //取一个和activeDrag交集最大的drop区域
154                 a = area(intersect(dragRegion, region(node)));
155                 if (a > vArea) {
156                     vArea = a;
157                     activeDrop = drop;
158                 }
159 
160             } else if (mode == "strict") {
161                 //drag 全部在 drop 里面
162                 a = area(intersect(dragRegion, region(node)));
163                 if (a == dragArea) {
164                     activeDrop = drop;
165                     return false;
166                 }
167             }
168         });
169         oldDrop = self.get("activeDrop");
170         if (oldDrop && oldDrop != activeDrop) {
171             oldDrop._handleOut(ev);
172             activeDrag._handleOut(ev);
173         }
174         self.__set("activeDrop", activeDrop);
175         if (activeDrop) {
176             if (oldDrop != activeDrop) {
177                 activeDrop._handleEnter(ev);
178             } else {
179                 // 注意处理代理时内部节点变化导致的 out、enter
180                 activeDrop._handleOver(ev);
181             }
182         }
183     }
184 
185 
186     /**
187      * 垫片只需创建一次
188      */
189     function activeShim(self) {
190         //创造垫片,防止进入iframe,外面document监听不到 mousedown/up/move
191         self._shim = new Node("<div " +
192             "style='" +
193             //red for debug
194             "background-color:red;" +
195             "position:" + (ie6 ? 'absolute' : 'fixed') + ";" +
196             "left:0;" +
197             "width:100%;" +
198             "height:100%;" +
199             "top:0;" +
200             "cursor:" + ddm.get("dragCursor") + ";" +
201             "z-index:" +
202             //覆盖iframe上面即可
203             SHIM_ZINDEX
204             + ";" +
205             "'><" + "/div>")
206             .prependTo(doc.body || doc.documentElement)
207             //0.5 for debug
208             .css("opacity", 0);
209 
210         activeShim = showShim;
211 
212         if (ie6) {
213             // ie6 不支持 fixed 以及 width/height 100%
214             // support dd-scroll
215             // prevent empty when scroll outside initial window
216             Event.on(win, "resize scroll", adjustShimSize, self);
217         }
218 
219         showShim(self);
220     }
221 
222     var adjustShimSize = S.throttle(function () {
223         var self = this,
224             activeDrag;
225         if ((activeDrag = self.get("activeDrag")) &&
226             activeDrag.get("shim")) {
227             self._shim.css({
228                 width:DOM.docWidth(),
229                 height:DOM.docHeight()
230             });
231         }
232     }, MOVE_DELAY);
233 
234     function showShim(self) {
235         // determine cursor according to activeHandler and dragCursor
236         var ah = self.get("activeDrag").get('activeHandler'),
237             cur = 'auto';
238         if (ah) {
239             cur = ah.css('cursor');
240         }
241         if (cur == 'auto') {
242             cur = self.get('dragCursor');
243         }
244         self._shim.css({
245             cursor:cur,
246             display:"block"
247         });
248         if (ie6) {
249             adjustShimSize.call(self);
250         }
251     }
252 
253     /**
254      * 开始时注册全局监听事件
255      */
256     function registerEvent(self) {
257         Event.on(doc, 'mouseup', self._end, self);
258         Event.on(doc, 'mousemove', _showShimMove, self);
259         // ie6 will not response to event when cursor is out of window.
260         if (UA.ie === 6) {
261             doc.body.setCapture();
262         }
263     }
264 
265     /**
266      * 结束时需要取消掉,防止平时无谓的监听
267      */
268     function unRegisterEvent(self) {
269         Event.remove(doc, 'mousemove', _showShimMove, self);
270         Event.remove(doc, 'mouseup', self._end, self);
271         if (UA.ie === 6) {
272             doc.body.releaseCapture();
273         }
274     }
275 
276 
277     function _activeDrops(self) {
278         var drops = self.get("drops");
279         self.__set("validDrops", []);
280         S.each(drops, function (d) {
281             d._active();
282         });
283     }
284 
285     function _deActiveDrops(self) {
286         var drops = self.get("drops");
287         self.__set("validDrops", []);
288         S.each(drops, function (d) {
289             d._deActive();
290         });
291     }
292 
293     /*
294      负责拖动涉及的全局事件:
295      1.全局统一的鼠标移动监控
296      2.全局统一的鼠标弹起监控,用来通知当前拖动对象停止
297      3.为了跨越 iframe 而统一在底下的遮罩层
298      */
299     S.extend(DDM, Base, {
300 
301         /**
302          * 可能要进行拖放的对象,需要通过 buffer/pixelThresh 考验
303          */
304         __activeToDrag:0,
305 
306         _regDrop:function (d) {
307             this.get("drops").push(d);
308         },
309 
310         _unRegDrop:function (d) {
311             var self = this,
312                 index = S.indexOf(d, self.get("drops"));
313             if (index != -1) {
314                 self.get("drops").splice(index, 1);
315             }
316         },
317 
318         /**
319          * 注册可能将要拖放的节点
320          * @param drag
321          */
322         _regToDrag:function (drag) {
323             var self = this;
324             // 事件先要注册好,防止点击,导致 mouseup 时还没注册事件
325             self.__activeToDrag = drag;
326             registerEvent(self);
327 
328         },
329 
330         /**
331          * 真正开始 drag
332          * 当前拖动对象通知全局:我要开始啦
333          * 全局设置当前拖动对象,
334          */
335         _start:function () {
336             var self = this,
337                 drops = self.get("drops"),
338                 drag = self.__activeToDrag;
339             self.__set('activeDrag', drag);
340             // 预备役清掉
341             self.__activeToDrag = 0;
342             // 真正开始移动了才激活垫片
343             if (drag.get("shim")) {
344                 activeShim(self);
345             }
346             cacheWH(drag.get("node"));
347             _activeDrops(self);
348         },
349 
350         _addValidDrop:function (drop) {
351             this.get("validDrops").push(drop);
352         },
353 
354         /**
355          * 全局通知当前拖动对象:结束拖动了!
356          */
357         _end:function () {
358             var self = this,
359                 activeDrag = self.get("activeDrag"),
360                 activeDrop = self.get("activeDrop");
361             unRegisterEvent(self);
362             // 预备役清掉 , click 情况下 mousedown->mouseup 极快过渡
363             if (self.__activeToDrag) {
364                 self.__activeToDrag._end();
365                 self.__activeToDrag = 0;
366             }
367             if (self._shim) {
368                 self._shim.hide();
369             }
370             if (!activeDrag) {
371                 return;
372             }
373             activeDrag._end();
374             _deActiveDrops(self);
375             if (activeDrop) {
376                 activeDrop._end();
377             }
378             self.__set("activeDrag", null);
379             self.__set("activeDrop", null);
380         }
381     });
382 
383     function region(node) {
384         var offset = node.offset();
385         if (!node.__dd_cached_width) {
386             S.log("no cache in dd!");
387             S.log(node[0]);
388         }
389         return {
390             left:offset.left,
391             right:offset.left + (node.__dd_cached_width || node.outerWidth()),
392             top:offset.top,
393             bottom:offset.top + (node.__dd_cached_height || node.outerHeight())
394         };
395     }
396 
397     function inRegion(region, pointer) {
398         return region.left <= pointer.left
399             && region.right >= pointer.left
400             && region.top <= pointer.top
401             && region.bottom >= pointer.top;
402     }
403 
404     function area(region) {
405         if (region.top >= region.bottom || region.left >= region.right) {
406             return 0;
407         }
408         return (region.right - region.left) * (region.bottom - region.top);
409     }
410 
411     function intersect(r1, r2) {
412         var t = Math.max(r1['top'], r2.top),
413             r = Math.min(r1.right, r2.right),
414             b = Math.min(r1['bottom'], r2.bottom),
415             l = Math.max(r1.left, r2.left);
416         return {
417             left:l,
418             right:r,
419             top:t,
420             bottom:b
421         };
422     }
423 
424     function inNodeByPointer(node, point) {
425         return inRegion(region(node), point);
426     }
427 
428     function cacheWH(node) {
429         if (node) {
430             node.__dd_cached_width = node.outerWidth();
431             node.__dd_cached_height = node.outerHeight();
432         }
433     }
434 
435     var ddm = new DDM();
436     ddm.inRegion = inRegion;
437     ddm.region = region;
438     ddm.area = area;
439     ddm.cacheWH = cacheWH;
440 
441     ddm.PREFIX_CLS='ks-dd-';
442     return ddm;
443 }, {
444     requires:["ua", "dom", "event", "node", "base"]
445 });
446 
447 /**
448  * refer
449  *  - YUI3 dd
450  */
451