Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / src / dd / DragTracker.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.dd.DragTracker
9  * @extends Ext.util.Observable
10  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
11  * as well as during the drag. This is useful for components such as {@link Ext.Slider}, where there is
12  * an element that can be dragged around to change the Slider's value.
13  * DragTracker provides a series of template methods that should be overridden to provide functionality
14  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
15  * See {@link Ext.Slider}'s initEvents function for an example implementation.
16  */
17 Ext.dd.DragTracker = Ext.extend(Ext.util.Observable,  {    
18     /**
19      * @cfg {Boolean} active
20          * Defaults to <tt>false</tt>.
21          */     
22     active: false,
23     /**
24      * @cfg {Number} tolerance
25          * Number of pixels the drag target must be moved before dragging is considered to have started. Defaults to <tt>5</tt>.
26          */     
27     tolerance: 5,
28     /**
29      * @cfg {Boolean/Number} autoStart
30          * Defaults to <tt>false</tt>. Specify <tt>true</tt> to defer trigger start by 1000 ms.
31          * Specify a Number for the number of milliseconds to defer trigger start.
32          */     
33     autoStart: false,
34     
35     constructor : function(config){
36         Ext.apply(this, config);
37             this.addEvents(
38                 /**
39                  * @event mousedown
40                  * @param {Object} this
41                  * @param {Object} e event object
42                  */
43                 'mousedown',
44                 /**
45                  * @event mouseup
46                  * @param {Object} this
47                  * @param {Object} e event object
48                  */
49                 'mouseup',
50                 /**
51                  * @event mousemove
52                  * @param {Object} this
53                  * @param {Object} e event object
54                  */
55                 'mousemove',
56                 /**
57                  * @event dragstart
58                  * @param {Object} this
59                  * @param {Object} startXY the page coordinates of the event
60                  */
61                 'dragstart',
62                 /**
63                  * @event dragend
64                  * @param {Object} this
65                  * @param {Object} e event object
66                  */
67                 'dragend',
68                 /**
69                  * @event drag
70                  * @param {Object} this
71                  * @param {Object} e event object
72                  */
73                 'drag'
74             );
75         
76             this.dragRegion = new Ext.lib.Region(0,0,0,0);
77         
78             if(this.el){
79                 this.initEl(this.el);
80             }
81         Ext.dd.DragTracker.superclass.constructor.call(this, config);
82     },
83
84     initEl: function(el){
85         this.el = Ext.get(el);
86         el.on('mousedown', this.onMouseDown, this,
87                 this.delegate ? {delegate: this.delegate} : undefined);
88     },
89
90     destroy : function(){
91         this.el.un('mousedown', this.onMouseDown, this);
92     },
93
94     onMouseDown: function(e, target){
95         if(this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false){
96             this.startXY = this.lastXY = e.getXY();
97             this.dragTarget = this.delegate ? target : this.el.dom;
98             if(this.preventDefault !== false){
99                 e.preventDefault();
100             }
101             var doc = Ext.getDoc();
102             doc.on('mouseup', this.onMouseUp, this);
103             doc.on('mousemove', this.onMouseMove, this);
104             doc.on('selectstart', this.stopSelect, this);
105             if(this.autoStart){
106                 this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this);
107             }
108         }
109     },
110
111     onMouseMove: function(e, target){
112         // HACK: IE hack to see if button was released outside of window. */
113         if(this.active && Ext.isIE && !e.browserEvent.button){
114             e.preventDefault();
115             this.onMouseUp(e);
116             return;
117         }
118
119         e.preventDefault();
120         var xy = e.getXY(), s = this.startXY;
121         this.lastXY = xy;
122         if(!this.active){
123             if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
124                 this.triggerStart();
125             }else{
126                 return;
127             }
128         }
129         this.fireEvent('mousemove', this, e);
130         this.onDrag(e);
131         this.fireEvent('drag', this, e);
132     },
133
134     onMouseUp: function(e) {
135         var doc = Ext.getDoc();
136         doc.un('mousemove', this.onMouseMove, this);
137         doc.un('mouseup', this.onMouseUp, this);
138         doc.un('selectstart', this.stopSelect, this);
139         e.preventDefault();
140         this.clearStart();
141         var wasActive = this.active;
142         this.active = false;
143         delete this.elRegion;
144         this.fireEvent('mouseup', this, e);
145         if(wasActive){
146             this.onEnd(e);
147             this.fireEvent('dragend', this, e);
148         }
149     },
150
151     triggerStart: function(isTimer) {
152         this.clearStart();
153         this.active = true;
154         this.onStart(this.startXY);
155         this.fireEvent('dragstart', this, this.startXY);
156     },
157
158     clearStart : function() {
159         if(this.timer){
160             clearTimeout(this.timer);
161             delete this.timer;
162         }
163     },
164
165     stopSelect : function(e) {
166         e.stopEvent();
167         return false;
168     },
169     
170     /**
171      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
172      * holds the mouse button down. Return false to disallow the drag
173      * @param {Ext.EventObject} e The event object
174      */
175     onBeforeStart : function(e) {
176
177     },
178
179     /**
180      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
181      * (e.g. the user has moved the tracked element beyond the specified tolerance)
182      * @param {Array} xy x and y co-ordinates of the original location of the tracked element
183      */
184     onStart : function(xy) {
185
186     },
187
188     /**
189      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
190      * @param {Ext.EventObject} e The event object
191      */
192     onDrag : function(e) {
193
194     },
195
196     /**
197      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
198      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
199      * @param {Ext.EventObject} e The event object
200      */
201     onEnd : function(e) {
202
203     },
204
205     /**
206      * Returns the drag target
207      * @return {Ext.Element} The element currently being tracked
208      */
209     getDragTarget : function(){
210         return this.dragTarget;
211     },
212
213     getDragCt : function(){
214         return this.el;
215     },
216
217     getXY : function(constrain){
218         return constrain ?
219                this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
220     },
221
222     getOffset : function(constrain){
223         var xy = this.getXY(constrain);
224         var s = this.startXY;
225         return [s[0]-xy[0], s[1]-xy[1]];
226     },
227
228     constrainModes: {
229         'point' : function(xy){
230
231             if(!this.elRegion){
232                 this.elRegion = this.getDragCt().getRegion();
233             }
234
235             var dr = this.dragRegion;
236
237             dr.left = xy[0];
238             dr.top = xy[1];
239             dr.right = xy[0];
240             dr.bottom = xy[1];
241
242             dr.constrainTo(this.elRegion);
243
244             return [dr.left, dr.top];
245         }
246     }
247 });