Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / docs / source / Reorderer.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 <div id="cls-Ext.ux.Reorderer"></div>/**
16  * @class Ext.ux.Reorderer
17  * @extends Object
18  * Generic base class for handling reordering of items. This base class must be extended to provide the
19  * actual reordering functionality - the base class just sets up events and abstract logic functions.
20  * It will fire events and set defaults, deferring the actual reordering to a doReorder implementation.
21  * See Ext.ux.TabReorderer for an example.
22  */
23 Ext.ux.Reorderer = Ext.extend(Object, {
24     <div id="prop-Ext.ux.Reorderer-defaults"></div>/**
25      * @property defaults
26      * @type Object
27      * Object containing default values for plugin configuration details. These can be overridden when
28      * constructing the plugin
29      */
30     defaults: {
31         <div id="cfg-Ext.ux.Reorderer-null"></div>/**
32          * @cfg animate
33          * @type Boolean
34          * If set to true, the rearranging of the toolbar items is animated
35          */
36         animate: true,
37         
38         <div id="cfg-Ext.ux.Reorderer-null"></div>/**
39          * @cfg animationDuration
40          * @type Number
41          * The duration of the animation used to move other toolbar items out of the way
42          */
43         animationDuration: 0.2,
44         
45         <div id="cfg-Ext.ux.Reorderer-null"></div>/**
46          * @cfg defaultReorderable
47          * @type Boolean
48          * True to make every toolbar draggable unless reorderable is specifically set to false.
49          * This defaults to false
50          */
51         defaultReorderable: false
52     },
53     
54     <div id="method-Ext.ux.Reorderer-constructor"></div>/**
55      * Creates the plugin instance, applies defaults
56      * @constructor
57      * @param {Object} config Optional config object
58      */
59     constructor: function(config) {
60         Ext.apply(this, config || {}, this.defaults);
61     },
62     
63     <div id="method-Ext.ux.Reorderer-init"></div>/**
64      * Initializes the plugin, stores a reference to the target 
65      * @param {Mixed} target The target component which contains the reorderable items
66      */
67     init: function(target) {
68         <div id="prop-Ext.ux.Reorderer-target"></div>/**
69          * @property target
70          * @type Ext.Component
71          * Reference to the target component which contains the reorderable items
72          */
73         this.target = target;
74         
75         this.initEvents();
76         
77         var items  = this.getItems(),
78             length = items.length,
79             i;
80         
81         for (i = 0; i < length; i++) {
82             this.createIfReorderable(items[i]);
83         }
84     },
85     
86     <div id="method-Ext.ux.Reorderer-reorder"></div>/**
87      * Reorders the items in the target component according to the given mapping object. Example:
88      * this.reorder({
89      *     1: 5,
90      *     3: 2
91      * });
92      * Would move the item at index 1 to index 5, and the item at index 3 to index 2
93      * @param {Object} mappings Object containing current item index as key and new index as property
94      */
95     reorder: function(mappings) {
96         var target = this.target;
97         
98         if (target.fireEvent('before-reorder', mappings, target, this) !== false) {
99             this.doReorder(mappings);
100             
101             target.fireEvent('reorder', mappings, target, this);
102         }
103     },
104     
105     <div id="method-Ext.ux.Reorderer-doReorder"></div>/**
106      * Abstract function to perform the actual reordering. This MUST be overridden in a subclass
107      * @param {Object} mappings Mappings of the old item indexes to new item indexes
108      */
109     doReorder: function(paramName) {
110         throw new Error("doReorder must be implemented in the Ext.ux.Reorderer subclass");
111     },
112     
113     /**
114      * Should create and return an Ext.dd.DD for the given item. This MUST be overridden in a subclass
115      * @param {Mixed} item The item to create a DD for. This could be a TabPanel tab, a Toolbar button, etc
116      * @return {Ext.dd.DD} The DD for the given item
117      */
118     createItemDD: function(item) {
119         throw new Error("createItemDD must be implemented in the Ext.ux.Reorderer subclass");
120     },
121     
122     <div id="method-Ext.ux.Reorderer-createItemDD"></div>/**
123      * Sets up the given Toolbar item as a draggable
124      * @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
125      */
126     createItemDD: function(button) {
127         var el   = button.getEl(),
128             id   = el.id,
129             tbar = this.target,
130             me   = this;
131         
132         button.dd = new Ext.dd.DD(el, undefined, {
133             isTarget: false
134         });
135         
136         button.dd.constrainTo(tbar.getEl());
137         button.dd.setYConstraint(0, 0, 0);
138         
139         Ext.apply(button.dd, {
140             b4StartDrag: function() {       
141                 this.startPosition = el.getXY();
142                 
143                 //bump up the z index of the button being dragged but keep a reference to the original
144                 this.startZIndex = el.getStyle('zIndex');
145                 el.setStyle('zIndex', 10000);
146                 
147                 button.suspendEvents();
148             },
149             
150             onDrag: function(e) {
151                 //calculate the button's index within the toolbar and its current midpoint
152                 var buttonX  = el.getXY()[0],
153                     deltaX   = buttonX - this.startPosition[0],
154                     items    = tbar.items.items,
155                     oldIndex = items.indexOf(button),
156                     newIndex;
157                 
158                 //find which item in the toolbar the midpoint is currently over
159                 for (var index = 0; index < items.length; index++) {
160                     var item = items[index];
161                     
162                     if (item.reorderable && item.id != button.id) {
163                         //find the midpoint of the button
164                         var box        = item.getEl().getBox(),
165                             midpoint   = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
166                             movedLeft  = oldIndex > index && deltaX < 0 && buttonX < midpoint,
167                             movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;
168                         
169                         if (movedLeft || movedRight) {
170                             me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
171                             break;
172                         }                        
173                     }
174                 }
175             },
176             
177             <div id="method-Ext.ux.Reorderer-endDrag"></div>/**
178              * After the drag has been completed, make sure the button being dragged makes it back to
179              * the correct location and resets its z index
180              */
181             endDrag: function() {
182                 //we need to update the cache here for cases where the button was dragged but its
183                 //position in the toolbar did not change
184                 me.updateButtonXCache();
185                 
186                 el.moveTo(me.buttonXCache[button.id], undefined, {
187                     duration: me.animationDuration,
188                     scope   : this,
189                     callback: function() {
190                         button.resumeEvents();
191                         
192                         tbar.fireEvent('reordered', button, tbar);
193                     }
194                 });
195                 
196                 el.setStyle('zIndex', this.startZIndex);
197             }
198         });
199     },
200     
201     /**
202      * @private
203      * Creates a DD instance for a given item if it is reorderable
204      * @param {Mixed} item The item
205      */
206     createIfReorderable: function(item) {
207         if (this.defaultReorderable && item.reorderable == undefined) {
208             item.reorderable = true;
209         }
210         
211         if (item.reorderable && !item.dd) {
212             if (item.rendered) {
213                 this.createItemDD(item);                
214             } else {
215                 item.on('render', this.createItemDD.createDelegate(this, [item]), this, {single: true});
216             }
217         }
218     },
219     
220     <div id="method-Ext.ux.Reorderer-getItems"></div>/**
221      * Returns an array of items which will be made draggable. This defaults to the contents of this.target.items,
222      * but can be overridden - e.g. for TabPanels
223      * @return {Array} The array of items which will be made draggable
224      */
225     getItems: function() {
226         return this.target.items.items;
227     },
228     
229     <div id="method-Ext.ux.Reorderer-initEvents"></div>/**
230      * Adds before-reorder and reorder events to the target component
231      */
232     initEvents: function() {
233         this.target.addEvents(
234           <div id="event-Ext.ux.Reorderer-before-reorder"></div>/**
235            * @event before-reorder
236            * Fires before a reorder occurs. Return false to cancel
237            * @param {Object} mappings Mappings of the old item indexes to new item indexes
238            * @param {Mixed} component The target component
239            * @param {Ext.ux.TabReorderer} this The plugin instance
240            */
241           'before-reorder',
242           
243           <div id="event-Ext.ux.Reorderer-reorder"></div>/**
244            * @event reorder
245            * Fires after a reorder has occured.
246            * @param {Object} mappings Mappings of the old item indexes to the new item indexes
247            * @param {Mixed} component The target component
248            * @param {Ext.ux.TabReorderer} this The plugin instance
249            */
250           'reorder'
251         );
252     }
253 });
254
255 <div id="cls-Ext.ux.HBoxReorderer"></div>/**
256  * @class Ext.ux.HBoxReorderer
257  * @extends Ext.ux.Reorderer
258  * Description
259  */
260 Ext.ux.HBoxReorderer = Ext.extend(Ext.ux.Reorderer, {
261     <div id="method-Ext.ux.HBoxReorderer-init"></div>/**
262      * Initializes the plugin, decorates the container with additional functionality
263      */
264     init: function(container) {
265         <div id="prop-Ext.ux.HBoxReorderer-buttonXCache"></div>/**
266          * This is used to store the correct x value of each button in the array. We need to use this
267          * instead of the button's reported x co-ordinate because the buttons are animated when they move -
268          * if another onDrag is fired while the button is still moving, the comparison x value will be incorrect
269          */
270         this.buttonXCache = {};
271         
272         container.on({
273             scope: this,
274             add  : function(container, item) {
275                 this.createIfReorderable(item);
276             }
277         });
278         
279         //super sets a reference to the toolbar in this.target
280         Ext.ux.HBoxReorderer.superclass.init.apply(this, arguments);
281     },
282     
283     <div id="method-Ext.ux.HBoxReorderer-createItemDD"></div>/**
284      * Sets up the given Toolbar item as a draggable
285      * @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
286      */
287     createItemDD: function(button) {
288         if (button.dd != undefined) {
289             return;
290         }
291         
292         var el   = button.getEl(),
293             id   = el.id,
294             me   = this,
295             tbar = me.target;
296         
297         button.dd = new Ext.dd.DD(el, undefined, {
298             isTarget: false
299         });
300         
301         el.applyStyles({
302             position: 'absolute'
303         });
304         
305         //if a button has a menu, it is disabled while dragging with this function
306         var menuDisabler = function() {
307             return false;
308         };
309         
310         Ext.apply(button.dd, {
311             b4StartDrag: function() {       
312                 this.startPosition = el.getXY();
313                 
314                 //bump up the z index of the button being dragged but keep a reference to the original
315                 this.startZIndex = el.getStyle('zIndex');
316                 el.setStyle('zIndex', 10000);
317                 
318                 button.suspendEvents();
319                 if (button.menu) {
320                     button.menu.on('beforeshow', menuDisabler, me);
321                 }
322             },
323             
324             startDrag: function() {
325                 this.constrainTo(tbar.getEl());
326                 this.setYConstraint(0, 0, 0);
327             },
328             
329             onDrag: function(e) {
330                 //calculate the button's index within the toolbar and its current midpoint
331                 var buttonX  = el.getXY()[0],
332                     deltaX   = buttonX - this.startPosition[0],
333                     items    = tbar.items.items,
334                     length   = items.length,
335                     oldIndex = items.indexOf(button),
336                     newIndex, index, item;
337                 
338                 //find which item in the toolbar the midpoint is currently over
339                 for (index = 0; index < length; index++) {
340                     item = items[index];
341                     
342                     if (item.reorderable && item.id != button.id) {
343                         //find the midpoint of the button
344                         var box        = item.getEl().getBox(),
345                             midpoint   = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
346                             movedLeft  = oldIndex > index && deltaX < 0 && buttonX < midpoint,
347                             movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;
348                         
349                         if (movedLeft || movedRight) {
350                             me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
351                             break;
352                         }                        
353                     }
354                 }
355             },
356             
357             <div id="method-Ext.ux.HBoxReorderer-endDrag"></div>/**
358              * After the drag has been completed, make sure the button being dragged makes it back to
359              * the correct location and resets its z index
360              */
361             endDrag: function() {
362                 //we need to update the cache here for cases where the button was dragged but its
363                 //position in the toolbar did not change
364                 me.updateButtonXCache();
365                 
366                 el.moveTo(me.buttonXCache[button.id], el.getY(), {
367                     duration: me.animationDuration,
368                     scope   : this,
369                     callback: function() {
370                         button.resumeEvents();
371                         if (button.menu) {
372                             button.menu.un('beforeshow', menuDisabler, me);
373                         }
374                         
375                         tbar.fireEvent('reordered', button, tbar);
376                     }
377                 });
378                 
379                 el.setStyle('zIndex', this.startZIndex);
380             }
381         });
382     },
383     
384     onMovedLeft: function(item, newIndex, oldIndex) {
385         var tbar   = this.target,
386             items  = tbar.items.items,
387             length = items.length,
388             index;
389         
390         if (newIndex != undefined && newIndex != oldIndex) {
391             //move the button currently under drag to its new location
392             tbar.remove(item, false);
393             tbar.insert(newIndex, item);
394             
395             //set the correct x location of each item in the toolbar
396             this.updateButtonXCache();
397             for (index = 0; index < length; index++) {
398                 var obj  = items[index],
399                     newX = this.buttonXCache[obj.id];
400                 
401                 if (item == obj) {
402                     item.dd.startPosition[0] = newX;
403                 } else {
404                     var el = obj.getEl();
405                     
406                     el.moveTo(newX, el.getY(), {
407                         duration: this.animationDuration
408                     });
409                 }
410             }
411         }
412     },
413     
414     onMovedRight: function(item, newIndex, oldIndex) {
415         this.onMovedLeft.apply(this, arguments);
416     },
417     
418     /**
419      * @private
420      * Updates the internal cache of button X locations. 
421      */
422     updateButtonXCache: function() {
423         var tbar   = this.target,
424             items  = tbar.items,
425             totalX = tbar.getEl().getBox(true).x;
426             
427         items.each(function(item) {
428             this.buttonXCache[item.id] = totalX;
429
430             totalX += item.getEl().getWidth();
431         }, this);
432     }
433 });</pre>    
434 </body>
435 </html>