1 /**
  2  * @fileOverview auto scroll for drag object's container
  3  * @author yiminghe@gmail.com
  4  */
  5 KISSY.add("dd/scroll", function (S, DDM, Base, Node, DOM) {
  6 
  7     var TAG_DRAG = "__dd-scroll-id-",
  8         win = S.Env.host,
  9         stamp = S.stamp,
 10         RATE = [10, 10],
 11         ADJUST_DELAY = 100,
 12         DIFF = [20, 20],
 13         DESTRUCTORS = "__dd_scrolls";
 14 
 15     /**
 16      * @name Scroll
 17      * @memberOf DD
 18      * @class
 19      * make parent node scroll while dragging
 20      */
 21     function Scroll() {
 22         var self = this;
 23         Scroll.superclass.constructor.apply(self, arguments);
 24         self[DESTRUCTORS] = {};
 25     }
 26 
 27     Scroll.ATTRS =
 28     /**
 29      * @lends DD.Scroll#
 30      */
 31     {
 32         /**
 33          * node to be scrolled while dragging
 34          * @type {window|String|HTMLElement}
 35          */
 36         node:{
 37             // value:window:不行,默认值一定是简单对象
 38             valueFn:function () {
 39                 return Node.one(win);
 40             },
 41             setter:function (v) {
 42                 return Node.one(v);
 43             }
 44         },
 45         /**
 46          * adjust velocity. default:[10,10]. larger faster
 47          * @type Number[]
 48          */
 49         rate:{
 50             value:RATE
 51         },
 52         /**
 53          * the margin to make node scroll. default: [20,20].
 54          * easier to scroll for node if larger.
 55          * @type number[]
 56          */
 57         diff:{
 58             value:DIFF
 59         }
 60     };
 61 
 62 
 63     var isWin = S.isWindow;
 64 
 65     S.extend(Scroll, Base,
 66         /**
 67          * @lends DD.Scroll#
 68          */
 69         {
 70             /**
 71              * @private
 72              * @param node
 73              */
 74             getRegion:function (node) {
 75                 if (isWin(node[0])) {
 76                     return {
 77                         width:DOM.viewportWidth(),
 78                         height:DOM.viewportHeight()
 79                     };
 80                 } else {
 81                     return {
 82                         width:node.outerWidth(),
 83                         height:node.outerHeight()
 84                     };
 85                 }
 86             },
 87 
 88             /**
 89              * @private
 90              * @param node
 91              */
 92             getOffset:function (node) {
 93                 if (isWin(node[0])) {
 94                     return {
 95                         left:DOM.scrollLeft(),
 96                         top:DOM.scrollTop()
 97                     };
 98                 } else {
 99                     return node.offset();
100                 }
101             },
102 
103             /**
104              * @private
105              * @param node
106              */
107             getScroll:function (node) {
108                 return {
109                     left:node.scrollLeft(),
110                     top:node.scrollTop()
111                 };
112             },
113 
114             /**
115              * @private
116              * @param node
117              */
118             setScroll:function (node, r) {
119                 node.scrollLeft(r.left);
120                 node.scrollTop(r.top);
121             },
122 
123             /**
124              * make node not to scroll while this drag object is dragging
125              * @param {DD.Draggable} drag
126              */
127             detachDrag:function (drag) {
128                 var tag,
129                     destructors = this[DESTRUCTORS];
130                 if (!(tag = stamp(drag, 1, TAG_DRAG)) ||
131                     !destructors[tag]
132                     ) {
133                     return;
134                 }
135                 destructors[tag].fn();
136                 delete destructors[tag];
137             },
138 
139             /**
140              * make node not to scroll at all
141              */
142             destroy:function () {
143                 var self = this,
144                     destructors = self[DESTRUCTORS];
145                 for (var d in destructors) {
146                     self.detachDrag(destructors[d].drag);
147                 }
148             },
149 
150             /**
151              * make node to scroll while this drag object is dragging
152              * @param {DD.Draggable} drag
153              */
154             attachDrag:function (drag) {
155                 var self = this,
156                     node = self.get("node"),
157                     tag = stamp(drag, 0, TAG_DRAG),
158                     destructors = self[DESTRUCTORS];
159 
160                 if (destructors[tag]) {
161                     return;
162                 }
163 
164                 var rate = self.get("rate"),
165                     diff = self.get('diff'),
166                     event,
167                 /*
168                  目前相对 container 的偏移,container 为 window 时,相对于 viewport
169                  */
170                     dxy,
171                     timer = null;
172 
173                 // fix https://github.com/kissyteam/kissy/issues/115
174                 // dragDelegate 时 可能一个 dragDelegate对应多个 scroll
175                 // check container
176                 function checkContainer() {
177                     if (isWin(node[0])) {
178                         return 0;
179                     }
180                     // 判断 proxyNode,不对 dragNode 做大的改变
181                     var mousePos = drag.mousePos,
182                         r = DDM.region(node);
183 
184                     if (!DDM.inRegion(r, mousePos)) {
185                         clearTimeout(timer);
186                         timer = 0;
187                         return 1;
188                     }
189                     return 0;
190                 }
191 
192                 function dragging(ev) {
193                     // 给调用者的事件,框架不需要处理
194                     // fake 也表示该事件不是因为 mouseover 产生的
195                     if (ev.fake) {
196                         return;
197                     }
198 
199                     if (checkContainer()) {
200                         return;
201                     }
202 
203                     // 更新当前鼠标相对于拖节点的相对位置
204                     event = ev;
205                     dxy = S.clone(drag.mousePos);
206                     var offset = self.getOffset(node);
207                     dxy.left -= offset.left;
208                     dxy.top -= offset.top;
209                     if (!timer) {
210                         checkAndScroll();
211                     }
212                 }
213 
214                 function dragEnd() {
215                     clearTimeout(timer);
216                     timer = null;
217                 }
218 
219                 drag.on("drag", dragging);
220 
221                 drag.on("dragstart", function () {
222                     DDM.cacheWH(node);
223                 });
224 
225                 drag.on("dragend", dragEnd);
226 
227                 destructors[tag] = {
228                     drag:drag,
229                     fn:function () {
230                         drag.detach("drag", dragging);
231                         drag.detach("dragend", dragEnd);
232                     }
233                 };
234 
235                 function checkAndScroll() {
236                     if (checkContainer()) {
237                         return;
238                     }
239 
240                     var r = self.getRegion(node),
241                         nw = r.width,
242                         nh = r.height,
243                         scroll = self.getScroll(node),
244                         origin = S.clone(scroll),
245                         diffY = dxy.top - nh,
246                         adjust = false;
247 
248                     if (diffY >= -diff[1]) {
249                         scroll.top += rate[1];
250                         adjust = true;
251                     }
252 
253                     var diffY2 = dxy.top;
254 
255                     if (diffY2 <= diff[1]) {
256                         scroll.top -= rate[1];
257                         adjust = true;
258                     }
259 
260                     var diffX = dxy.left - nw;
261 
262                     if (diffX >= -diff[0]) {
263                         scroll.left += rate[0];
264                         adjust = true;
265                     }
266 
267                     var diffX2 = dxy.left;
268 
269                     if (diffX2 <= diff[0]) {
270                         scroll.left -= rate[0];
271                         adjust = true;
272                     }
273 
274                     if (adjust) {
275                         self.setScroll(node, scroll);
276                         timer = setTimeout(checkAndScroll, ADJUST_DELAY);
277                         // 不希望更新相对值,特别对于相对 window 时,相对值如果不真正拖放触发的 drag,是不变的,
278                         // 不会因为程序 scroll 而改变相对值
279 
280                         // 调整事件,不需要 scroll 监控,达到预期结果:元素随容器的持续不断滚动而自动调整位置.
281                         event.fake = true;
282                         if (isWin(node[0])) {
283                             // 当使 window 自动滚动时,也要使得拖放物体相对文档位置随 scroll 改变
284                             // 而相对 node 容器时,只需 node 容器滚动,拖动物体相对文档位置不需要改变
285                             scroll = self.getScroll(node);
286                             event.left += scroll.left - origin.left;
287                             event.top += scroll.top - origin.top;
288                         }
289                         // 容器滚动了,元素也要重新设置 left,top
290                         if (drag.get("move")) {
291                             drag.get("node").offset(event);
292                         }
293                         drag.fire("drag", event);
294                     } else {
295                         timer = null;
296                     }
297                 }
298 
299             }
300         });
301 
302     // for compatibility
303     var ScrollPrototype = Scroll.prototype;
304     ScrollPrototype.attach = ScrollPrototype.attachDrag;
305     ScrollPrototype.unAttach = ScrollPrototype.detachDrag;
306     return Scroll;
307 }, {
308     requires:['./ddm', 'base', 'node', 'dom']
309 });