Make Build.PL less complainy and add manifest
[cxgn-jslib.git] / MochiKit / DragAndDrop.js
blobc471ffe90087e9d3e2b1a217785804a9993aca3c
1 /***
2 MochiKit.DragAndDrop 1.4
4 See <http://mochikit.com/> for documentation, downloads, license, etc.
6 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
7     Mochi-ized By Thomas Herve (_firstname_@nimail.org)
9 ***/
11 if (typeof(dojo) != 'undefined') {
12     dojo.provide('MochiKit.DragAndDrop');
13     dojo.require('MochiKit.Base');
14     dojo.require('MochiKit.DOM');
15     dojo.require('MochiKit.Iter');
16     dojo.require('MochiKit.Visual');
17     dojo.require('MochiKit.Signal');
20 if (typeof(JSAN) != 'undefined') {
21     JSAN.use("MochiKit.Base", []);
22     JSAN.use("MochiKit.DOM", []);
23     JSAN.use("MochiKit.Visual", []);
24     JSAN.use("MochiKit.Iter", []);
25     JSAN.use("MochiKit.Signal", []);
28 try {
29     if (typeof(MochiKit.Base) == 'undefined' ||
30         typeof(MochiKit.DOM) == 'undefined' ||
31         typeof(MochiKit.Visual) == 'undefined' ||
32         typeof(MochiKit.Signal) == 'undefined' ||
33         typeof(MochiKit.Iter) == 'undefined') {
34         throw "";
35     }
36 } catch (e) {
37     throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM, MochiKit.Visual, MochiKit.Signal and MochiKit.Iter!";
40 if (typeof(MochiKit.DragAndDrop) == 'undefined') {
41     MochiKit.DragAndDrop = {};
44 MochiKit.DragAndDrop.NAME = 'MochiKit.DragAndDrop';
45 MochiKit.DragAndDrop.VERSION = '1.4';
47 MochiKit.DragAndDrop.__repr__ = function () {
48     return '[' + this.NAME + ' ' + this.VERSION + ']';
51 MochiKit.DragAndDrop.toString = function () {
52     return this.__repr__();
55 MochiKit.DragAndDrop.EXPORT = [
56     "Droppable",
57     "Draggable"
60 MochiKit.DragAndDrop.EXPORT_OK = [
61     "Droppables",
62     "Draggables"
65 MochiKit.DragAndDrop.Droppables = {
66     /***
68     Manage all droppables. Shouldn't be used, use the Droppable object instead.
70     ***/
71     drops: [],
73     remove: function (element) {
74         this.drops = MochiKit.Base.filter(function (d) {
75             return d.element != MochiKit.DOM.getElement(element);
76         }, this.drops);
77     },
79     register: function (drop) {
80         this.drops.push(drop);
81     },
83     unregister: function (drop) {
84         this.drops = MochiKit.Base.filter(function (d) {
85             return d != drop;
86         }, this.drops);
87     },
89     prepare: function (element) {
90         MochiKit.Base.map(function (drop) {
91             if (drop.isAccepted(element)) {
92                 if (drop.options.activeclass) {
93                     MochiKit.DOM.addElementClass(drop.element,
94                                                  drop.options.activeclass);
95                 }
96                 drop.options.onactive(drop.element, element);
97             }
98         }, this.drops);
99     },
101     findDeepestChild: function (drops) {
102         deepest = drops[0];
104         for (i = 1; i < drops.length; ++i) {
105             if (MochiKit.DOM.isParent(drops[i].element, deepest.element)) {
106                 deepest = drops[i];
107             }
108         }
109         return deepest;
110     },
112     show: function (point, element) {
113         if (!this.drops.length) {
114             return;
115         }
116         var affected = [];
118         if (this.last_active) {
119             this.last_active.deactivate();
120         }
121         MochiKit.Iter.forEach(this.drops, function (drop) {
122             if (drop.isAffected(point, element)) {
123                 affected.push(drop);
124             }
125         });
126         if (affected.length > 0) {
127             drop = this.findDeepestChild(affected);
128             MochiKit.Position.within(drop.element, point.page.x, point.page.y);
129             drop.options.onhover(element, drop.element,
130                 MochiKit.Position.overlap(drop.options.overlap, drop.element));
131             drop.activate();
132         }
133     },
135     fire: function (event, element) {
136         if (!this.last_active) {
137             return;
138         }
139         MochiKit.Position.prepare();
141         if (this.last_active.isAffected(event.mouse(), element)) {
142             this.last_active.options.ondrop(element,
143                this.last_active.element, event);
144         }
145     },
147     reset: function (element) {
148         MochiKit.Base.map(function (drop) {
149             if (drop.options.activeclass) {
150                 MochiKit.DOM.removeElementClass(drop.element,
151                                                 drop.options.activeclass);
152             }
153             drop.options.ondesactive(drop.element, element);
154         }, this.drops);
155         if (this.last_active) {
156             this.last_active.deactivate();
157         }
158     }
161 /** @id MochiKit.DragAndDrop.Droppable */
162 MochiKit.DragAndDrop.Droppable = function (element, options) {
163     var cls = arguments.callee;
164     if (!(this instanceof cls)) {
165         return new cls(element, options);
166     }
167     this.__init__(element, options);
170 MochiKit.DragAndDrop.Droppable.prototype = {
171     /***
173     A droppable object. Simple use is to create giving an element:
175         new MochiKit.DragAndDrop.Droppable('myelement');
177     Generally you'll want to define the 'ondrop' function and maybe the
178     'accept' option to filter draggables.
180     ***/
181     __class__: MochiKit.DragAndDrop.Droppable,
183     __init__: function (element, /* optional */options) {
184         var d = MochiKit.DOM;
185         var b = MochiKit.Base;
186         this.element = d.getElement(element);
187         this.options = b.update({
189             /** @id MochiKit.DragAndDrop.greedy */
190             greedy: true,
192             /** @id MochiKit.DragAndDrop.hoverclass */
193             hoverclass: null,
195             /** @id MochiKit.DragAndDrop.activeclass */
196             activeclass: null,
198             /** @id MochiKit.DragAndDrop.hoverfunc */
199             hoverfunc: b.noop,
201             /** @id MochiKit.DragAndDrop.accept */
202             accept: null,
204             /** @id MochiKit.DragAndDrop.onactive */
205             onactive: b.noop,
207             /** @id MochiKit.DragAndDrop.ondesactive */
208             ondesactive: b.noop,
210             /** @id MochiKit.DragAndDrop.onhover */
211             onhover: b.noop,
213             /** @id MochiKit.DragAndDrop.ondrop */
214             ondrop: b.noop,
216             /** @id MochiKit.DragAndDrop.containment */
217             containment: [],
218             tree: false
219         }, options || {});
221         // cache containers
222         this.options._containers = [];
223         b.map(MochiKit.Base.bind(function (c) {
224             this.options._containers.push(d.getElement(c));
225         }, this), this.options.containment);
227         d.makePositioned(this.element); // fix IE
229         MochiKit.DragAndDrop.Droppables.register(this);
230     },
232     /** @id MochiKit.DragAndDrop.isContained */
233     isContained: function (element) {
234         if (this.options._containers.length) {
235             var containmentNode;
236             if (this.options.tree) {
237                 containmentNode = element.treeNode;
238             } else {
239                 containmentNode = element.parentNode;
240             }
241             return MochiKit.Iter.some(this.options._containers, function (c) {
242                 return containmentNode == c;
243             });
244         } else {
245             return true;
246         }
247     },
249     /** @id MochiKit.DragAndDrop.isAccepted */
250     isAccepted: function (element) {
251         return ((!this.options.accept) || MochiKit.Iter.some(
252           this.options.accept, function (c) {
253             return MochiKit.DOM.hasElementClass(element, c);
254         }));
255     },
257     /** @id MochiKit.DragAndDrop.isAffected */
258     isAffected: function (point, element) {
259         return ((this.element != element) &&
260                 this.isContained(element) &&
261                 this.isAccepted(element) &&
262                 MochiKit.Position.within(this.element, point.page.x,
263                                                        point.page.y));
264     },
266     /** @id MochiKit.DragAndDrop.deactivate */
267     deactivate: function () {
268         /***
270         A droppable is deactivate when a draggable has been over it and left.
272         ***/
273         if (this.options.hoverclass) {
274             MochiKit.DOM.removeElementClass(this.element,
275                                             this.options.hoverclass);
276         }
277         this.options.hoverfunc(this.element, false);
278         MochiKit.DragAndDrop.Droppables.last_active = null;
279     },
281     /** @id MochiKit.DragAndDrop.activate */
282     activate: function () {
283         /***
285         A droppable is active when a draggable is over it.
287         ***/
288         if (this.options.hoverclass) {
289             MochiKit.DOM.addElementClass(this.element, this.options.hoverclass);
290         }
291         this.options.hoverfunc(this.element, true);
292         MochiKit.DragAndDrop.Droppables.last_active = this;
293     },
295     /** @id MochiKit.DragAndDrop.destroy */
296     destroy: function () {
297         /***
299         Delete this droppable.
301         ***/
302         MochiKit.DragAndDrop.Droppables.unregister(this);
303     },
305     /** @id MochiKit.DragAndDrop.repr */
306     repr: function () {
307         return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
308     }
311 MochiKit.DragAndDrop.Draggables = {
312     /***
314     Manage draggables elements. Not intended to direct use.
316     ***/
317     drags: [],
319     register: function (draggable) {
320         if (this.drags.length === 0) {
321             var conn = MochiKit.Signal.connect;
322             this.eventMouseUp = conn(document, 'onmouseup', this, this.endDrag);
323             this.eventMouseMove = conn(document, 'onmousemove', this,
324                                        this.updateDrag);
325             this.eventKeypress = conn(document, 'onkeypress', this,
326                                       this.keyPress);
327         }
328         this.drags.push(draggable);
329     },
331     unregister: function (draggable) {
332         this.drags = MochiKit.Base.filter(function (d) {
333             return d != draggable;
334         }, this.drags);
335         if (this.drags.length === 0) {
336             var disc = MochiKit.Signal.disconnect;
337             disc(this.eventMouseUp);
338             disc(this.eventMouseMove);
339             disc(this.eventKeypress);
340         }
341     },
343     activate: function (draggable) {
344         // allows keypress events if window is not currently focused
345         // fails for Safari
346         window.focus();
347         this.activeDraggable = draggable;
348     },
350     deactivate: function () {
351         this.activeDraggable = null;
352     },
354     updateDrag: function (event) {
355         if (!this.activeDraggable) {
356             return;
357         }
358         var pointer = event.mouse();
359         // Mozilla-based browsers fire successive mousemove events with
360         // the same coordinates, prevent needless redrawing (moz bug?)
361         if (this._lastPointer && (MochiKit.Base.repr(this._lastPointer.page) ==
362                                   MochiKit.Base.repr(pointer.page))) {
363             return;
364         }
365         this._lastPointer = pointer;
366         this.activeDraggable.updateDrag(event, pointer);
367     },
369     endDrag: function (event) {
370         if (!this.activeDraggable) {
371             return;
372         }
373         this._lastPointer = null;
374         this.activeDraggable.endDrag(event);
375         this.activeDraggable = null;
376     },
378     keyPress: function (event) {
379         if (this.activeDraggable) {
380             this.activeDraggable.keyPress(event);
381         }
382     },
384     notify: function (eventName, draggable, event) {
385         MochiKit.Signal.signal(this, eventName, draggable, event);
386     }
389 /** @id MochiKit.DragAndDrop.Draggable */
390 MochiKit.DragAndDrop.Draggable = function (element, options) {
391     var cls = arguments.callee;
392     if (!(this instanceof cls)) {
393         return new cls(element, options);
394     }
395     this.__init__(element, options);
398 MochiKit.DragAndDrop.Draggable.prototype = {
399     /***
401     A draggable object. Simple instantiate :
403         new MochiKit.DragAndDrop.Draggable('myelement');
405     ***/
406     __class__ : MochiKit.DragAndDrop.Draggable,
408     __init__: function (element, /* optional */options) {
409         var v = MochiKit.Visual;
410         var b = MochiKit.Base;
411         options = b.update({
413             /** @id MochiKit.DragAndDrop.handle */
414             handle: false,
416             /** @id MochiKit.DragAndDrop.starteffect */
417             starteffect: function (innerelement) {
418                 this._savedOpacity = MochiKit.Style.getStyle(innerelement, 'opacity') || 1.0;
419                 new v.Opacity(innerelement, {duration:0.2, from:this._savedOpacity, to:0.7});
420             },
421             /** @id MochiKit.DragAndDrop.reverteffect */
422             reverteffect: function (innerelement, top_offset, left_offset) {
423                 var dur = Math.sqrt(Math.abs(top_offset^2) +
424                           Math.abs(left_offset^2))*0.02;
425                 return new v.Move(innerelement,
426                             {x: -left_offset, y: -top_offset, duration: dur});
427             },
429             /** @id MochiKit.DragAndDrop.endeffect */
430             endeffect: function (innerelement) {
431                 new v.Opacity(innerelement, {duration:0.2, from:0.7, to:this._savedOpacity});
432             },
434             /** @id MochiKit.DragAndDrop.onchange */
435             onchange: b.noop,
437             /** @id MochiKit.DragAndDrop.zindex */
438             zindex: 1000,
440             /** @id MochiKit.DragAndDrop.revert */
441             revert: false,
443             /** @id MochiKit.DragAndDrop.scroll */
444             scroll: false,
446             /** @id MochiKit.DragAndDrop.scrollSensitivity */
447             scrollSensitivity: 20,
449             /** @id MochiKit.DragAndDrop.scrollSpeed */
450             scrollSpeed: 15,
451             // false, or xy or [x, y] or function (x, y){return [x, y];}
453             /** @id MochiKit.DragAndDrop.snap */
454             snap: false
455         }, options || {});
457         var d = MochiKit.DOM;
458         this.element = d.getElement(element);
460         if (options.handle && (typeof(options.handle) == 'string')) {
461             this.handle = d.getFirstElementByTagAndClassName(null,
462                                        options.handle, this.element);
463         }
464         if (!this.handle) {
465             this.handle = d.getElement(options.handle);
466         }
467         if (!this.handle) {
468             this.handle = this.element;
469         }
471         if (options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
472             options.scroll = d.getElement(options.scroll);
473             this._isScrollChild = MochiKit.DOM.isChildNode(this.element, options.scroll);
474         }
476         d.makePositioned(this.element);  // fix IE
478         this.delta = this.currentDelta();
479         this.options = options;
480         this.dragging = false;
482         this.eventMouseDown = MochiKit.Signal.connect(this.handle,
483                               'onmousedown', this, this.initDrag);
484         MochiKit.DragAndDrop.Draggables.register(this);
485     },
487     /** @id MochiKit.DragAndDrop.destroy */
488     destroy: function () {
489         MochiKit.Signal.disconnect(this.eventMouseDown);
490         MochiKit.DragAndDrop.Draggables.unregister(this);
491     },
493     /** @id MochiKit.DragAndDrop.currentDelta */
494     currentDelta: function () {
495         var s = MochiKit.Style.getStyle;
496         return [
497           parseInt(s(this.element, 'left') || '0'),
498           parseInt(s(this.element, 'top') || '0')];
499     },
501     /** @id MochiKit.DragAndDrop.initDrag */
502     initDrag: function (event) {
503         if (!event.mouse().button.left) {
504             return;
505         }
506         // abort on form elements, fixes a Firefox issue
507         var src = event.target();
508         var tagName = (src.tagName || '').toUpperCase();
509         if (tagName === 'INPUT' || tagName === 'SELECT' ||
510             tagName === 'OPTION' || tagName === 'BUTTON' ||
511             tagName === 'TEXTAREA') {
512             return;
513         }
515         if (this._revert) {
516             this._revert.cancel();
517             this._revert = null;
518         }
520         var pointer = event.mouse();
521         var pos = MochiKit.Position.cumulativeOffset(this.element);
522         this.offset = [pointer.page.x - pos.x, pointer.page.y - pos.y];
524         MochiKit.DragAndDrop.Draggables.activate(this);
525         event.stop();
526     },
528     /** @id MochiKit.DragAndDrop.startDrag */
529     startDrag: function (event) {
530         this.dragging = true;
531         if (this.options.selectclass) {
532             MochiKit.DOM.addElementClass(this.element,
533                                          this.options.selectclass);
534         }
535         if (this.options.zindex) {
536             this.originalZ = parseInt(MochiKit.Style.getStyle(this.element,
537                                       'z-index') || '0');
538             this.element.style.zIndex = this.options.zindex;
539         }
541         if (this.options.ghosting) {
542             this._clone = this.element.cloneNode(true);
543             this.ghostPosition = MochiKit.Position.absolutize(this.element);
544             this.element.parentNode.insertBefore(this._clone, this.element);
545         }
547         if (this.options.scroll) {
548             if (this.options.scroll == window) {
549                 var where = this._getWindowScroll(this.options.scroll);
550                 this.originalScrollLeft = where.left;
551                 this.originalScrollTop = where.top;
552             } else {
553                 this.originalScrollLeft = this.options.scroll.scrollLeft;
554                 this.originalScrollTop = this.options.scroll.scrollTop;
555             }
556         }
558         MochiKit.DragAndDrop.Droppables.prepare(this.element);
559         MochiKit.DragAndDrop.Draggables.notify('start', this, event);
560         if (this.options.starteffect) {
561             this.options.starteffect(this.element);
562         }
563     },
565     /** @id MochiKit.DragAndDrop.updateDrag */
566     updateDrag: function (event, pointer) {
567         if (!this.dragging) {
568             this.startDrag(event);
569         }
570         MochiKit.Position.prepare();
571         MochiKit.DragAndDrop.Droppables.show(pointer, this.element);
572         MochiKit.DragAndDrop.Draggables.notify('drag', this, event);
573         this.draw(pointer);
574         this.options.onchange(this);
576         if (this.options.scroll) {
577             this.stopScrolling();
578             var p, q;
579             if (this.options.scroll == window) {
580                 var s = this._getWindowScroll(this.options.scroll);
581                 p = new MochiKit.Style.Coordinates(s.left, s.top);
582                 q = new MochiKit.Style.Coordinates(s.left + s.width,
583                                                    s.top + s.height);
584             } else {
585                 p = MochiKit.Position.page(this.options.scroll);
586                 p.x += this.options.scroll.scrollLeft;
587                 p.y += this.options.scroll.scrollTop;
588                 p.x += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
589                 p.y += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
590                 q = new MochiKit.Style.Coordinates(p.x + this.options.scroll.offsetWidth,
591                                                    p.y + this.options.scroll.offsetHeight);
592             }
593             var speed = [0, 0];
594             if (pointer.page.x > (q.x - this.options.scrollSensitivity)) {
595                 speed[0] = pointer.page.x - (q.x - this.options.scrollSensitivity);
596             } else if (pointer.page.x < (p.x + this.options.scrollSensitivity)) {
597                 speed[0] = pointer.page.x - (p.x + this.options.scrollSensitivity);
598             }
599             if (pointer.page.y > (q.y - this.options.scrollSensitivity)) {
600                 speed[1] = pointer.page.y - (q.y - this.options.scrollSensitivity);
601             } else if (pointer.page.y < (p.y + this.options.scrollSensitivity)) {
602                 speed[1] = pointer.page.y - (p.y + this.options.scrollSensitivity);
603             }
604             this.startScrolling(speed);
605         }
607         // fix AppleWebKit rendering
608         if (/AppleWebKit'/.test(navigator.appVersion)) {
609             window.scrollBy(0, 0);
610         }
611         event.stop();
612     },
614     /** @id MochiKit.DragAndDrop.finishDrag */
615     finishDrag: function (event, success) {
616         var dr = MochiKit.DragAndDrop;
617         this.dragging = false;
618         if (this.options.selectclass) {
619             MochiKit.DOM.removeElementClass(this.element,
620                                             this.options.selectclass);
621         }
623         if (this.options.ghosting) {
624             // XXX: from a user point of view, it would be better to remove
625             // the node only *after* the MochiKit.Visual.Move end when used
626             // with revert.
627             MochiKit.Position.relativize(this.element, this.ghostPosition);
628             MochiKit.DOM.removeElement(this._clone);
629             this._clone = null;
630         }
632         if (success) {
633             dr.Droppables.fire(event, this.element);
634         }
635         dr.Draggables.notify('end', this, event);
637         var revert = this.options.revert;
638         if (revert && typeof(revert) == 'function') {
639             revert = revert(this.element);
640         }
642         var d = this.currentDelta();
643         if (revert && this.options.reverteffect) {
644             this._revert = this.options.reverteffect(this.element,
645                 d[1] - this.delta[1], d[0] - this.delta[0]);
646         } else {
647             this.delta = d;
648         }
650         if (this.options.zindex) {
651             this.element.style.zIndex = this.originalZ;
652         }
654         if (this.options.endeffect) {
655             this.options.endeffect(this.element);
656         }
658         dr.Draggables.deactivate();
659         dr.Droppables.reset(this.element);
660     },
662     /** @id MochiKit.DragAndDrop.keyPress */
663     keyPress: function (event) {
664         if (event.key().string != "KEY_ESCAPE") {
665             return;
666         }
667         this.finishDrag(event, false);
668         event.stop();
669     },
671     /** @id MochiKit.DragAndDrop.endDrag */
672     endDrag: function (event) {
673         if (!this.dragging) {
674             return;
675         }
676         this.stopScrolling();
677         this.finishDrag(event, true);
678         event.stop();
679     },
681     /** @id MochiKit.DragAndDrop.draw */
682     draw: function (point) {
683         var pos = MochiKit.Position.cumulativeOffset(this.element);
684         var d = this.currentDelta();
685         pos.x -= d[0];
686         pos.y -= d[1];
688         if (this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
689             pos.x -= this.options.scroll.scrollLeft - this.originalScrollLeft;
690             pos.y -= this.options.scroll.scrollTop - this.originalScrollTop;
691         }
693         var p = [point.page.x - pos.x - this.offset[0],
694                  point.page.y - pos.y - this.offset[1]];
696         if (this.options.snap) {
697             if (typeof(this.options.snap) == 'function') {
698                 p = this.options.snap(p[0], p[1]);
699             } else {
700                 if (this.options.snap instanceof Array) {
701                     var i = -1;
702                     p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
703                             i += 1;
704                             return Math.round(v/this.options.snap[i]) *
705                                    this.options.snap[i];
706                         }, this), p);
707                 } else {
708                     p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
709                         return Math.round(v/this.options.snap) *
710                                this.options.snap;
711                         }, this), p);
712                 }
713             }
714         }
715         var style = this.element.style;
716         if ((!this.options.constraint) ||
717             (this.options.constraint == 'horizontal')) {
718             style.left = p[0] + 'px';
719         }
720         if ((!this.options.constraint) ||
721             (this.options.constraint == 'vertical')) {
722             style.top = p[1] + 'px';
723         }
724         if (style.visibility == 'hidden') {
725             style.visibility = '';  // fix gecko rendering
726         }
727     },
729     /** @id MochiKit.DragAndDrop.stopScrolling */
730     stopScrolling: function () {
731         if (this.scrollInterval) {
732             clearInterval(this.scrollInterval);
733             this.scrollInterval = null;
734             MochiKit.DragAndDrop.Draggables._lastScrollPointer = null;
735         }
736     },
738     /** @id MochiKit.DragAndDrop.startScrolling */
739     startScrolling: function (speed) {
740         if (!speed[0] && !speed[1]) {
741             return;
742         }
743         this.scrollSpeed = [speed[0] * this.options.scrollSpeed,
744                             speed[1] * this.options.scrollSpeed];
745         this.lastScrolled = new Date();
746         this.scrollInterval = setInterval(MochiKit.Base.bind(this.scroll, this), 10);
747     },
749     /** @id MochiKit.DragAndDrop.scroll */
750     scroll: function () {
751         var current = new Date();
752         var delta = current - this.lastScrolled;
753         this.lastScrolled = current;
755         if (this.options.scroll == window) {
756             var s = this._getWindowScroll(this.options.scroll);
757             if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
758                 var dm = delta / 1000;
759                 this.options.scroll.scrollTo(s.left + dm * this.scrollSpeed[0],
760                                              s.top + dm * this.scrollSpeed[1]);
761             }
762         } else {
763             this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
764             this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
765         }
767         var d = MochiKit.DragAndDrop;
769         MochiKit.Position.prepare();
770         d.Droppables.show(d.Draggables._lastPointer, this.element);
771         d.Draggables.notify('drag', this);
772         if (this._isScrollChild) {
773             d.Draggables._lastScrollPointer = d.Draggables._lastScrollPointer || d.Draggables._lastPointer;
774             d.Draggables._lastScrollPointer.x += this.scrollSpeed[0] * delta / 1000;
775             d.Draggables._lastScrollPointer.y += this.scrollSpeed[1] * delta / 1000;
776             if (d.Draggables._lastScrollPointer.x < 0) {
777                 d.Draggables._lastScrollPointer.x = 0;
778             }
779             if (d.Draggables._lastScrollPointer.y < 0) {
780                 d.Draggables._lastScrollPointer.y = 0;
781             }
782             this.draw(d.Draggables._lastScrollPointer);
783         }
785         this.options.onchange(this);
786     },
788     _getWindowScroll: function (win) {
789         var vp, w, h;
790         MochiKit.DOM.withWindow(win, function () {
791             vp = MochiKit.Style.getViewportPosition(win.document);
792         });
793         if (win.innerWidth) {
794             w = win.innerWidth;
795             h = win.innerHeight;
796         } else if (win.document.documentElement && win.document.documentElement.clientWidth) {
797             w = win.document.documentElement.clientWidth;
798             h = win.document.documentElement.clientHeight;
799         } else {
800             w = win.document.body.offsetWidth;
801             h = win.document.body.offsetHeight;
802         }
803         return {top: vp.x, left: vp.y, width: w, height: h};
804     },
806     /** @id MochiKit.DragAndDrop.repr */
807     repr: function () {
808         return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
809     }
812 MochiKit.DragAndDrop.__new__ = function () {
813     MochiKit.Base.nameFunctions(this);
815     this.EXPORT_TAGS = {
816         ":common": this.EXPORT,
817         ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
818     };
821 MochiKit.DragAndDrop.__new__();
823 MochiKit.Base._exportSymbols(this, MochiKit.DragAndDrop);