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