Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / examples / ux / form / ItemSelector.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /*
16  * Note that this control will most likely remain as an example, and not as a core Ext form
17  * control.  However, the API will be changing in a future release and so should not yet be
18  * treated as a final, stable API at this time.
19  */
20
21 /**
22  * @class Ext.ux.form.ItemSelector
23  * @extends Ext.form.field.Base
24  * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
25  *
26  *  @history
27  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
28  *
29  * @constructor
30  * Create a new ItemSelector
31  * @param {Object} config Configuration options
32  * @xtype itemselector 
33  */
34 Ext.define('Ext.ux.form.ItemSelector', {
35     extend: 'Ext.ux.form.MultiSelect',
36     alias: ['widget.itemselectorfield', 'widget.itemselector'],
37     alternateClassName: ['Ext.ux.ItemSelector'],
38     requires: ['Ext.ux.layout.component.form.ItemSelector', 'Ext.button.Button'],
39     
40     hideNavIcons:false,
41
42     /**
43      * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
44      * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
45      * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
46      * This can be overridden with a custom Array to change which buttons are displayed or their order.
47      */
48     buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
49
50     buttonsText: {
51         top: "Move to Top",
52         up: "Move Up",
53         add: "Add to Selected",
54         remove: "Remove from Selected",
55         down: "Move Down",
56         bottom: "Move to Bottom"
57     },
58
59     /**
60      * @cfg {Array} multiselects An optional array of {@link Ext.ux.form.MultiSelect} config objects, containing
61      * additional configuration to be applied to the internal MultiSelect fields.
62      */
63     multiselects: [],
64
65     componentLayout: 'itemselectorfield',
66
67     fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',
68
69
70     bindStore: function(store, initial) {
71         var me = this,
72             toField = me.toField,
73             fromField = me.fromField,
74             models;
75
76         me.callParent(arguments);
77
78         if (toField) {
79             // Clear both field stores
80             toField.store.removeAll();
81             fromField.store.removeAll();
82
83             // Clone the contents of the main store into the fromField
84             models = [];
85             me.store.each(function(model) {
86                 models.push(model.copy(model.getId()));
87             });
88             fromField.store.add(models);
89         }
90     },
91
92     onRender: function(ct, position) {
93         var me = this,
94             baseCSSPrefix = Ext.baseCSSPrefix,
95             ddGroup = 'ItemSelectorDD-' + Ext.id(),
96             commonConfig = {
97                 displayField: me.displayField,
98                 valueField: me.valueField,
99                 dragGroup: ddGroup,
100                 dropGroup: ddGroup,
101                 flex: 1,
102                 hideLabel: true,
103                 disabled: me.disabled
104             },
105             fromConfig = Ext.apply({
106                 listTitle: 'Available',
107                 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
108                 listeners: {
109                     boundList: {
110                         itemdblclick: me.onItemDblClick,
111                         scope: me
112                     }
113                 }
114             }, me.multiselects[0], commonConfig),
115             toConfig = Ext.apply({
116                 listTitle: 'Selected',
117                 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
118                 listeners: {
119                     boundList: {
120                         itemdblclick: me.onItemDblClick,
121                         scope: me
122                     },
123                     change: me.onToFieldChange,
124                     scope: me
125                 }
126             }, me.multiselects[1], commonConfig),
127             fromField = Ext.widget('multiselect', fromConfig),
128             toField = Ext.widget('multiselect', toConfig),
129             innerCt,
130             buttons = [];
131
132         // Skip MultiSelect's onRender as we don't want its content
133         Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
134
135         me.fromField = fromField;
136         me.toField = toField;
137
138         if (!me.hideNavIcons) {
139             Ext.Array.forEach(me.buttons, function(name) {
140                 buttons.push({
141                     xtype: 'button',
142                     tooltip: me.buttonsText[name],
143                     handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
144                     cls: baseCSSPrefix + 'form-itemselector-btn',
145                     iconCls: baseCSSPrefix + 'form-itemselector-' + name,
146                     scope: me
147                 });
148                 //div separator to force vertical stacking
149                 buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
150             });
151         }
152
153         innerCt = me.innerCt = Ext.widget('container', {
154             renderTo: me.bodyEl,
155             layout: {
156                 type: 'hbox',
157                 align: 'middle'
158             },
159             items: [
160                 me.fromField,
161                 {
162                     xtype: 'container',
163                     margins: '0 4',
164                     items: buttons
165                 },
166                 me.toField
167             ]
168         });
169
170         // Must set upward link after first render
171         innerCt.ownerCt = me;
172
173         // Rebind the store so it gets cloned to the fromField
174         me.bindStore(me.store);
175
176         // Set the initial value
177         me.setRawValue(me.rawValue);
178     },
179     
180     onToFieldChange: function() {
181         this.checkChange();
182     },
183     
184     getSelections: function(list){
185         var store = list.getStore(),
186             selections = list.getSelectionModel().getSelection(),
187             i = 0,
188             len = selections.length;
189             
190         return Ext.Array.sort(selections, function(a, b){
191             a = store.indexOf(a);
192             b = store.indexOf(b);
193             
194             if (a < b) {
195                 return -1;
196             } else if (a > b) {
197                 return 1;
198             }
199             return 0;
200         });
201     },
202
203     onTopBtnClick : function() {
204         var list = this.toField.boundList,
205             store = list.getStore(),
206             selected = this.getSelections(list),
207             i = selected.length - 1,
208             selection;
209         
210         
211         store.suspendEvents();
212         for (; i > -1; --i) {
213             selection = selected[i];
214             store.remove(selected);
215             store.insert(0, selected);
216         }
217         store.resumeEvents();
218         list.refresh();    
219     },
220
221     onBottomBtnClick : function() {
222         var list = this.toField.boundList,
223             store = list.getStore(),
224             selected = this.getSelections(list),
225             i = 0,
226             len = selected.length,
227             selection;
228             
229         store.suspendEvents();
230         for (; i < len; ++i) {
231             selection = selected[i];
232             store.remove(selection);
233             store.add(selection);
234         }
235         store.resumeEvents();
236         list.refresh();
237     },
238
239     onUpBtnClick : function() {
240         var list = this.toField.boundList,
241             store = list.getStore(),
242             selected = this.getSelections(list),
243             i = 0,
244             len = selected.length,
245             selection,
246             index;
247             
248         store.suspendEvents();
249         for (; i < len; ++i) {
250             selection = selected[i];
251             index = Math.max(0, store.indexOf(selection) - 1);
252             store.remove(selection);
253             store.insert(index, selection);
254         }
255         store.resumeEvents();
256         list.refresh();
257     },
258
259     onDownBtnClick : function() {
260         var list = this.toField.boundList,
261             store = list.getStore(),
262             selected = this.getSelections(list),
263             i = 0,
264             len = selected.length,
265             max = store.getCount(),
266             selection,
267             index;
268             
269         store.suspendEvents();
270         for (; i < len; ++i) {
271             selection = selected[i];
272             index = Math.min(max, store.indexOf(selection) + 1);
273             store.remove(selection);
274             store.insert(index, selection);
275         }
276         store.resumeEvents();
277         list.refresh();
278     },
279
280     onAddBtnClick : function() {
281         var me = this,
282             fromList = me.fromField.boundList,
283             selected = this.getSelections(fromList);
284             
285         fromList.getStore().remove(selected);
286         this.toField.boundList.getStore().add(selected);
287     },
288
289     onRemoveBtnClick : function() {
290         var me = this,
291             toList = me.toField.boundList,
292             selected = this.getSelections(toList);
293             
294         toList.getStore().remove(selected);
295         this.fromField.boundList.getStore().add(selected);
296     },
297
298     onItemDblClick : function(view) {
299         var me = this;
300         if (view == me.toField.boundList){
301             me.onRemoveBtnClick();
302         }
303         else if (view == me.fromField.boundList) {
304             me.onAddBtnClick();
305         }
306     },
307
308     setRawValue: function(value) {
309         var me = this,
310             Array = Ext.Array,
311             toStore, fromStore, models;
312
313         value = Array.from(value);
314         me.rawValue = value;
315
316         if (me.toField) {
317             toStore = me.toField.boundList.getStore();
318             fromStore = me.fromField.boundList.getStore();
319
320             // Move any selected values back to the fromField
321             fromStore.add(toStore.getRange());
322             toStore.removeAll();
323
324             // Move the new values over to the toField
325             models = [];
326             Ext.Array.forEach(value, function(val) {
327                 var undef,
328                     model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
329                 if (model) {
330                     models.push(model);
331                 }
332             });
333             fromStore.remove(models);
334             toStore.add(models);
335         }
336
337         return value;
338     },
339
340     getRawValue: function() {
341         var me = this,
342             toField = me.toField,
343             rawValue = me.rawValue;
344
345         if (toField) {
346             rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
347                 return model.get(me.valueField);
348             });
349         }
350
351         me.rawValue = rawValue;
352         return rawValue;
353     },
354
355     /**
356      * @private Cascade readOnly/disabled state to the sub-fields and buttons
357      */
358     updateReadOnly: function() {
359         var me = this,
360             readOnly = me.readOnly || me.disabled;
361
362         if (me.rendered) {
363             me.toField.setReadOnly(readOnly);
364             me.fromField.setReadOnly(readOnly);
365             Ext.Array.forEach(me.innerCt.query('button'), function(button) {
366                 button.setDisabled(readOnly);
367             });
368         }
369     },
370     
371     onDisable: function(){
372         this.callParent();
373         var fromField = this.fromField;
374         
375         // if we have one, we have both, they get created at the same time    
376         if (fromField) {
377             fromField.disable();
378             this.toField.disable();
379         }
380     },
381     
382     onEnable: function(){
383         this.callParent();
384         var fromField = this.fromField;
385         
386         // if we have one, we have both, they get created at the same time    
387         if (fromField) {
388             fromField.enable();
389             this.toField.enable();
390         }
391     },
392
393     onDestroy: function() {
394         Ext.destroyMembers(this, 'innerCt');
395         this.callParent();
396     }
397
398 });
399