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