2 * Note that this control will most likely remain as an example, and not as a core Ext form
3 * control. However, the API will be changing in a future release and so should not yet be
4 * treated as a final, stable API at this time.
8 * @class Ext.ux.form.ItemSelector
9 * @extends Ext.form.field.Base
10 * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
13 * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
16 * Create a new ItemSelector
17 * @param {Object} config Configuration options
20 Ext.define('Ext.ux.form.ItemSelector', {
21 extend: 'Ext.ux.form.MultiSelect',
22 alias: ['widget.itemselectorfield', 'widget.itemselector'],
23 alternateClassName: ['Ext.ux.ItemSelector'],
24 requires: ['Ext.ux.layout.component.form.ItemSelector', 'Ext.button.Button'],
29 * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
30 * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
31 * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
32 * This can be overridden with a custom Array to change which buttons are displayed or their order.
34 buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
39 add: "Add to Selected",
40 remove: "Remove from Selected",
42 bottom: "Move to Bottom"
46 * @cfg {Array} multiselects An optional array of {@link Ext.ux.form.MultiSelect} config objects, containing
47 * additional configuration to be applied to the internal MultiSelect fields.
51 componentLayout: 'itemselectorfield',
53 fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',
56 bindStore: function(store, initial) {
59 fromField = me.fromField,
62 me.callParent(arguments);
65 // Clear both field stores
66 toField.store.removeAll();
67 fromField.store.removeAll();
69 // Clone the contents of the main store into the fromField
71 me.store.each(function(model) {
72 models.push(model.copy(model.getId()));
74 fromField.store.add(models);
78 onRender: function(ct, position) {
80 baseCSSPrefix = Ext.baseCSSPrefix,
81 ddGroup = 'ItemSelectorDD-' + Ext.id(),
83 displayField: me.displayField,
84 valueField: me.valueField,
90 fromConfig = Ext.apply({
91 listTitle: 'Available',
92 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
95 itemdblclick: me.onItemDblClick,
99 }, me.multiselects[0], commonConfig),
100 toConfig = Ext.apply({
101 listTitle: 'Selected',
102 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
105 itemdblclick: me.onItemDblClick,
108 change: me.onToFieldChange,
111 }, me.multiselects[1], commonConfig),
112 fromField = Ext.widget('multiselect', fromConfig),
113 toField = Ext.widget('multiselect', toConfig),
117 // Skip MultiSelect's onRender as we don't want its content
118 Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
120 me.fromField = fromField;
121 me.toField = toField;
123 if (!me.hideNavIcons) {
124 Ext.Array.forEach(me.buttons, function(name) {
127 tooltip: me.buttonsText[name],
128 handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
129 cls: baseCSSPrefix + 'form-itemselector-btn',
130 iconCls: baseCSSPrefix + 'form-itemselector-' + name,
133 //div separator to force vertical stacking
134 buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
138 innerCt = me.innerCt = Ext.widget('container', {
155 // Must set upward link after first render
156 innerCt.ownerCt = me;
158 // Rebind the store so it gets cloned to the fromField
159 me.bindStore(me.store);
161 // Set the initial value
162 me.setRawValue(me.rawValue);
165 onToFieldChange: function() {
169 getSelections: function(list){
170 var store = list.getStore(),
171 selections = list.getSelectionModel().getSelection(),
173 len = selections.length;
175 return Ext.Array.sort(selections, function(a, b){
176 a = store.indexOf(a);
177 b = store.indexOf(b);
188 onTopBtnClick : function() {
189 var list = this.toField.boundList,
190 store = list.getStore(),
191 selected = this.getSelections(list),
192 i = selected.length - 1,
196 store.suspendEvents();
197 for (; i > -1; --i) {
198 selection = selected[i];
199 store.remove(selected);
200 store.insert(0, selected);
202 store.resumeEvents();
206 onBottomBtnClick : function() {
207 var list = this.toField.boundList,
208 store = list.getStore(),
209 selected = this.getSelections(list),
211 len = selected.length,
214 store.suspendEvents();
215 for (; i < len; ++i) {
216 selection = selected[i];
217 store.remove(selection);
218 store.add(selection);
220 store.resumeEvents();
224 onUpBtnClick : function() {
225 var list = this.toField.boundList,
226 store = list.getStore(),
227 selected = this.getSelections(list),
229 len = selected.length,
233 store.suspendEvents();
234 for (; i < len; ++i) {
235 selection = selected[i];
236 index = Math.max(0, store.indexOf(selection) - 1);
237 store.remove(selection);
238 store.insert(index, selection);
240 store.resumeEvents();
244 onDownBtnClick : function() {
245 var list = this.toField.boundList,
246 store = list.getStore(),
247 selected = this.getSelections(list),
249 len = selected.length,
250 max = store.getCount(),
254 store.suspendEvents();
255 for (; i < len; ++i) {
256 selection = selected[i];
257 index = Math.min(max, store.indexOf(selection) + 1);
258 store.remove(selection);
259 store.insert(index, selection);
261 store.resumeEvents();
265 onAddBtnClick : function() {
267 fromList = me.fromField.boundList,
268 selected = this.getSelections(fromList);
270 fromList.getStore().remove(selected);
271 this.toField.boundList.getStore().add(selected);
274 onRemoveBtnClick : function() {
276 toList = me.toField.boundList,
277 selected = this.getSelections(toList);
279 toList.getStore().remove(selected);
280 this.fromField.boundList.getStore().add(selected);
283 onItemDblClick : function(view) {
285 if (view == me.toField.boundList){
286 me.onRemoveBtnClick();
288 else if (view == me.fromField.boundList) {
293 setRawValue: function(value) {
296 toStore, fromStore, models;
298 value = Array.from(value);
302 toStore = me.toField.boundList.getStore();
303 fromStore = me.fromField.boundList.getStore();
305 // Move any selected values back to the fromField
306 fromStore.add(toStore.getRange());
309 // Move the new values over to the toField
311 Ext.Array.forEach(value, function(val) {
313 model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
318 fromStore.remove(models);
325 getRawValue: function() {
327 toField = me.toField,
328 rawValue = me.rawValue;
331 rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
332 return model.get(me.valueField);
336 me.rawValue = rawValue;
341 * @private Cascade readOnly/disabled state to the sub-fields and buttons
343 updateReadOnly: function() {
345 readOnly = me.readOnly || me.disabled;
348 me.toField.setReadOnly(readOnly);
349 me.fromField.setReadOnly(readOnly);
350 Ext.Array.forEach(me.innerCt.query('button'), function(button) {
351 button.setDisabled(readOnly);
356 onDestroy: function() {
357 Ext.destroyMembers(this, 'innerCt');