3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
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.
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.
27 * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
30 * Create a new ItemSelector
31 * @param {Object} config Configuration options
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'],
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.
48 buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
53 add: "Add to Selected",
54 remove: "Remove from Selected",
56 bottom: "Move to Bottom"
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.
65 componentLayout: 'itemselectorfield',
67 fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',
70 bindStore: function(store, initial) {
73 fromField = me.fromField,
76 me.callParent(arguments);
79 // Clear both field stores
80 toField.store.removeAll();
81 fromField.store.removeAll();
83 // Clone the contents of the main store into the fromField
85 me.store.each(function(model) {
86 models.push(model.copy(model.getId()));
88 fromField.store.add(models);
92 onRender: function(ct, position) {
94 baseCSSPrefix = Ext.baseCSSPrefix,
95 ddGroup = 'ItemSelectorDD-' + Ext.id(),
97 displayField: me.displayField,
98 valueField: me.valueField,
104 fromConfig = Ext.apply({
105 listTitle: 'Available',
106 store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
109 itemdblclick: me.onItemDblClick,
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
119 itemdblclick: me.onItemDblClick,
122 change: me.onToFieldChange,
125 }, me.multiselects[1], commonConfig),
126 fromField = Ext.widget('multiselect', fromConfig),
127 toField = Ext.widget('multiselect', toConfig),
131 // Skip MultiSelect's onRender as we don't want its content
132 Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
134 me.fromField = fromField;
135 me.toField = toField;
137 if (!me.hideNavIcons) {
138 Ext.Array.forEach(me.buttons, function(name) {
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,
147 //div separator to force vertical stacking
148 buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
152 innerCt = me.innerCt = Ext.widget('container', {
169 // Must set upward link after first render
170 innerCt.ownerCt = me;
172 // Rebind the store so it gets cloned to the fromField
173 me.bindStore(me.store);
175 // Set the initial value
176 me.setRawValue(me.rawValue);
179 onToFieldChange: function() {
183 getSelections: function(list){
184 var store = list.getStore(),
185 selections = list.getSelectionModel().getSelection(),
187 len = selections.length;
189 return Ext.Array.sort(selections, function(a, b){
190 a = store.indexOf(a);
191 b = store.indexOf(b);
202 onTopBtnClick : function() {
203 var list = this.toField.boundList,
204 store = list.getStore(),
205 selected = this.getSelections(list),
206 i = selected.length - 1,
210 store.suspendEvents();
211 for (; i > -1; --i) {
212 selection = selected[i];
213 store.remove(selected);
214 store.insert(0, selected);
216 store.resumeEvents();
220 onBottomBtnClick : function() {
221 var list = this.toField.boundList,
222 store = list.getStore(),
223 selected = this.getSelections(list),
225 len = selected.length,
228 store.suspendEvents();
229 for (; i < len; ++i) {
230 selection = selected[i];
231 store.remove(selection);
232 store.add(selection);
234 store.resumeEvents();
238 onUpBtnClick : function() {
239 var list = this.toField.boundList,
240 store = list.getStore(),
241 selected = this.getSelections(list),
243 len = selected.length,
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);
254 store.resumeEvents();
258 onDownBtnClick : function() {
259 var list = this.toField.boundList,
260 store = list.getStore(),
261 selected = this.getSelections(list),
263 len = selected.length,
264 max = store.getCount(),
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);
275 store.resumeEvents();
279 onAddBtnClick : function() {
281 fromList = me.fromField.boundList,
282 selected = this.getSelections(fromList);
284 fromList.getStore().remove(selected);
285 this.toField.boundList.getStore().add(selected);
288 onRemoveBtnClick : function() {
290 toList = me.toField.boundList,
291 selected = this.getSelections(toList);
293 toList.getStore().remove(selected);
294 this.fromField.boundList.getStore().add(selected);
297 onItemDblClick : function(view) {
299 if (view == me.toField.boundList){
300 me.onRemoveBtnClick();
302 else if (view == me.fromField.boundList) {
307 setRawValue: function(value) {
310 toStore, fromStore, models;
312 value = Array.from(value);
316 toStore = me.toField.boundList.getStore();
317 fromStore = me.fromField.boundList.getStore();
319 // Move any selected values back to the fromField
320 fromStore.add(toStore.getRange());
323 // Move the new values over to the toField
325 Ext.Array.forEach(value, function(val) {
327 model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
332 fromStore.remove(models);
339 getRawValue: function() {
341 toField = me.toField,
342 rawValue = me.rawValue;
345 rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
346 return model.get(me.valueField);
350 me.rawValue = rawValue;
355 * @private Cascade readOnly/disabled state to the sub-fields and buttons
357 updateReadOnly: function() {
359 readOnly = me.readOnly || me.disabled;
362 me.toField.setReadOnly(readOnly);
363 me.fromField.setReadOnly(readOnly);
364 Ext.Array.forEach(me.innerCt.query('button'), function(button) {
365 button.setDisabled(readOnly);
370 onDestroy: function() {
371 Ext.destroyMembers(this, 'innerCt');