439 lines
No EOL
16 KiB
JavaScript
439 lines
No EOL
16 KiB
JavaScript
/*
|
|
This file is part of Ext JS 3.4
|
|
|
|
Copyright (c) 2011-2013 Sencha Inc
|
|
|
|
Contact: http://www.sencha.com/contact
|
|
|
|
GNU General Public License Usage
|
|
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.
|
|
|
|
If you are unsure which license is appropriate for your use, please contact the sales department
|
|
at http://www.sencha.com/contact.
|
|
|
|
Build date: 2013-04-03 15:07:25
|
|
*/
|
|
/**
|
|
* @class Ext.ux.Reorderer
|
|
* @extends Object
|
|
* Generic base class for handling reordering of items. This base class must be extended to provide the
|
|
* actual reordering functionality - the base class just sets up events and abstract logic functions.
|
|
* It will fire events and set defaults, deferring the actual reordering to a doReorder implementation.
|
|
* See Ext.ux.TabReorderer for an example.
|
|
*/
|
|
Ext.ux.Reorderer = Ext.extend(Object, {
|
|
/**
|
|
* @property defaults
|
|
* @type Object
|
|
* Object containing default values for plugin configuration details. These can be overridden when
|
|
* constructing the plugin
|
|
*/
|
|
defaults: {
|
|
/**
|
|
* @cfg animate
|
|
* @type Boolean
|
|
* If set to true, the rearranging of the toolbar items is animated
|
|
*/
|
|
animate: true,
|
|
|
|
/**
|
|
* @cfg animationDuration
|
|
* @type Number
|
|
* The duration of the animation used to move other toolbar items out of the way
|
|
*/
|
|
animationDuration: 0.2,
|
|
|
|
/**
|
|
* @cfg defaultReorderable
|
|
* @type Boolean
|
|
* True to make every toolbar draggable unless reorderable is specifically set to false.
|
|
* This defaults to false
|
|
*/
|
|
defaultReorderable: false
|
|
},
|
|
|
|
/**
|
|
* Creates the plugin instance, applies defaults
|
|
* @constructor
|
|
* @param {Object} config Optional config object
|
|
*/
|
|
constructor: function(config) {
|
|
Ext.apply(this, config || {}, this.defaults);
|
|
},
|
|
|
|
/**
|
|
* Initializes the plugin, stores a reference to the target
|
|
* @param {Mixed} target The target component which contains the reorderable items
|
|
*/
|
|
init: function(target) {
|
|
/**
|
|
* @property target
|
|
* @type Ext.Component
|
|
* Reference to the target component which contains the reorderable items
|
|
*/
|
|
this.target = target;
|
|
|
|
this.initEvents();
|
|
|
|
var items = this.getItems(),
|
|
length = items.length,
|
|
i;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
this.createIfReorderable(items[i]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Reorders the items in the target component according to the given mapping object. Example:
|
|
* this.reorder({
|
|
* 1: 5,
|
|
* 3: 2
|
|
* });
|
|
* Would move the item at index 1 to index 5, and the item at index 3 to index 2
|
|
* @param {Object} mappings Object containing current item index as key and new index as property
|
|
*/
|
|
reorder: function(mappings) {
|
|
var target = this.target;
|
|
|
|
if (target.fireEvent('before-reorder', mappings, target, this) !== false) {
|
|
this.doReorder(mappings);
|
|
|
|
target.fireEvent('reorder', mappings, target, this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Abstract function to perform the actual reordering. This MUST be overridden in a subclass
|
|
* @param {Object} mappings Mappings of the old item indexes to new item indexes
|
|
*/
|
|
doReorder: function(paramName) {
|
|
throw new Error("doReorder must be implemented in the Ext.ux.Reorderer subclass");
|
|
},
|
|
|
|
/**
|
|
* Should create and return an Ext.dd.DD for the given item. This MUST be overridden in a subclass
|
|
* @param {Mixed} item The item to create a DD for. This could be a TabPanel tab, a Toolbar button, etc
|
|
* @return {Ext.dd.DD} The DD for the given item
|
|
*/
|
|
createItemDD: function(item) {
|
|
throw new Error("createItemDD must be implemented in the Ext.ux.Reorderer subclass");
|
|
},
|
|
|
|
/**
|
|
* Sets up the given Toolbar item as a draggable
|
|
* @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
|
|
*/
|
|
createItemDD: function(button) {
|
|
var el = button.getEl(),
|
|
id = el.id,
|
|
tbar = this.target,
|
|
me = this;
|
|
|
|
button.dd = new Ext.dd.DD(el, undefined, {
|
|
isTarget: false
|
|
});
|
|
|
|
button.dd.constrainTo(tbar.getEl());
|
|
button.dd.setYConstraint(0, 0, 0);
|
|
|
|
Ext.apply(button.dd, {
|
|
b4StartDrag: function() {
|
|
this.startPosition = el.getXY();
|
|
|
|
//bump up the z index of the button being dragged but keep a reference to the original
|
|
this.startZIndex = el.getStyle('zIndex');
|
|
el.setStyle('zIndex', 10000);
|
|
|
|
button.suspendEvents();
|
|
},
|
|
|
|
onDrag: function(e) {
|
|
//calculate the button's index within the toolbar and its current midpoint
|
|
var buttonX = el.getXY()[0],
|
|
deltaX = buttonX - this.startPosition[0],
|
|
items = tbar.items.items,
|
|
oldIndex = items.indexOf(button),
|
|
newIndex;
|
|
|
|
//find which item in the toolbar the midpoint is currently over
|
|
for (var index = 0; index < items.length; index++) {
|
|
var item = items[index];
|
|
|
|
if (item.reorderable && item.id != button.id) {
|
|
//find the midpoint of the button
|
|
var box = item.getEl().getBox(),
|
|
midpoint = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
|
|
movedLeft = oldIndex > index && deltaX < 0 && buttonX < midpoint,
|
|
movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;
|
|
|
|
if (movedLeft || movedRight) {
|
|
me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* After the drag has been completed, make sure the button being dragged makes it back to
|
|
* the correct location and resets its z index
|
|
*/
|
|
endDrag: function() {
|
|
//we need to update the cache here for cases where the button was dragged but its
|
|
//position in the toolbar did not change
|
|
me.updateButtonXCache();
|
|
|
|
el.moveTo(me.buttonXCache[button.id], undefined, {
|
|
duration: me.animationDuration,
|
|
scope : this,
|
|
callback: function() {
|
|
button.resumeEvents();
|
|
|
|
tbar.fireEvent('reordered', button, tbar);
|
|
}
|
|
});
|
|
|
|
el.setStyle('zIndex', this.startZIndex);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Creates a DD instance for a given item if it is reorderable
|
|
* @param {Mixed} item The item
|
|
*/
|
|
createIfReorderable: function(item) {
|
|
if (this.defaultReorderable && item.reorderable == undefined) {
|
|
item.reorderable = true;
|
|
}
|
|
|
|
if (item.reorderable && !item.dd) {
|
|
if (item.rendered) {
|
|
this.createItemDD(item);
|
|
} else {
|
|
item.on('render', this.createItemDD.createDelegate(this, [item]), this, {single: true});
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns an array of items which will be made draggable. This defaults to the contents of this.target.items,
|
|
* but can be overridden - e.g. for TabPanels
|
|
* @return {Array} The array of items which will be made draggable
|
|
*/
|
|
getItems: function() {
|
|
return this.target.items.items;
|
|
},
|
|
|
|
/**
|
|
* Adds before-reorder and reorder events to the target component
|
|
*/
|
|
initEvents: function() {
|
|
this.target.addEvents(
|
|
/**
|
|
* @event before-reorder
|
|
* Fires before a reorder occurs. Return false to cancel
|
|
* @param {Object} mappings Mappings of the old item indexes to new item indexes
|
|
* @param {Mixed} component The target component
|
|
* @param {Ext.ux.TabReorderer} this The plugin instance
|
|
*/
|
|
'before-reorder',
|
|
|
|
/**
|
|
* @event reorder
|
|
* Fires after a reorder has occured.
|
|
* @param {Object} mappings Mappings of the old item indexes to the new item indexes
|
|
* @param {Mixed} component The target component
|
|
* @param {Ext.ux.TabReorderer} this The plugin instance
|
|
*/
|
|
'reorder'
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @class Ext.ux.HBoxReorderer
|
|
* @extends Ext.ux.Reorderer
|
|
* Description
|
|
*/
|
|
Ext.ux.HBoxReorderer = Ext.extend(Ext.ux.Reorderer, {
|
|
/**
|
|
* Initializes the plugin, decorates the container with additional functionality
|
|
*/
|
|
init: function(container) {
|
|
/**
|
|
* This is used to store the correct x value of each button in the array. We need to use this
|
|
* instead of the button's reported x co-ordinate because the buttons are animated when they move -
|
|
* if another onDrag is fired while the button is still moving, the comparison x value will be incorrect
|
|
*/
|
|
this.buttonXCache = {};
|
|
|
|
container.on({
|
|
scope: this,
|
|
add : function(container, item) {
|
|
this.createIfReorderable(item);
|
|
}
|
|
});
|
|
|
|
//super sets a reference to the toolbar in this.target
|
|
Ext.ux.HBoxReorderer.superclass.init.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Sets up the given Toolbar item as a draggable
|
|
* @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
|
|
*/
|
|
createItemDD: function(button) {
|
|
if (button.dd != undefined) {
|
|
return;
|
|
}
|
|
|
|
var el = button.getEl(),
|
|
id = el.id,
|
|
me = this,
|
|
tbar = me.target;
|
|
|
|
button.dd = new Ext.dd.DD(el, undefined, {
|
|
isTarget: false
|
|
});
|
|
|
|
el.applyStyles({
|
|
position: 'absolute'
|
|
});
|
|
|
|
//if a button has a menu, it is disabled while dragging with this function
|
|
var menuDisabler = function() {
|
|
return false;
|
|
};
|
|
|
|
Ext.apply(button.dd, {
|
|
b4StartDrag: function() {
|
|
this.startPosition = el.getXY();
|
|
|
|
//bump up the z index of the button being dragged but keep a reference to the original
|
|
this.startZIndex = el.getStyle('zIndex');
|
|
el.setStyle('zIndex', 10000);
|
|
|
|
button.suspendEvents();
|
|
if (button.menu) {
|
|
button.menu.on('beforeshow', menuDisabler, me);
|
|
}
|
|
},
|
|
|
|
startDrag: function() {
|
|
this.constrainTo(tbar.getEl());
|
|
this.setYConstraint(0, 0, 0);
|
|
},
|
|
|
|
onDrag: function(e) {
|
|
//calculate the button's index within the toolbar and its current midpoint
|
|
var buttonX = el.getXY()[0],
|
|
deltaX = buttonX - this.startPosition[0],
|
|
items = tbar.items.items,
|
|
length = items.length,
|
|
oldIndex = items.indexOf(button),
|
|
newIndex, index, item;
|
|
|
|
//find which item in the toolbar the midpoint is currently over
|
|
for (index = 0; index < length; index++) {
|
|
item = items[index];
|
|
|
|
if (item.reorderable && item.id != button.id) {
|
|
//find the midpoint of the button
|
|
var box = item.getEl().getBox(),
|
|
midpoint = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
|
|
movedLeft = oldIndex > index && deltaX < 0 && buttonX < midpoint,
|
|
movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;
|
|
|
|
if (movedLeft || movedRight) {
|
|
me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* After the drag has been completed, make sure the button being dragged makes it back to
|
|
* the correct location and resets its z index
|
|
*/
|
|
endDrag: function() {
|
|
//we need to update the cache here for cases where the button was dragged but its
|
|
//position in the toolbar did not change
|
|
me.updateButtonXCache();
|
|
|
|
el.moveTo(me.buttonXCache[button.id], el.getY(), {
|
|
duration: me.animationDuration,
|
|
scope : this,
|
|
callback: function() {
|
|
button.resumeEvents();
|
|
if (button.menu) {
|
|
button.menu.un('beforeshow', menuDisabler, me);
|
|
}
|
|
|
|
tbar.fireEvent('reordered', button, tbar);
|
|
}
|
|
});
|
|
|
|
el.setStyle('zIndex', this.startZIndex);
|
|
}
|
|
});
|
|
},
|
|
|
|
onMovedLeft: function(item, newIndex, oldIndex) {
|
|
var tbar = this.target,
|
|
items = tbar.items.items,
|
|
length = items.length,
|
|
index;
|
|
|
|
if (newIndex != undefined && newIndex != oldIndex) {
|
|
//move the button currently under drag to its new location
|
|
tbar.remove(item, false);
|
|
tbar.insert(newIndex, item);
|
|
|
|
//set the correct x location of each item in the toolbar
|
|
this.updateButtonXCache();
|
|
for (index = 0; index < length; index++) {
|
|
var obj = items[index],
|
|
newX = this.buttonXCache[obj.id];
|
|
|
|
if (item == obj) {
|
|
item.dd.startPosition[0] = newX;
|
|
} else {
|
|
var el = obj.getEl();
|
|
|
|
el.moveTo(newX, el.getY(), {
|
|
duration: this.animationDuration
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
onMovedRight: function(item, newIndex, oldIndex) {
|
|
this.onMovedLeft.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Updates the internal cache of button X locations.
|
|
*/
|
|
updateButtonXCache: function() {
|
|
var tbar = this.target,
|
|
items = tbar.items,
|
|
totalX = tbar.getEl().getBox(true).x;
|
|
|
|
items.each(function(item) {
|
|
this.buttonXCache[item.id] = totalX;
|
|
|
|
totalX += item.getEl().getWidth();
|
|
}, this);
|
|
}
|
|
}); |