Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / dd / DragDrop.js
1 /*
2  * This is a derivative of the similarly named class in the YUI Library.
3  * The original license:
4  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
5  * Code licensed under the BSD License:
6  * http://developer.yahoo.net/yui/license.txt
7  */
8
9
10 /**
11  * @class Ext.dd.DragDrop
12  * Defines the interface and base operation of items that that can be
13  * dragged or can be drop targets.  It was designed to be extended, overriding
14  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
15  * Up to three html elements can be associated with a DragDrop instance:
16  * <ul>
17  * <li>linked element: the element that is passed into the constructor.
18  * This is the element which defines the boundaries for interaction with
19  * other DragDrop objects.</li>
20  * <li>handle element(s): The drag operation only occurs if the element that
21  * was clicked matches a handle element.  By default this is the linked
22  * element, but there are times that you will want only a portion of the
23  * linked element to initiate the drag operation, and the setHandleElId()
24  * method provides a way to define this.</li>
25  * <li>drag element: this represents the element that would be moved along
26  * with the cursor during a drag operation.  By default, this is the linked
27  * element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
28  * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
29  * </li>
30  * </ul>
31  * This class should not be instantiated until the onload event to ensure that
32  * the associated elements are available.
33  * The following would define a DragDrop obj that would interact with any
34  * other DragDrop obj in the "group1" group:
35  * <pre>
36  *  dd = new Ext.dd.DragDrop("div1", "group1");
37  * </pre>
38  * Since none of the event handlers have been implemented, nothing would
39  * actually happen if you were to run the code above.  Normally you would
40  * override this class or one of the default implementations, but you can
41  * also override the methods you want on an instance of the class...
42  * <pre>
43  *  dd.onDragDrop = function(e, id) {
44  *  &nbsp;&nbsp;alert("dd was dropped on " + id);
45  *  }
46  * </pre>
47  * @constructor
48  * @param {String} id of the element that is linked to this instance
49  * @param {String} sGroup the group of related DragDrop objects
50  * @param {object} config an object containing configurable attributes
51  *                Valid properties for DragDrop:
52  *                    padding, isTarget, maintainOffset, primaryButtonOnly
53  */
54
55 Ext.define('Ext.dd.DragDrop', {
56     requires: ['Ext.dd.DragDropManager'],
57     constructor: function(id, sGroup, config) {
58         if(id) {
59             this.init(id, sGroup, config);
60         }
61     },
62     
63     /**
64      * Set to false to enable a DragDrop object to fire drag events while dragging
65      * over its own Element. Defaults to true - DragDrop objects do not by default
66      * fire drag events to themselves.
67      * @property ignoreSelf
68      * @type Boolean
69      */
70
71     /**
72      * The id of the element associated with this object.  This is what we
73      * refer to as the "linked element" because the size and position of
74      * this element is used to determine when the drag and drop objects have
75      * interacted.
76      * @property id
77      * @type String
78      */
79     id: null,
80
81     /**
82      * Configuration attributes passed into the constructor
83      * @property config
84      * @type object
85      */
86     config: null,
87
88     /**
89      * The id of the element that will be dragged.  By default this is same
90      * as the linked element, but could be changed to another element. Ex:
91      * Ext.dd.DDProxy
92      * @property dragElId
93      * @type String
94      * @private
95      */
96     dragElId: null,
97
98     /**
99      * The ID of the element that initiates the drag operation.  By default
100      * this is the linked element, but could be changed to be a child of this
101      * element.  This lets us do things like only starting the drag when the
102      * header element within the linked html element is clicked.
103      * @property handleElId
104      * @type String
105      * @private
106      */
107     handleElId: null,
108
109     /**
110      * An object who's property names identify HTML tags to be considered invalid as drag handles.
111      * A non-null property value identifies the tag as invalid. Defaults to the 
112      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
113 {
114     A: "A"
115 }</code></pre>
116      * @property invalidHandleTypes
117      * @type Object
118      */
119     invalidHandleTypes: null,
120
121     /**
122      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
123      * A non-null property value identifies the ID as invalid. For example, to prevent
124      * dragging from being initiated on element ID "foo", use:<pre><code>
125 {
126     foo: true
127 }</code></pre>
128      * @property invalidHandleIds
129      * @type Object
130      */
131     invalidHandleIds: null,
132
133     /**
134      * An Array of CSS class names for elements to be considered in valid as drag handles.
135      * @property invalidHandleClasses
136      * @type Array
137      */
138     invalidHandleClasses: null,
139
140     /**
141      * The linked element's absolute X position at the time the drag was
142      * started
143      * @property startPageX
144      * @type int
145      * @private
146      */
147     startPageX: 0,
148
149     /**
150      * The linked element's absolute X position at the time the drag was
151      * started
152      * @property startPageY
153      * @type int
154      * @private
155      */
156     startPageY: 0,
157
158     /**
159      * The group defines a logical collection of DragDrop objects that are
160      * related.  Instances only get events when interacting with other
161      * DragDrop object in the same group.  This lets us define multiple
162      * groups using a single DragDrop subclass if we want.
163      * @property groups
164      * @type object An object in the format {'group1':true, 'group2':true}
165      */
166     groups: null,
167
168     /**
169      * Individual drag/drop instances can be locked.  This will prevent
170      * onmousedown start drag.
171      * @property locked
172      * @type boolean
173      * @private
174      */
175     locked: false,
176
177     /**
178      * Lock this instance
179      * @method lock
180      */
181     lock: function() {
182         this.locked = true;
183     },
184
185     /**
186      * When set to true, other DD objects in cooperating DDGroups do not receive
187      * notification events when this DD object is dragged over them. Defaults to false.
188      * @property moveOnly
189      * @type boolean
190      */
191     moveOnly: false,
192
193     /**
194      * Unlock this instace
195      * @method unlock
196      */
197     unlock: function() {
198         this.locked = false;
199     },
200
201     /**
202      * By default, all instances can be a drop target.  This can be disabled by
203      * setting isTarget to false.
204      * @property isTarget
205      * @type boolean
206      */
207     isTarget: true,
208
209     /**
210      * The padding configured for this drag and drop object for calculating
211      * the drop zone intersection with this object.
212      * An array containing the 4 padding values: [top, right, bottom, left]
213      * @property {[int]} padding
214      */
215     padding: null,
216
217     /**
218      * Cached reference to the linked element
219      * @property _domRef
220      * @private
221      */
222     _domRef: null,
223
224     /**
225      * Internal typeof flag
226      * @property __ygDragDrop
227      * @private
228      */
229     __ygDragDrop: true,
230
231     /**
232      * Set to true when horizontal contraints are applied
233      * @property constrainX
234      * @type boolean
235      * @private
236      */
237     constrainX: false,
238
239     /**
240      * Set to true when vertical contraints are applied
241      * @property constrainY
242      * @type boolean
243      * @private
244      */
245     constrainY: false,
246
247     /**
248      * The left constraint
249      * @property minX
250      * @type int
251      * @private
252      */
253     minX: 0,
254
255     /**
256      * The right constraint
257      * @property maxX
258      * @type int
259      * @private
260      */
261     maxX: 0,
262
263     /**
264      * The up constraint
265      * @property minY
266      * @type int
267      * @private
268      */
269     minY: 0,
270
271     /**
272      * The down constraint
273      * @property maxY
274      * @type int
275      * @private
276      */
277     maxY: 0,
278
279     /**
280      * Maintain offsets when we resetconstraints.  Set to true when you want
281      * the position of the element relative to its parent to stay the same
282      * when the page changes
283      *
284      * @property maintainOffset
285      * @type boolean
286      */
287     maintainOffset: false,
288
289     /**
290      * Array of pixel locations the element will snap to if we specified a
291      * horizontal graduation/interval.  This array is generated automatically
292      * when you define a tick interval.
293      * @property {[int]} xTicks
294      */
295     xTicks: null,
296
297     /**
298      * Array of pixel locations the element will snap to if we specified a
299      * vertical graduation/interval.  This array is generated automatically
300      * when you define a tick interval.
301      * @property {[int]} yTicks
302      */
303     yTicks: null,
304
305     /**
306      * By default the drag and drop instance will only respond to the primary
307      * button click (left button for a right-handed mouse).  Set to true to
308      * allow drag and drop to start with any mouse click that is propogated
309      * by the browser
310      * @property primaryButtonOnly
311      * @type boolean
312      */
313     primaryButtonOnly: true,
314
315     /**
316      * The available property is false until the linked dom element is accessible.
317      * @property available
318      * @type boolean
319      */
320     available: false,
321
322     /**
323      * By default, drags can only be initiated if the mousedown occurs in the
324      * region the linked element is.  This is done in part to work around a
325      * bug in some browsers that mis-report the mousedown if the previous
326      * mouseup happened outside of the window.  This property is set to true
327      * if outer handles are defined.
328      *
329      * @property hasOuterHandles
330      * @type boolean
331      * @default false
332      */
333     hasOuterHandles: false,
334
335     /**
336      * Code that executes immediately before the startDrag event
337      * @method b4StartDrag
338      * @private
339      */
340     b4StartDrag: function(x, y) { },
341
342     /**
343      * Abstract method called after a drag/drop object is clicked
344      * and the drag or mousedown time thresholds have beeen met.
345      * @method startDrag
346      * @param {int} X click location
347      * @param {int} Y click location
348      */
349     startDrag: function(x, y) { /* override this */ },
350
351     /**
352      * Code that executes immediately before the onDrag event
353      * @method b4Drag
354      * @private
355      */
356     b4Drag: function(e) { },
357
358     /**
359      * Abstract method called during the onMouseMove event while dragging an
360      * object.
361      * @method onDrag
362      * @param {Event} e the mousemove event
363      */
364     onDrag: function(e) { /* override this */ },
365
366     /**
367      * Abstract method called when this element fist begins hovering over
368      * another DragDrop obj
369      * @method onDragEnter
370      * @param {Event} e the mousemove event
371      * @param {String|DragDrop[]} id In POINT mode, the element
372      * id this is hovering over.  In INTERSECT mode, an array of one or more
373      * dragdrop items being hovered over.
374      */
375     onDragEnter: function(e, id) { /* override this */ },
376
377     /**
378      * Code that executes immediately before the onDragOver event
379      * @method b4DragOver
380      * @private
381      */
382     b4DragOver: function(e) { },
383
384     /**
385      * Abstract method called when this element is hovering over another
386      * DragDrop obj
387      * @method onDragOver
388      * @param {Event} e the mousemove event
389      * @param {String|DragDrop[]} id In POINT mode, the element
390      * id this is hovering over.  In INTERSECT mode, an array of dd items
391      * being hovered over.
392      */
393     onDragOver: function(e, id) { /* override this */ },
394
395     /**
396      * Code that executes immediately before the onDragOut event
397      * @method b4DragOut
398      * @private
399      */
400     b4DragOut: function(e) { },
401
402     /**
403      * Abstract method called when we are no longer hovering over an element
404      * @method onDragOut
405      * @param {Event} e the mousemove event
406      * @param {String|DragDrop[]} id In POINT mode, the element
407      * id this was hovering over.  In INTERSECT mode, an array of dd items
408      * that the mouse is no longer over.
409      */
410     onDragOut: function(e, id) { /* override this */ },
411
412     /**
413      * Code that executes immediately before the onDragDrop event
414      * @method b4DragDrop
415      * @private
416      */
417     b4DragDrop: function(e) { },
418
419     /**
420      * Abstract method called when this item is dropped on another DragDrop
421      * obj
422      * @method onDragDrop
423      * @param {Event} e the mouseup event
424      * @param {String|DragDrop[]} id In POINT mode, the element
425      * id this was dropped on.  In INTERSECT mode, an array of dd items this
426      * was dropped on.
427      */
428     onDragDrop: function(e, id) { /* override this */ },
429
430     /**
431      * Abstract method called when this item is dropped on an area with no
432      * drop target
433      * @method onInvalidDrop
434      * @param {Event} e the mouseup event
435      */
436     onInvalidDrop: function(e) { /* override this */ },
437
438     /**
439      * Code that executes immediately before the endDrag event
440      * @method b4EndDrag
441      * @private
442      */
443     b4EndDrag: function(e) { },
444
445     /**
446      * Fired when we are done dragging the object
447      * @method endDrag
448      * @param {Event} e the mouseup event
449      */
450     endDrag: function(e) { /* override this */ },
451
452     /**
453      * Code executed immediately before the onMouseDown event
454      * @method b4MouseDown
455      * @param {Event} e the mousedown event
456      * @private
457      */
458     b4MouseDown: function(e) {  },
459
460     /**
461      * Event handler that fires when a drag/drop obj gets a mousedown
462      * @method onMouseDown
463      * @param {Event} e the mousedown event
464      */
465     onMouseDown: function(e) { /* override this */ },
466
467     /**
468      * Event handler that fires when a drag/drop obj gets a mouseup
469      * @method onMouseUp
470      * @param {Event} e the mouseup event
471      */
472     onMouseUp: function(e) { /* override this */ },
473
474     /**
475      * Override the onAvailable method to do what is needed after the initial
476      * position was determined.
477      * @method onAvailable
478      */
479     onAvailable: function () {
480     },
481
482     /**
483      * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
484      * @type Object
485      */
486     defaultPadding: {
487         left: 0,
488         right: 0,
489         top: 0,
490         bottom: 0
491     },
492
493     /**
494      * Initializes the drag drop object's constraints to restrict movement to a certain element.
495  *
496  * Usage:
497  <pre><code>
498  var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
499                 { dragElId: "existingProxyDiv" });
500  dd.startDrag = function(){
501      this.constrainTo("parent-id");
502  };
503  </code></pre>
504  * Or you can initalize it using the {@link Ext.core.Element} object:
505  <pre><code>
506  Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
507      startDrag : function(){
508          this.constrainTo("parent-id");
509      }
510  });
511  </code></pre>
512      * @param {Mixed} constrainTo The element to constrain to.
513      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
514      * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
515      * an object containing the sides to pad. For example: {right:10, bottom:10}
516      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
517      */
518     constrainTo : function(constrainTo, pad, inContent){
519         if(Ext.isNumber(pad)){
520             pad = {left: pad, right:pad, top:pad, bottom:pad};
521         }
522         pad = pad || this.defaultPadding;
523         var b = Ext.get(this.getEl()).getBox(),
524             ce = Ext.get(constrainTo),
525             s = ce.getScroll(),
526             c, 
527             cd = ce.dom;
528         if(cd == document.body){
529             c = { x: s.left, y: s.top, width: Ext.core.Element.getViewWidth(), height: Ext.core.Element.getViewHeight()};
530         }else{
531             var xy = ce.getXY();
532             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
533         }
534
535
536         var topSpace = b.y - c.y,
537             leftSpace = b.x - c.x;
538
539         this.resetConstraints();
540         this.setXConstraint(leftSpace - (pad.left||0), // left
541                 c.width - leftSpace - b.width - (pad.right||0), //right
542                                 this.xTickSize
543         );
544         this.setYConstraint(topSpace - (pad.top||0), //top
545                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
546                                 this.yTickSize
547         );
548     },
549
550     /**
551      * Returns a reference to the linked element
552      * @method getEl
553      * @return {HTMLElement} the html element
554      */
555     getEl: function() {
556         if (!this._domRef) {
557             this._domRef = Ext.getDom(this.id);
558         }
559
560         return this._domRef;
561     },
562
563     /**
564      * Returns a reference to the actual element to drag.  By default this is
565      * the same as the html element, but it can be assigned to another
566      * element. An example of this can be found in Ext.dd.DDProxy
567      * @method getDragEl
568      * @return {HTMLElement} the html element
569      */
570     getDragEl: function() {
571         return Ext.getDom(this.dragElId);
572     },
573
574     /**
575      * Sets up the DragDrop object.  Must be called in the constructor of any
576      * Ext.dd.DragDrop subclass
577      * @method init
578      * @param id the id of the linked element
579      * @param {String} sGroup the group of related items
580      * @param {object} config configuration attributes
581      */
582     init: function(id, sGroup, config) {
583         this.initTarget(id, sGroup, config);
584         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
585         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
586     },
587
588     /**
589      * Initializes Targeting functionality only... the object does not
590      * get a mousedown handler.
591      * @method initTarget
592      * @param id the id of the linked element
593      * @param {String} sGroup the group of related items
594      * @param {object} config configuration attributes
595      */
596     initTarget: function(id, sGroup, config) {
597
598         // configuration attributes
599         this.config = config || {};
600
601         // create a local reference to the drag and drop manager
602         this.DDMInstance = Ext.dd.DragDropManager;
603         // initialize the groups array
604         this.groups = {};
605
606         // assume that we have an element reference instead of an id if the
607         // parameter is not a string
608         if (typeof id !== "string") {
609             id = Ext.id(id);
610         }
611
612         // set the id
613         this.id = id;
614
615         // add to an interaction group
616         this.addToGroup((sGroup) ? sGroup : "default");
617
618         // We don't want to register this as the handle with the manager
619         // so we just set the id rather than calling the setter.
620         this.handleElId = id;
621
622         // the linked element is the element that gets dragged by default
623         this.setDragElId(id);
624
625         // by default, clicked anchors will not start drag operations.
626         this.invalidHandleTypes = { A: "A" };
627         this.invalidHandleIds = {};
628         this.invalidHandleClasses = [];
629
630         this.applyConfig();
631
632         this.handleOnAvailable();
633     },
634
635     /**
636      * Applies the configuration parameters that were passed into the constructor.
637      * This is supposed to happen at each level through the inheritance chain.  So
638      * a DDProxy implentation will execute apply config on DDProxy, DD, and
639      * DragDrop in order to get all of the parameters that are available in
640      * each object.
641      * @method applyConfig
642      */
643     applyConfig: function() {
644
645         // configurable properties:
646         //    padding, isTarget, maintainOffset, primaryButtonOnly
647         this.padding           = this.config.padding || [0, 0, 0, 0];
648         this.isTarget          = (this.config.isTarget !== false);
649         this.maintainOffset    = (this.config.maintainOffset);
650         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
651
652     },
653
654     /**
655      * Executed when the linked element is available
656      * @method handleOnAvailable
657      * @private
658      */
659     handleOnAvailable: function() {
660         this.available = true;
661         this.resetConstraints();
662         this.onAvailable();
663     },
664
665      /**
666      * Configures the padding for the target zone in px.  Effectively expands
667      * (or reduces) the virtual object size for targeting calculations.
668      * Supports css-style shorthand; if only one parameter is passed, all sides
669      * will have that padding, and if only two are passed, the top and bottom
670      * will have the first param, the left and right the second.
671      * @method setPadding
672      * @param {int} iTop    Top pad
673      * @param {int} iRight  Right pad
674      * @param {int} iBot    Bot pad
675      * @param {int} iLeft   Left pad
676      */
677     setPadding: function(iTop, iRight, iBot, iLeft) {
678         // this.padding = [iLeft, iRight, iTop, iBot];
679         if (!iRight && 0 !== iRight) {
680             this.padding = [iTop, iTop, iTop, iTop];
681         } else if (!iBot && 0 !== iBot) {
682             this.padding = [iTop, iRight, iTop, iRight];
683         } else {
684             this.padding = [iTop, iRight, iBot, iLeft];
685         }
686     },
687
688     /**
689      * Stores the initial placement of the linked element.
690      * @method setInitPosition
691      * @param {int} diffX   the X offset, default 0
692      * @param {int} diffY   the Y offset, default 0
693      */
694     setInitPosition: function(diffX, diffY) {
695         var el = this.getEl();
696
697         if (!this.DDMInstance.verifyEl(el)) {
698             return;
699         }
700
701         var dx = diffX || 0;
702         var dy = diffY || 0;
703
704         var p = Ext.core.Element.getXY( el );
705
706         this.initPageX = p[0] - dx;
707         this.initPageY = p[1] - dy;
708
709         this.lastPageX = p[0];
710         this.lastPageY = p[1];
711
712         this.setStartPosition(p);
713     },
714
715     /**
716      * Sets the start position of the element.  This is set when the obj
717      * is initialized, the reset when a drag is started.
718      * @method setStartPosition
719      * @param pos current position (from previous lookup)
720      * @private
721      */
722     setStartPosition: function(pos) {
723         var p = pos || Ext.core.Element.getXY( this.getEl() );
724         this.deltaSetXY = null;
725
726         this.startPageX = p[0];
727         this.startPageY = p[1];
728     },
729
730     /**
731      * Add this instance to a group of related drag/drop objects.  All
732      * instances belong to at least one group, and can belong to as many
733      * groups as needed.
734      * @method addToGroup
735      * @param sGroup {string} the name of the group
736      */
737     addToGroup: function(sGroup) {
738         this.groups[sGroup] = true;
739         this.DDMInstance.regDragDrop(this, sGroup);
740     },
741
742     /**
743      * Remove's this instance from the supplied interaction group
744      * @method removeFromGroup
745      * @param {string}  sGroup  The group to drop
746      */
747     removeFromGroup: function(sGroup) {
748         if (this.groups[sGroup]) {
749             delete this.groups[sGroup];
750         }
751
752         this.DDMInstance.removeDDFromGroup(this, sGroup);
753     },
754
755     /**
756      * Allows you to specify that an element other than the linked element
757      * will be moved with the cursor during a drag
758      * @method setDragElId
759      * @param id {string} the id of the element that will be used to initiate the drag
760      */
761     setDragElId: function(id) {
762         this.dragElId = id;
763     },
764
765     /**
766      * Allows you to specify a child of the linked element that should be
767      * used to initiate the drag operation.  An example of this would be if
768      * you have a content div with text and links.  Clicking anywhere in the
769      * content area would normally start the drag operation.  Use this method
770      * to specify that an element inside of the content div is the element
771      * that starts the drag operation.
772      * @method setHandleElId
773      * @param id {string} the id of the element that will be used to
774      * initiate the drag.
775      */
776     setHandleElId: function(id) {
777         if (typeof id !== "string") {
778             id = Ext.id(id);
779         }
780         this.handleElId = id;
781         this.DDMInstance.regHandle(this.id, id);
782     },
783
784     /**
785      * Allows you to set an element outside of the linked element as a drag
786      * handle
787      * @method setOuterHandleElId
788      * @param id the id of the element that will be used to initiate the drag
789      */
790     setOuterHandleElId: function(id) {
791         if (typeof id !== "string") {
792             id = Ext.id(id);
793         }
794         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
795         this.setHandleElId(id);
796
797         this.hasOuterHandles = true;
798     },
799
800     /**
801      * Remove all drag and drop hooks for this element
802      * @method unreg
803      */
804     unreg: function() {
805         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
806         this._domRef = null;
807         this.DDMInstance._remove(this);
808     },
809
810     destroy : function(){
811         this.unreg();
812     },
813
814     /**
815      * Returns true if this instance is locked, or the drag drop mgr is locked
816      * (meaning that all drag/drop is disabled on the page.)
817      * @method isLocked
818      * @return {boolean} true if this obj or all drag/drop is locked, else
819      * false
820      */
821     isLocked: function() {
822         return (this.DDMInstance.isLocked() || this.locked);
823     },
824
825     /**
826      * Fired when this object is clicked
827      * @method handleMouseDown
828      * @param {Event} e
829      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
830      * @private
831      */
832     handleMouseDown: function(e, oDD){
833         if (this.primaryButtonOnly && e.button != 0) {
834             return;
835         }
836
837         if (this.isLocked()) {
838             return;
839         }
840
841         this.DDMInstance.refreshCache(this.groups);
842
843         var pt = e.getPoint();
844         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
845         } else {
846             if (this.clickValidator(e)) {
847                 // set the initial element position
848                 this.setStartPosition();
849                 this.b4MouseDown(e);
850                 this.onMouseDown(e);
851
852                 this.DDMInstance.handleMouseDown(e, this);
853
854                 this.DDMInstance.stopEvent(e);
855             } else {
856
857
858             }
859         }
860     },
861
862     clickValidator: function(e) {
863         var target = e.getTarget();
864         return ( this.isValidHandleChild(target) &&
865                     (this.id == this.handleElId ||
866                         this.DDMInstance.handleWasClicked(target, this.id)) );
867     },
868
869     /**
870      * Allows you to specify a tag name that should not start a drag operation
871      * when clicked.  This is designed to facilitate embedding links within a
872      * drag handle that do something other than start the drag.
873      * @method addInvalidHandleType
874      * @param {string} tagName the type of element to exclude
875      */
876     addInvalidHandleType: function(tagName) {
877         var type = tagName.toUpperCase();
878         this.invalidHandleTypes[type] = type;
879     },
880
881     /**
882      * Lets you to specify an element id for a child of a drag handle
883      * that should not initiate a drag
884      * @method addInvalidHandleId
885      * @param {string} id the element id of the element you wish to ignore
886      */
887     addInvalidHandleId: function(id) {
888         if (typeof id !== "string") {
889             id = Ext.id(id);
890         }
891         this.invalidHandleIds[id] = id;
892     },
893
894     /**
895      * Lets you specify a css class of elements that will not initiate a drag
896      * @method addInvalidHandleClass
897      * @param {string} cssClass the class of the elements you wish to ignore
898      */
899     addInvalidHandleClass: function(cssClass) {
900         this.invalidHandleClasses.push(cssClass);
901     },
902
903     /**
904      * Unsets an excluded tag name set by addInvalidHandleType
905      * @method removeInvalidHandleType
906      * @param {string} tagName the type of element to unexclude
907      */
908     removeInvalidHandleType: function(tagName) {
909         var type = tagName.toUpperCase();
910         // this.invalidHandleTypes[type] = null;
911         delete this.invalidHandleTypes[type];
912     },
913
914     /**
915      * Unsets an invalid handle id
916      * @method removeInvalidHandleId
917      * @param {string} id the id of the element to re-enable
918      */
919     removeInvalidHandleId: function(id) {
920         if (typeof id !== "string") {
921             id = Ext.id(id);
922         }
923         delete this.invalidHandleIds[id];
924     },
925
926     /**
927      * Unsets an invalid css class
928      * @method removeInvalidHandleClass
929      * @param {string} cssClass the class of the element(s) you wish to
930      * re-enable
931      */
932     removeInvalidHandleClass: function(cssClass) {
933         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
934             if (this.invalidHandleClasses[i] == cssClass) {
935                 delete this.invalidHandleClasses[i];
936             }
937         }
938     },
939
940     /**
941      * Checks the tag exclusion list to see if this click should be ignored
942      * @method isValidHandleChild
943      * @param {HTMLElement} node the HTMLElement to evaluate
944      * @return {boolean} true if this is a valid tag type, false if not
945      */
946     isValidHandleChild: function(node) {
947
948         var valid = true;
949         // var n = (node.nodeName == "#text") ? node.parentNode : node;
950         var nodeName;
951         try {
952             nodeName = node.nodeName.toUpperCase();
953         } catch(e) {
954             nodeName = node.nodeName;
955         }
956         valid = valid && !this.invalidHandleTypes[nodeName];
957         valid = valid && !this.invalidHandleIds[node.id];
958
959         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
960             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
961         }
962
963
964         return valid;
965
966     },
967
968     /**
969      * Create the array of horizontal tick marks if an interval was specified
970      * in setXConstraint().
971      * @method setXTicks
972      * @private
973      */
974     setXTicks: function(iStartX, iTickSize) {
975         this.xTicks = [];
976         this.xTickSize = iTickSize;
977
978         var tickMap = {};
979
980         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
981             if (!tickMap[i]) {
982                 this.xTicks[this.xTicks.length] = i;
983                 tickMap[i] = true;
984             }
985         }
986
987         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
988             if (!tickMap[i]) {
989                 this.xTicks[this.xTicks.length] = i;
990                 tickMap[i] = true;
991             }
992         }
993
994         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
995     },
996
997     /**
998      * Create the array of vertical tick marks if an interval was specified in
999      * setYConstraint().
1000      * @method setYTicks
1001      * @private
1002      */
1003     setYTicks: function(iStartY, iTickSize) {
1004         this.yTicks = [];
1005         this.yTickSize = iTickSize;
1006
1007         var tickMap = {};
1008
1009         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
1010             if (!tickMap[i]) {
1011                 this.yTicks[this.yTicks.length] = i;
1012                 tickMap[i] = true;
1013             }
1014         }
1015
1016         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
1017             if (!tickMap[i]) {
1018                 this.yTicks[this.yTicks.length] = i;
1019                 tickMap[i] = true;
1020             }
1021         }
1022
1023         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
1024     },
1025
1026     /**
1027      * By default, the element can be dragged any place on the screen.  Use
1028      * this method to limit the horizontal travel of the element.  Pass in
1029      * 0,0 for the parameters if you want to lock the drag to the y axis.
1030      * @method setXConstraint
1031      * @param {int} iLeft the number of pixels the element can move to the left
1032      * @param {int} iRight the number of pixels the element can move to the
1033      * right
1034      * @param {int} iTickSize optional parameter for specifying that the
1035      * element
1036      * should move iTickSize pixels at a time.
1037      */
1038     setXConstraint: function(iLeft, iRight, iTickSize) {
1039         this.leftConstraint = iLeft;
1040         this.rightConstraint = iRight;
1041
1042         this.minX = this.initPageX - iLeft;
1043         this.maxX = this.initPageX + iRight;
1044         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
1045
1046         this.constrainX = true;
1047     },
1048
1049     /**
1050      * Clears any constraints applied to this instance.  Also clears ticks
1051      * since they can't exist independent of a constraint at this time.
1052      * @method clearConstraints
1053      */
1054     clearConstraints: function() {
1055         this.constrainX = false;
1056         this.constrainY = false;
1057         this.clearTicks();
1058     },
1059
1060     /**
1061      * Clears any tick interval defined for this instance
1062      * @method clearTicks
1063      */
1064     clearTicks: function() {
1065         this.xTicks = null;
1066         this.yTicks = null;
1067         this.xTickSize = 0;
1068         this.yTickSize = 0;
1069     },
1070
1071     /**
1072      * By default, the element can be dragged any place on the screen.  Set
1073      * this to limit the vertical travel of the element.  Pass in 0,0 for the
1074      * parameters if you want to lock the drag to the x axis.
1075      * @method setYConstraint
1076      * @param {int} iUp the number of pixels the element can move up
1077      * @param {int} iDown the number of pixels the element can move down
1078      * @param {int} iTickSize optional parameter for specifying that the
1079      * element should move iTickSize pixels at a time.
1080      */
1081     setYConstraint: function(iUp, iDown, iTickSize) {
1082         this.topConstraint = iUp;
1083         this.bottomConstraint = iDown;
1084
1085         this.minY = this.initPageY - iUp;
1086         this.maxY = this.initPageY + iDown;
1087         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
1088
1089         this.constrainY = true;
1090
1091     },
1092
1093     /**
1094      * resetConstraints must be called if you manually reposition a dd element.
1095      * @method resetConstraints
1096      * @param {boolean} maintainOffset
1097      */
1098     resetConstraints: function() {
1099         // Maintain offsets if necessary
1100         if (this.initPageX || this.initPageX === 0) {
1101             // figure out how much this thing has moved
1102             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
1103             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
1104
1105             this.setInitPosition(dx, dy);
1106
1107         // This is the first time we have detected the element's position
1108         } else {
1109             this.setInitPosition();
1110         }
1111
1112         if (this.constrainX) {
1113             this.setXConstraint( this.leftConstraint,
1114                                  this.rightConstraint,
1115                                  this.xTickSize        );
1116         }
1117
1118         if (this.constrainY) {
1119             this.setYConstraint( this.topConstraint,
1120                                  this.bottomConstraint,
1121                                  this.yTickSize         );
1122         }
1123     },
1124
1125     /**
1126      * Normally the drag element is moved pixel by pixel, but we can specify
1127      * that it move a number of pixels at a time.  This method resolves the
1128      * location when we have it set up like this.
1129      * @method getTick
1130      * @param {int} val where we want to place the object
1131      * @param {int[]} tickArray sorted array of valid points
1132      * @return {int} the closest tick
1133      * @private
1134      */
1135     getTick: function(val, tickArray) {
1136         if (!tickArray) {
1137             // If tick interval is not defined, it is effectively 1 pixel,
1138             // so we return the value passed to us.
1139             return val;
1140         } else if (tickArray[0] >= val) {
1141             // The value is lower than the first tick, so we return the first
1142             // tick.
1143             return tickArray[0];
1144         } else {
1145             for (var i=0, len=tickArray.length; i<len; ++i) {
1146                 var next = i + 1;
1147                 if (tickArray[next] && tickArray[next] >= val) {
1148                     var diff1 = val - tickArray[i];
1149                     var diff2 = tickArray[next] - val;
1150                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
1151                 }
1152             }
1153
1154             // The value is larger than the last tick, so we return the last
1155             // tick.
1156             return tickArray[tickArray.length - 1];
1157         }
1158     },
1159
1160     /**
1161      * toString method
1162      * @method toString
1163      * @return {string} string representation of the dd obj
1164      */
1165     toString: function() {
1166         return ("DragDrop " + this.id);
1167     }
1168
1169 });