Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / examples / ux / form / ItemSelector.js
1 /*
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.
5  */
6
7 /**
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.
11  *
12  *  @history
13  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
14  *
15  * @constructor
16  * Create a new ItemSelector
17  * @param {Object} config Configuration options
18  * @xtype itemselector 
19  */
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'],
25     
26     hideNavIcons:false,
27
28     /**
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.
33      */
34     buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
35
36     buttonsText: {
37         top: "Move to Top",
38         up: "Move Up",
39         add: "Add to Selected",
40         remove: "Remove from Selected",
41         down: "Move Down",
42         bottom: "Move to Bottom"
43     },
44
45     /**
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.
48      */
49     multiselects: [],
50
51     componentLayout: 'itemselectorfield',
52
53     fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',
54
55
56     bindStore: function(store, initial) {
57         var me = this,
58             toField = me.toField,
59             fromField = me.fromField,
60             models;
61
62         me.callParent(arguments);
63
64         if (toField) {
65             // Clear both field stores
66             toField.store.removeAll();
67             fromField.store.removeAll();
68
69             // Clone the contents of the main store into the fromField
70             models = [];
71             me.store.each(function(model) {
72                 models.push(model.copy(model.getId()));
73             });
74             fromField.store.add(models);
75         }
76     },
77
78     onRender: function(ct, position) {
79         var me = this,
80             baseCSSPrefix = Ext.baseCSSPrefix,
81             ddGroup = 'ItemSelectorDD-' + Ext.id(),
82             commonConfig = {
83                 displayField: me.displayField,
84                 valueField: me.valueField,
85                 dragGroup: ddGroup,
86                 dropGroup: ddGroup,
87                 flex: 1,
88                 hideLabel: true
89             },
90             fromConfig = Ext.apply({
91                 listTitle: 'Available',
92                 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
93                 listeners: {
94                     boundList: {
95                         itemdblclick: me.onItemDblClick,
96                         scope: me
97                     }
98                 }
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
103                 listeners: {
104                     boundList: {
105                         itemdblclick: me.onItemDblClick,
106                         scope: me
107                     },
108                     change: me.onToFieldChange,
109                     scope: me
110                 }
111             }, me.multiselects[1], commonConfig),
112             fromField = Ext.widget('multiselect', fromConfig),
113             toField = Ext.widget('multiselect', toConfig),
114             innerCt,
115             buttons = [];
116
117         // Skip MultiSelect's onRender as we don't want its content
118         Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
119
120         me.fromField = fromField;
121         me.toField = toField;
122
123         if (!me.hideNavIcons) {
124             Ext.Array.forEach(me.buttons, function(name) {
125                 buttons.push({
126                     xtype: 'button',
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,
131                     scope: me
132                 });
133                 //div separator to force vertical stacking
134                 buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
135             });
136         }
137
138         innerCt = me.innerCt = Ext.widget('container', {
139             renderTo: me.bodyEl,
140             layout: {
141                 type: 'hbox',
142                 align: 'middle'
143             },
144             items: [
145                 me.fromField,
146                 {
147                     xtype: 'container',
148                     margins: '0 4',
149                     items: buttons
150                 },
151                 me.toField
152             ]
153         });
154
155         // Must set upward link after first render
156         innerCt.ownerCt = me;
157
158         // Rebind the store so it gets cloned to the fromField
159         me.bindStore(me.store);
160
161         // Set the initial value
162         me.setRawValue(me.rawValue);
163     },
164     
165     onToFieldChange: function() {
166         this.checkChange();
167     },
168     
169     getSelections: function(list){
170         var store = list.getStore(),
171             selections = list.getSelectionModel().getSelection(),
172             i = 0,
173             len = selections.length;
174             
175         return Ext.Array.sort(selections, function(a, b){
176             a = store.indexOf(a);
177             b = store.indexOf(b);
178             
179             if (a < b) {
180                 return -1;
181             } else if (a > b) {
182                 return 1;
183             }
184             return 0;
185         });
186     },
187
188     onTopBtnClick : function() {
189         var list = this.toField.boundList,
190             store = list.getStore(),
191             selected = this.getSelections(list),
192             i = selected.length - 1,
193             selection;
194         
195         
196         store.suspendEvents();
197         for (; i > -1; --i) {
198             selection = selected[i];
199             store.remove(selected);
200             store.insert(0, selected);
201         }
202         store.resumeEvents();
203         list.refresh();    
204     },
205
206     onBottomBtnClick : function() {
207         var list = this.toField.boundList,
208             store = list.getStore(),
209             selected = this.getSelections(list),
210             i = 0,
211             len = selected.length,
212             selection;
213             
214         store.suspendEvents();
215         for (; i < len; ++i) {
216             selection = selected[i];
217             store.remove(selection);
218             store.add(selection);
219         }
220         store.resumeEvents();
221         list.refresh();
222     },
223
224     onUpBtnClick : function() {
225         var list = this.toField.boundList,
226             store = list.getStore(),
227             selected = this.getSelections(list),
228             i = 0,
229             len = selected.length,
230             selection,
231             index;
232             
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);
239         }
240         store.resumeEvents();
241         list.refresh();
242     },
243
244     onDownBtnClick : function() {
245         var list = this.toField.boundList,
246             store = list.getStore(),
247             selected = this.getSelections(list),
248             i = 0,
249             len = selected.length,
250             max = store.getCount(),
251             selection,
252             index;
253             
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);
260         }
261         store.resumeEvents();
262         list.refresh();
263     },
264
265     onAddBtnClick : function() {
266         var me = this,
267             fromList = me.fromField.boundList,
268             selected = this.getSelections(fromList);
269             
270         fromList.getStore().remove(selected);
271         this.toField.boundList.getStore().add(selected);
272     },
273
274     onRemoveBtnClick : function() {
275         var me = this,
276             toList = me.toField.boundList,
277             selected = this.getSelections(toList);
278             
279         toList.getStore().remove(selected);
280         this.fromField.boundList.getStore().add(selected);
281     },
282
283     onItemDblClick : function(view) {
284         var me = this;
285         if (view == me.toField.boundList){
286             me.onRemoveBtnClick();
287         }
288         else if (view == me.fromField.boundList) {
289             me.onAddBtnClick();
290         }
291     },
292
293     setRawValue: function(value) {
294         var me = this,
295             Array = Ext.Array,
296             toStore, fromStore, models;
297
298         value = Array.from(value);
299         me.rawValue = value;
300
301         if (me.toField) {
302             toStore = me.toField.boundList.getStore();
303             fromStore = me.fromField.boundList.getStore();
304
305             // Move any selected values back to the fromField
306             fromStore.add(toStore.getRange());
307             toStore.removeAll();
308
309             // Move the new values over to the toField
310             models = [];
311             Ext.Array.forEach(value, function(val) {
312                 var undef,
313                     model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
314                 if (model) {
315                     models.push(model);
316                 }
317             });
318             fromStore.remove(models);
319             toStore.add(models);
320         }
321
322         return value;
323     },
324
325     getRawValue: function() {
326         var me = this,
327             toField = me.toField,
328             rawValue = me.rawValue;
329
330         if (toField) {
331             rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
332                 return model.get(me.valueField);
333             });
334         }
335
336         me.rawValue = rawValue;
337         return rawValue;
338     },
339
340     /**
341      * @private Cascade readOnly/disabled state to the sub-fields and buttons
342      */
343     updateReadOnly: function() {
344         var me = this,
345             readOnly = me.readOnly || me.disabled;
346
347         if (me.rendered) {
348             me.toField.setReadOnly(readOnly);
349             me.fromField.setReadOnly(readOnly);
350             Ext.Array.forEach(me.innerCt.query('button'), function(button) {
351                 button.setDisabled(readOnly);
352             });
353         }
354     },
355
356     onDestroy: function() {
357         Ext.destroyMembers(this, 'innerCt');
358         this.callParent();
359     }
360
361 });