--- /dev/null
+/*
+ * Note that this control will most likely remain as an example, and not as a core Ext form
+ * control. However, the API will be changing in a future release and so should not yet be
+ * treated as a final, stable API at this time.
+ */
+
+/**
+ * @class Ext.ux.form.ItemSelector
+ * @extends Ext.form.field.Base
+ * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
+ *
+ * @history
+ * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
+ *
+ * @constructor
+ * Create a new ItemSelector
+ * @param {Object} config Configuration options
+ * @xtype itemselector
+ */
+Ext.define('Ext.ux.form.ItemSelector', {
+ extend: 'Ext.ux.form.MultiSelect',
+ alias: ['widget.itemselectorfield', 'widget.itemselector'],
+ alternateClassName: ['Ext.ux.ItemSelector'],
+ requires: ['Ext.ux.layout.component.form.ItemSelector', 'Ext.button.Button'],
+
+ hideNavIcons:false,
+
+ /**
+ * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
+ * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
+ * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
+ * This can be overridden with a custom Array to change which buttons are displayed or their order.
+ */
+ buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
+
+ buttonsText: {
+ top: "Move to Top",
+ up: "Move Up",
+ add: "Add to Selected",
+ remove: "Remove from Selected",
+ down: "Move Down",
+ bottom: "Move to Bottom"
+ },
+
+ /**
+ * @cfg {Array} multiselects An optional array of {@link Ext.ux.form.MultiSelect} config objects, containing
+ * additional configuration to be applied to the internal MultiSelect fields.
+ */
+ multiselects: [],
+
+ componentLayout: 'itemselectorfield',
+
+ fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',
+
+
+ bindStore: function(store, initial) {
+ var me = this,
+ toField = me.toField,
+ fromField = me.fromField,
+ models;
+
+ me.callParent(arguments);
+
+ if (toField) {
+ // Clear both field stores
+ toField.store.removeAll();
+ fromField.store.removeAll();
+
+ // Clone the contents of the main store into the fromField
+ models = [];
+ me.store.each(function(model) {
+ models.push(model.copy(model.getId()));
+ });
+ fromField.store.add(models);
+ }
+ },
+
+ onRender: function(ct, position) {
+ var me = this,
+ baseCSSPrefix = Ext.baseCSSPrefix,
+ ddGroup = 'ItemSelectorDD-' + Ext.id(),
+ commonConfig = {
+ displayField: me.displayField,
+ valueField: me.valueField,
+ dragGroup: ddGroup,
+ dropGroup: ddGroup,
+ flex: 1,
+ hideLabel: true
+ },
+ fromConfig = Ext.apply({
+ listTitle: 'Available',
+ store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
+ listeners: {
+ boundList: {
+ itemdblclick: me.onItemDblClick,
+ scope: me
+ }
+ }
+ }, me.multiselects[0], commonConfig),
+ toConfig = Ext.apply({
+ listTitle: 'Selected',
+ store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
+ listeners: {
+ boundList: {
+ itemdblclick: me.onItemDblClick,
+ scope: me
+ },
+ change: me.onToFieldChange,
+ scope: me
+ }
+ }, me.multiselects[1], commonConfig),
+ fromField = Ext.widget('multiselect', fromConfig),
+ toField = Ext.widget('multiselect', toConfig),
+ innerCt,
+ buttons = [];
+
+ // Skip MultiSelect's onRender as we don't want its content
+ Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
+
+ me.fromField = fromField;
+ me.toField = toField;
+
+ if (!me.hideNavIcons) {
+ Ext.Array.forEach(me.buttons, function(name) {
+ buttons.push({
+ xtype: 'button',
+ tooltip: me.buttonsText[name],
+ handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
+ cls: baseCSSPrefix + 'form-itemselector-btn',
+ iconCls: baseCSSPrefix + 'form-itemselector-' + name,
+ scope: me
+ });
+ //div separator to force vertical stacking
+ buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
+ });
+ }
+
+ innerCt = me.innerCt = Ext.widget('container', {
+ renderTo: me.bodyEl,
+ layout: {
+ type: 'hbox',
+ align: 'middle'
+ },
+ items: [
+ me.fromField,
+ {
+ xtype: 'container',
+ margins: '0 4',
+ items: buttons
+ },
+ me.toField
+ ]
+ });
+
+ // Must set upward link after first render
+ innerCt.ownerCt = me;
+
+ // Rebind the store so it gets cloned to the fromField
+ me.bindStore(me.store);
+
+ // Set the initial value
+ me.setRawValue(me.rawValue);
+ },
+
+ onToFieldChange: function() {
+ this.checkChange();
+ },
+
+ getSelections: function(list){
+ var store = list.getStore(),
+ selections = list.getSelectionModel().getSelection(),
+ i = 0,
+ len = selections.length;
+
+ return Ext.Array.sort(selections, function(a, b){
+ a = store.indexOf(a);
+ b = store.indexOf(b);
+
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+ return 0;
+ });
+ },
+
+ onTopBtnClick : function() {
+ var list = this.toField.boundList,
+ store = list.getStore(),
+ selected = this.getSelections(list),
+ i = selected.length - 1,
+ selection;
+
+
+ store.suspendEvents();
+ for (; i > -1; --i) {
+ selection = selected[i];
+ store.remove(selected);
+ store.insert(0, selected);
+ }
+ store.resumeEvents();
+ list.refresh();
+ },
+
+ onBottomBtnClick : function() {
+ var list = this.toField.boundList,
+ store = list.getStore(),
+ selected = this.getSelections(list),
+ i = 0,
+ len = selected.length,
+ selection;
+
+ store.suspendEvents();
+ for (; i < len; ++i) {
+ selection = selected[i];
+ store.remove(selection);
+ store.add(selection);
+ }
+ store.resumeEvents();
+ list.refresh();
+ },
+
+ onUpBtnClick : function() {
+ var list = this.toField.boundList,
+ store = list.getStore(),
+ selected = this.getSelections(list),
+ i = 0,
+ len = selected.length,
+ selection,
+ index;
+
+ store.suspendEvents();
+ for (; i < len; ++i) {
+ selection = selected[i];
+ index = Math.max(0, store.indexOf(selection) - 1);
+ store.remove(selection);
+ store.insert(index, selection);
+ }
+ store.resumeEvents();
+ list.refresh();
+ },
+
+ onDownBtnClick : function() {
+ var list = this.toField.boundList,
+ store = list.getStore(),
+ selected = this.getSelections(list),
+ i = 0,
+ len = selected.length,
+ max = store.getCount(),
+ selection,
+ index;
+
+ store.suspendEvents();
+ for (; i < len; ++i) {
+ selection = selected[i];
+ index = Math.min(max, store.indexOf(selection) + 1);
+ store.remove(selection);
+ store.insert(index, selection);
+ }
+ store.resumeEvents();
+ list.refresh();
+ },
+
+ onAddBtnClick : function() {
+ var me = this,
+ fromList = me.fromField.boundList,
+ selected = this.getSelections(fromList);
+
+ fromList.getStore().remove(selected);
+ this.toField.boundList.getStore().add(selected);
+ },
+
+ onRemoveBtnClick : function() {
+ var me = this,
+ toList = me.toField.boundList,
+ selected = this.getSelections(toList);
+
+ toList.getStore().remove(selected);
+ this.fromField.boundList.getStore().add(selected);
+ },
+
+ onItemDblClick : function(view) {
+ var me = this;
+ if (view == me.toField.boundList){
+ me.onRemoveBtnClick();
+ }
+ else if (view == me.fromField.boundList) {
+ me.onAddBtnClick();
+ }
+ },
+
+ setRawValue: function(value) {
+ var me = this,
+ Array = Ext.Array,
+ toStore, fromStore, models;
+
+ value = Array.from(value);
+ me.rawValue = value;
+
+ if (me.toField) {
+ toStore = me.toField.boundList.getStore();
+ fromStore = me.fromField.boundList.getStore();
+
+ // Move any selected values back to the fromField
+ fromStore.add(toStore.getRange());
+ toStore.removeAll();
+
+ // Move the new values over to the toField
+ models = [];
+ Ext.Array.forEach(value, function(val) {
+ var undef,
+ model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
+ if (model) {
+ models.push(model);
+ }
+ });
+ fromStore.remove(models);
+ toStore.add(models);
+ }
+
+ return value;
+ },
+
+ getRawValue: function() {
+ var me = this,
+ toField = me.toField,
+ rawValue = me.rawValue;
+
+ if (toField) {
+ rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
+ return model.get(me.valueField);
+ });
+ }
+
+ me.rawValue = rawValue;
+ return rawValue;
+ },
+
+ /**
+ * @private Cascade readOnly/disabled state to the sub-fields and buttons
+ */
+ updateReadOnly: function() {
+ var me = this,
+ readOnly = me.readOnly || me.disabled;
+
+ if (me.rendered) {
+ me.toField.setReadOnly(readOnly);
+ me.fromField.setReadOnly(readOnly);
+ Ext.Array.forEach(me.innerCt.query('button'), function(button) {
+ button.setDisabled(readOnly);
+ });
+ }
+ },
+
+ onDestroy: function() {
+ Ext.destroyMembers(this, 'innerCt');
+ this.callParent();
+ }
+
+});