Upgrade to ExtJS 4.0.2 - Released 06/09/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             },
104             fromConfig = Ext.apply({
105                 listTitle: 'Available',
106                 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
107                 listeners: {
108                     boundList: {
109                         itemdblclick: me.onItemDblClick,
110                         scope: me
111                     }
112                 }
113             }, me.multiselects[0], commonConfig),
114             toConfig = Ext.apply({
115                 listTitle: 'Selected',
116                 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
117                 listeners: {
118                     boundList: {
119                         itemdblclick: me.onItemDblClick,
120                         scope: me
121                     },
122                     change: me.onToFieldChange,
123                     scope: me
124                 }
125             }, me.multiselects[1], commonConfig),
126             fromField = Ext.widget('multiselect', fromConfig),
127             toField = Ext.widget('multiselect', toConfig),
128             innerCt,
129             buttons = [];
130
131         // Skip MultiSelect's onRender as we don't want its content
132         Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
133
134         me.fromField = fromField;
135         me.toField = toField;
136
137         if (!me.hideNavIcons) {
138             Ext.Array.forEach(me.buttons, function(name) {
139                 buttons.push({
140                     xtype: 'button',
141                     tooltip: me.buttonsText[name],
142                     handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
143                     cls: baseCSSPrefix + 'form-itemselector-btn',
144                     iconCls: baseCSSPrefix + 'form-itemselector-' + name,
145                     scope: me
146                 });
147                 //div separator to force vertical stacking
148                 buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
149             });
150         }
151
152         innerCt = me.innerCt = Ext.widget('container', {
153             renderTo: me.bodyEl,
154             layout: {
155                 type: 'hbox',
156                 align: 'middle'
157             },
158             items: [
159                 me.fromField,
160                 {
161                     xtype: 'container',
162                     margins: '0 4',
163                     items: buttons
164                 },
165                 me.toField
166             ]
167         });
168
169         // Must set upward link after first render
170         innerCt.ownerCt = me;
171
172         // Rebind the store so it gets cloned to the fromField
173         me.bindStore(me.store);
174
175         // Set the initial value
176         me.setRawValue(me.rawValue);
177     },
178     
179     onToFieldChange: function() {
180         this.checkChange();
181     },
182     
183     getSelections: function(list){
184         var store = list.getStore(),
185             selections = list.getSelectionModel().getSelection(),
186             i = 0,
187             len = selections.length;
188             
189         return Ext.Array.sort(selections, function(a, b){
190             a = store.indexOf(a);
191             b = store.indexOf(b);
192             
193             if (a < b) {
194                 return -1;
195             } else if (a > b) {
196                 return 1;
197             }
198             return 0;
199         });
200     },
201
202     onTopBtnClick : function() {
203         var list = this.toField.boundList,
204             store = list.getStore(),
205             selected = this.getSelections(list),
206             i = selected.length - 1,
207             selection;
208         
209         
210         store.suspendEvents();
211         for (; i > -1; --i) {
212             selection = selected[i];
213             store.remove(selected);
214             store.insert(0, selected);
215         }
216         store.resumeEvents();
217         list.refresh();    
218     },
219
220     onBottomBtnClick : function() {
221         var list = this.toField.boundList,
222             store = list.getStore(),
223             selected = this.getSelections(list),
224             i = 0,
225             len = selected.length,
226             selection;
227             
228         store.suspendEvents();
229         for (; i < len; ++i) {
230             selection = selected[i];
231             store.remove(selection);
232             store.add(selection);
233         }
234         store.resumeEvents();
235         list.refresh();
236     },
237
238     onUpBtnClick : function() {
239         var list = this.toField.boundList,
240             store = list.getStore(),
241             selected = this.getSelections(list),
242             i = 0,
243             len = selected.length,
244             selection,
245             index;
246             
247         store.suspendEvents();
248         for (; i < len; ++i) {
249             selection = selected[i];
250             index = Math.max(0, store.indexOf(selection) - 1);
251             store.remove(selection);
252             store.insert(index, selection);
253         }
254         store.resumeEvents();
255         list.refresh();
256     },
257
258     onDownBtnClick : function() {
259         var list = this.toField.boundList,
260             store = list.getStore(),
261             selected = this.getSelections(list),
262             i = 0,
263             len = selected.length,
264             max = store.getCount(),
265             selection,
266             index;
267             
268         store.suspendEvents();
269         for (; i < len; ++i) {
270             selection = selected[i];
271             index = Math.min(max, store.indexOf(selection) + 1);
272             store.remove(selection);
273             store.insert(index, selection);
274         }
275         store.resumeEvents();
276         list.refresh();
277     },
278
279     onAddBtnClick : function() {
280         var me = this,
281             fromList = me.fromField.boundList,
282             selected = this.getSelections(fromList);
283             
284         fromList.getStore().remove(selected);
285         this.toField.boundList.getStore().add(selected);
286     },
287
288     onRemoveBtnClick : function() {
289         var me = this,
290             toList = me.toField.boundList,
291             selected = this.getSelections(toList);
292             
293         toList.getStore().remove(selected);
294         this.fromField.boundList.getStore().add(selected);
295     },
296
297     onItemDblClick : function(view) {
298         var me = this;
299         if (view == me.toField.boundList){
300             me.onRemoveBtnClick();
301         }
302         else if (view == me.fromField.boundList) {
303             me.onAddBtnClick();
304         }
305     },
306
307     setRawValue: function(value) {
308         var me = this,
309             Array = Ext.Array,
310             toStore, fromStore, models;
311
312         value = Array.from(value);
313         me.rawValue = value;
314
315         if (me.toField) {
316             toStore = me.toField.boundList.getStore();
317             fromStore = me.fromField.boundList.getStore();
318
319             // Move any selected values back to the fromField
320             fromStore.add(toStore.getRange());
321             toStore.removeAll();
322
323             // Move the new values over to the toField
324             models = [];
325             Ext.Array.forEach(value, function(val) {
326                 var undef,
327                     model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
328                 if (model) {
329                     models.push(model);
330                 }
331             });
332             fromStore.remove(models);
333             toStore.add(models);
334         }
335
336         return value;
337     },
338
339     getRawValue: function() {
340         var me = this,
341             toField = me.toField,
342             rawValue = me.rawValue;
343
344         if (toField) {
345             rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
346                 return model.get(me.valueField);
347             });
348         }
349
350         me.rawValue = rawValue;
351         return rawValue;
352     },
353
354     /**
355      * @private Cascade readOnly/disabled state to the sub-fields and buttons
356      */
357     updateReadOnly: function() {
358         var me = this,
359             readOnly = me.readOnly || me.disabled;
360
361         if (me.rendered) {
362             me.toField.setReadOnly(readOnly);
363             me.fromField.setReadOnly(readOnly);
364             Ext.Array.forEach(me.innerCt.query('button'), function(button) {
365                 button.setDisabled(readOnly);
366             });
367         }
368     },
369
370     onDestroy: function() {
371         Ext.destroyMembers(this, 'innerCt');
372         this.callParent();
373     }
374
375 });
376