Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / dd / ScrollManager.js
1 /**
2  * @class Ext.dd.ScrollManager
3  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
4  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
5  * but you can also override most of the configs per scroll container by adding a
6  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
7  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
8  * <pre><code>
9 var el = Ext.get('scroll-ct');
10 el.ddScrollConfig = {
11     vthresh: 50,
12     hthresh: -1,
13     frequency: 100,
14     increment: 200
15 };
16 Ext.dd.ScrollManager.register(el);
17 </code></pre>
18  * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
19  * @singleton
20  */
21 Ext.define('Ext.dd.ScrollManager', {
22     singleton: true,
23     requires: [
24         'Ext.dd.DragDropManager'
25     ],
26
27     constructor: function() {
28         var ddm = Ext.dd.DragDropManager;
29         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
30         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
31         this.doScroll = Ext.Function.bind(this.doScroll, this);
32         this.ddmInstance = ddm;
33         this.els = {};
34         this.dragEl = null;
35         this.proc = {};
36     },
37
38     onStop: function(e){
39         var sm = Ext.dd.ScrollManager;
40         sm.dragEl = null;
41         sm.clearProc();
42     },
43
44     triggerRefresh: function() {
45         if (this.ddmInstance.dragCurrent) {
46             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
47         }
48     },
49
50     doScroll: function() {
51         if (this.ddmInstance.dragCurrent) {
52             var proc   = this.proc,
53                 procEl = proc.el,
54                 ddScrollConfig = proc.el.ddScrollConfig,
55                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
56
57             if (!this.animate) {
58                 if (procEl.scroll(proc.dir, inc)) {
59                     this.triggerRefresh();
60                 }
61             } else {
62                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
63             }
64         }
65     },
66
67     clearProc: function() {
68         var proc = this.proc;
69         if (proc.id) {
70             clearInterval(proc.id);
71         }
72         proc.id = 0;
73         proc.el = null;
74         proc.dir = "";
75     },
76
77     startProc: function(el, dir) {
78         this.clearProc();
79         this.proc.el = el;
80         this.proc.dir = dir;
81         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
82             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
83                   ? el.ddScrollConfig.frequency
84                   : this.frequency;
85
86         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
87             this.proc.id = setInterval(this.doScroll, freq);
88         }
89     },
90
91     onFire: function(e, isDrop) {
92         if (isDrop || !this.ddmInstance.dragCurrent) {
93             return;
94         }
95         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
96             this.dragEl = this.ddmInstance.dragCurrent;
97             // refresh regions on drag start
98             this.refreshCache();
99         }
100
101         var xy = e.getXY(),
102             pt = e.getPoint(),
103             proc = this.proc,
104             els = this.els;
105
106         for (var id in els) {
107             var el = els[id], r = el._region;
108             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
109             if (r && r.contains(pt) && el.isScrollable()) {
110                 if (r.bottom - pt.y <= c.vthresh) {
111                     if(proc.el != el){
112                         this.startProc(el, "down");
113                     }
114                     return;
115                 }else if (r.right - pt.x <= c.hthresh) {
116                     if (proc.el != el) {
117                         this.startProc(el, "left");
118                     }
119                     return;
120                 } else if(pt.y - r.top <= c.vthresh) {
121                     if (proc.el != el) {
122                         this.startProc(el, "up");
123                     }
124                     return;
125                 } else if(pt.x - r.left <= c.hthresh) {
126                     if (proc.el != el) {
127                         this.startProc(el, "right");
128                     }
129                     return;
130                 }
131             }
132         }
133         this.clearProc();
134     },
135
136     /**
137      * Registers new overflow element(s) to auto scroll
138      * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
139      */
140     register : function(el){
141         if (Ext.isArray(el)) {
142             for(var i = 0, len = el.length; i < len; i++) {
143                     this.register(el[i]);
144             }
145         } else {
146             el = Ext.get(el);
147             this.els[el.id] = el;
148         }
149     },
150
151     /**
152      * Unregisters overflow element(s) so they are no longer scrolled
153      * @param {Mixed/Array} el The id of or the element to be removed or an array of either
154      */
155     unregister : function(el){
156         if(Ext.isArray(el)){
157             for (var i = 0, len = el.length; i < len; i++) {
158                 this.unregister(el[i]);
159             }
160         }else{
161             el = Ext.get(el);
162             delete this.els[el.id];
163         }
164     },
165
166     /**
167      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
168      * trigger scrolling (defaults to 25)
169      * @type Number
170      */
171     vthresh : 25,
172     /**
173      * The number of pixels from the right or left edge of a container the pointer needs to be to
174      * trigger scrolling (defaults to 25)
175      * @type Number
176      */
177     hthresh : 25,
178
179     /**
180      * The number of pixels to scroll in each scroll increment (defaults to 100)
181      * @type Number
182      */
183     increment : 100,
184
185     /**
186      * The frequency of scrolls in milliseconds (defaults to 500)
187      * @type Number
188      */
189     frequency : 500,
190
191     /**
192      * True to animate the scroll (defaults to true)
193      * @type Boolean
194      */
195     animate: true,
196
197     /**
198      * The animation duration in seconds -
199      * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
200      * @type Number
201      */
202     animDuration: 0.4,
203
204     /**
205      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs (defaults to undefined).
206      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
207      * @type String
208      */
209     ddGroup: undefined,
210
211     /**
212      * Manually trigger a cache refresh.
213      */
214     refreshCache : function(){
215         var els = this.els,
216             id;
217         for (id in els) {
218             if(typeof els[id] == 'object'){ // for people extending the object prototype
219                 els[id]._region = els[id].getRegion();
220             }
221         }
222     }
223 });