3 * Copyright(c) 2006-2010 Sencha Inc.
5 * http://www.sencha.com/license
8 * @class Ext.ux.Reorderer
10 * Generic base class for handling reordering of items. This base class must be extended to provide the
11 * actual reordering functionality - the base class just sets up events and abstract logic functions.
12 * It will fire events and set defaults, deferring the actual reordering to a doReorder implementation.
13 * See Ext.ux.TabReorderer for an example.
15 Ext.ux.Reorderer = Ext.extend(Object, {
19 * Object containing default values for plugin configuration details. These can be overridden when
20 * constructing the plugin
26 * If set to true, the rearranging of the toolbar items is animated
31 * @cfg animationDuration
33 * The duration of the animation used to move other toolbar items out of the way
35 animationDuration: 0.2,
38 * @cfg defaultReorderable
40 * True to make every toolbar draggable unless reorderable is specifically set to false.
41 * This defaults to false
43 defaultReorderable: false
47 * Creates the plugin instance, applies defaults
49 * @param {Object} config Optional config object
51 constructor: function(config) {
52 Ext.apply(this, config || {}, this.defaults);
56 * Initializes the plugin, stores a reference to the target
57 * @param {Mixed} target The target component which contains the reorderable items
59 init: function(target) {
63 * Reference to the target component which contains the reorderable items
69 var items = this.getItems(),
70 length = items.length,
73 for (i = 0; i < length; i++) {
74 this.createIfReorderable(items[i]);
79 * Reorders the items in the target component according to the given mapping object. Example:
84 * Would move the item at index 1 to index 5, and the item at index 3 to index 2
85 * @param {Object} mappings Object containing current item index as key and new index as property
87 reorder: function(mappings) {
88 var target = this.target;
90 if (target.fireEvent('before-reorder', mappings, target, this) !== false) {
91 this.doReorder(mappings);
93 target.fireEvent('reorder', mappings, target, this);
98 * Abstract function to perform the actual reordering. This MUST be overridden in a subclass
99 * @param {Object} mappings Mappings of the old item indexes to new item indexes
101 doReorder: function(paramName) {
102 throw new Error("doReorder must be implemented in the Ext.ux.Reorderer subclass");
106 * Should create and return an Ext.dd.DD for the given item. This MUST be overridden in a subclass
107 * @param {Mixed} item The item to create a DD for. This could be a TabPanel tab, a Toolbar button, etc
108 * @return {Ext.dd.DD} The DD for the given item
110 createItemDD: function(item) {
111 throw new Error("createItemDD must be implemented in the Ext.ux.Reorderer subclass");
115 * Sets up the given Toolbar item as a draggable
116 * @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
118 createItemDD: function(button) {
119 var el = button.getEl(),
124 button.dd = new Ext.dd.DD(el, undefined, {
128 button.dd.constrainTo(tbar.getEl());
129 button.dd.setYConstraint(0, 0, 0);
131 Ext.apply(button.dd, {
132 b4StartDrag: function() {
133 this.startPosition = el.getXY();
135 //bump up the z index of the button being dragged but keep a reference to the original
136 this.startZIndex = el.getStyle('zIndex');
137 el.setStyle('zIndex', 10000);
139 button.suspendEvents();
142 onDrag: function(e) {
143 //calculate the button's index within the toolbar and its current midpoint
144 var buttonX = el.getXY()[0],
145 deltaX = buttonX - this.startPosition[0],
146 items = tbar.items.items,
147 oldIndex = items.indexOf(button),
150 //find which item in the toolbar the midpoint is currently over
151 for (var index = 0; index < items.length; index++) {
152 var item = items[index];
154 if (item.reorderable && item.id != button.id) {
155 //find the midpoint of the button
156 var box = item.getEl().getBox(),
157 midpoint = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
158 movedLeft = oldIndex > index && deltaX < 0 && buttonX < midpoint,
159 movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;
161 if (movedLeft || movedRight) {
162 me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
170 * After the drag has been completed, make sure the button being dragged makes it back to
171 * the correct location and resets its z index
173 endDrag: function() {
174 //we need to update the cache here for cases where the button was dragged but its
175 //position in the toolbar did not change
176 me.updateButtonXCache();
178 el.moveTo(me.buttonXCache[button.id], undefined, {
179 duration: me.animationDuration,
181 callback: function() {
182 button.resumeEvents();
184 tbar.fireEvent('reordered', button, tbar);
188 el.setStyle('zIndex', this.startZIndex);
195 * Creates a DD instance for a given item if it is reorderable
196 * @param {Mixed} item The item
198 createIfReorderable: function(item) {
199 if (this.defaultReorderable && item.reorderable == undefined) {
200 item.reorderable = true;
203 if (item.reorderable && !item.dd) {
205 this.createItemDD(item);
207 item.on('render', this.createItemDD.createDelegate(this, [item]), this, {single: true});
213 * Returns an array of items which will be made draggable. This defaults to the contents of this.target.items,
214 * but can be overridden - e.g. for TabPanels
215 * @return {Array} The array of items which will be made draggable
217 getItems: function() {
218 return this.target.items.items;
222 * Adds before-reorder and reorder events to the target component
224 initEvents: function() {
225 this.target.addEvents(
227 * @event before-reorder
228 * Fires before a reorder occurs. Return false to cancel
229 * @param {Object} mappings Mappings of the old item indexes to new item indexes
230 * @param {Mixed} component The target component
231 * @param {Ext.ux.TabReorderer} this The plugin instance
237 * Fires after a reorder has occured.
238 * @param {Object} mappings Mappings of the old item indexes to the new item indexes
239 * @param {Mixed} component The target component
240 * @param {Ext.ux.TabReorderer} this The plugin instance
248 * @class Ext.ux.HBoxReorderer
249 * @extends Ext.ux.Reorderer
252 Ext.ux.HBoxReorderer = Ext.extend(Ext.ux.Reorderer, {
254 * Initializes the plugin, decorates the container with additional functionality
256 init: function(container) {
258 * This is used to store the correct x value of each button in the array. We need to use this
259 * instead of the button's reported x co-ordinate because the buttons are animated when they move -
260 * if another onDrag is fired while the button is still moving, the comparison x value will be incorrect
262 this.buttonXCache = {};
266 add : function(container, item) {
267 this.createIfReorderable(item);
271 //super sets a reference to the toolbar in this.target
272 Ext.ux.HBoxReorderer.superclass.init.apply(this, arguments);
276 * Sets up the given Toolbar item as a draggable
277 * @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
279 createItemDD: function(button) {
280 if (button.dd != undefined) {
284 var el = button.getEl(),
289 button.dd = new Ext.dd.DD(el, undefined, {
297 //if a button has a menu, it is disabled while dragging with this function
298 var menuDisabler = function() {
302 Ext.apply(button.dd, {
303 b4StartDrag: function() {
304 this.startPosition = el.getXY();
306 //bump up the z index of the button being dragged but keep a reference to the original
307 this.startZIndex = el.getStyle('zIndex');
308 el.setStyle('zIndex', 10000);
310 button.suspendEvents();
312 button.menu.on('beforeshow', menuDisabler, me);
316 startDrag: function() {
317 this.constrainTo(tbar.getEl());
318 this.setYConstraint(0, 0, 0);
321 onDrag: function(e) {
322 //calculate the button's index within the toolbar and its current midpoint
323 var buttonX = el.getXY()[0],
324 deltaX = buttonX - this.startPosition[0],
325 items = tbar.items.items,
326 length = items.length,
327 oldIndex = items.indexOf(button),
328 newIndex, index, item;
330 //find which item in the toolbar the midpoint is currently over
331 for (index = 0; index < length; index++) {
334 if (item.reorderable && item.id != button.id) {
335 //find the midpoint of the button
336 var box = item.getEl().getBox(),
337 midpoint = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
338 movedLeft = oldIndex > index && deltaX < 0 && buttonX < midpoint,
339 movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;
341 if (movedLeft || movedRight) {
342 me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
350 * After the drag has been completed, make sure the button being dragged makes it back to
351 * the correct location and resets its z index
353 endDrag: function() {
354 //we need to update the cache here for cases where the button was dragged but its
355 //position in the toolbar did not change
356 me.updateButtonXCache();
358 el.moveTo(me.buttonXCache[button.id], el.getY(), {
359 duration: me.animationDuration,
361 callback: function() {
362 button.resumeEvents();
364 button.menu.un('beforeshow', menuDisabler, me);
367 tbar.fireEvent('reordered', button, tbar);
371 el.setStyle('zIndex', this.startZIndex);
376 onMovedLeft: function(item, newIndex, oldIndex) {
377 var tbar = this.target,
378 items = tbar.items.items,
379 length = items.length,
382 if (newIndex != undefined && newIndex != oldIndex) {
383 //move the button currently under drag to its new location
384 tbar.remove(item, false);
385 tbar.insert(newIndex, item);
387 //set the correct x location of each item in the toolbar
388 this.updateButtonXCache();
389 for (index = 0; index < length; index++) {
390 var obj = items[index],
391 newX = this.buttonXCache[obj.id];
394 item.dd.startPosition[0] = newX;
396 var el = obj.getEl();
398 el.moveTo(newX, el.getY(), {
399 duration: this.animationDuration
406 onMovedRight: function(item, newIndex, oldIndex) {
407 this.onMovedLeft.apply(this, arguments);
412 * Updates the internal cache of button X locations.
414 updateButtonXCache: function() {
415 var tbar = this.target,
417 totalX = tbar.getEl().getBox(true).x;
419 items.each(function(item) {
420 this.buttonXCache[item.id] = totalX;
422 totalX += item.getEl().getWidth();