/* * Note that this control will most likely remain as an example, and not as a core Ext form * control. However, the API will be changing in a future release and so should not yet be * treated as a final, stable API at this time. */ /** * A control that allows selection of between two Ext.ux.form.MultiSelect controls. */Ext.define('Ext.ux.form.ItemSelector', { extend: 'Ext.ux.form.MultiSelect', alias: ['widget.itemselectorfield', 'widget.itemselector'], alternateClassName: ['Ext.ux.ItemSelector'], requires: [ 'Ext.button.Button', 'Ext.ux.form.MultiSelect' ], /** * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons */ hideNavIcons:false, /** * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}. * This can be overridden with a custom Array to change which buttons are displayed or their order. */ buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'], /** * @cfg {Object} buttonsText The tooltips for the {@link #buttons}. * Labels for buttons. */ buttonsText: { top: "Move to Top", up: "Move Up", add: "Add to Selected", remove: "Remove from Selected", down: "Move Down", bottom: "Move to Bottom" }, layout: { type: 'hbox', align: 'stretch' }, ariaRole: 'group', initComponent: function() { var me = this; me.ddGroup = me.id + '-dd'; me.ariaRenderAttributes = me.ariaRenderAttributes || {}; me.ariaRenderAttributes['aria-labelledby'] = me.id + '-labelEl'; me.callParent(); // bindStore must be called after the fromField has been created because // it copies records from our configured Store into the fromField's Store me.bindStore(me.store); }, createList: function(title){ var me = this; return Ext.create('Ext.ux.form.MultiSelect', { // We don't want the multiselects themselves to act like fields, // so override these methods to prevent them from including // any of their values submitValue: false, getSubmitData: function(){ return null; }, getModelData: function(){ return null; }, flex: 1, dragGroup: me.ddGroup, dropGroup: me.ddGroup, title: title, store: { model: me.store.model, data: [] }, displayField: me.displayField, valueField: me.valueField, disabled: me.disabled, listeners: { boundList: { scope: me, itemdblclick: me.onItemDblClick, drop: me.syncValue } } }); }, setupItems: function() { var me = this; me.fromField = me.createList(me.fromTitle); me.toField = me.createList(me.toTitle); return [ me.fromField, { xtype: 'toolbar', margin: '0 4', padding: 0, layout: { type: 'vbox', pack: 'center' }, items: me.createButtons() }, me.toField ]; }, createButtons: function() { var me = this, buttons = []; if (!me.hideNavIcons) { Ext.Array.forEach(me.buttons, function(name) { buttons.push({ xtype: 'button', ui: 'default', tooltip: me.buttonsText[name], ariaLabel: me.buttonsText[name], handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'], cls: Ext.baseCSSPrefix + 'form-itemselector-btn', iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name, navBtn: true, scope: me, margin: '4 0 0 0' }); }); } return buttons; }, /** * Get the selected records from the specified list. * * Records will be returned *in store order*, not in order of selection. * @param {Ext.view.BoundList} list The list to read selections from. * @return {Ext.data.Model[]} The selected records in store order. * */ getSelections: function(list) { var store = list.getStore(); return Ext.Array.sort(list.getSelectionModel().getSelection(), function(a, b) { a = store.indexOf(a); b = store.indexOf(b); if (a < b) { return -1; } else if (a > b) { return 1; } return 0; }); }, onTopBtnClick : function() { var list = this.toField.boundList, store = list.getStore(), selected = this.getSelections(list); store.suspendEvents(); store.remove(selected, true); store.insert(0, selected); store.resumeEvents(); list.refresh(); this.syncValue(); list.getSelectionModel().select(selected); }, onBottomBtnClick : function() { var list = this.toField.boundList, store = list.getStore(), selected = this.getSelections(list); store.suspendEvents(); store.remove(selected, true); store.add(selected); store.resumeEvents(); list.refresh(); this.syncValue(); list.getSelectionModel().select(selected); }, onUpBtnClick : function() { var list = this.toField.boundList, store = list.getStore(), selected = this.getSelections(list), rec, i = 0, len = selected.length, index = 0; // Move each selection up by one place if possible store.suspendEvents(); for (; i < len; ++i, index++) { rec = selected[i]; index = Math.max(index, store.indexOf(rec) - 1); store.remove(rec, true); store.insert(index, rec); } store.resumeEvents(); list.refresh(); this.syncValue(); list.getSelectionModel().select(selected); }, onDownBtnClick : function() { var list = this.toField.boundList, store = list.getStore(), selected = this.getSelections(list), rec, i = selected.length - 1, index = store.getCount() - 1; // Move each selection down by one place if possible store.suspendEvents(); for (; i > -1; --i, index--) { rec = selected[i]; index = Math.min(index, store.indexOf(rec) + 1); store.remove(rec, true); store.insert(index, rec); } store.resumeEvents(); list.refresh(); this.syncValue(); list.getSelectionModel().select(selected); }, onAddBtnClick : function() { var me = this, selected = me.getSelections(me.fromField.boundList); me.moveRec(true, selected); me.toField.boundList.getSelectionModel().select(selected); }, onRemoveBtnClick : function() { var me = this, selected = me.getSelections(me.toField.boundList); me.moveRec(false, selected); me.fromField.boundList.getSelectionModel().select(selected); }, moveRec: function(add, recs) { var me = this, fromField = me.fromField, toField = me.toField, fromStore = add ? fromField.store : toField.store, toStore = add ? toField.store : fromField.store; fromStore.suspendEvents(); toStore.suspendEvents(); fromStore.remove(recs); toStore.add(recs); fromStore.resumeEvents(); toStore.resumeEvents(); // If the list item was focused when moved (e.g. via double-click) // then removing it will cause the focus to be thrown back to the // document body. Which might disrupt things if ItemSelector is // contained by a floating thingie like a Menu. // Focusing the list itself will prevent that. if (fromField.boundList.containsFocus) { fromField.boundList.focus(); } fromField.boundList.refresh(); toField.boundList.refresh(); me.syncValue(); }, // Synchronizes the submit value with the current state of the toStore syncValue: function() { var me = this; me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange())); }, onItemDblClick: function(view, rec) { this.moveRec(view === this.fromField.boundList, rec); }, setValue: function(value) { var me = this, fromField = me.fromField, toField = me.toField, fromStore = fromField.store, toStore = toField.store, selected; // Wait for from store to be loaded if (!me.fromStorePopulated) { me.fromField.store.on({ load: Ext.Function.bind(me.setValue, me, [value]), single: true }); return; } value = me.setupValue(value); me.mixins.field.setValue.call(me, value); selected = me.getRecordsForValue(value); // Clear both left and right Stores. // Both stores must not fire events during this process. fromStore.suspendEvents(); toStore.suspendEvents(); fromStore.removeAll(); toStore.removeAll(); // Reset fromStore me.populateFromStore(me.store); // Copy selection across to toStore Ext.Array.forEach(selected, function(rec){ // In the from store, move it over if (fromStore.indexOf(rec) > -1) { fromStore.remove(rec); } toStore.add(rec); }); // Stores may now fire events fromStore.resumeEvents(); toStore.resumeEvents(); // Refresh both sides and then update the app layout Ext.suspendLayouts(); fromField.boundList.refresh(); toField.boundList.refresh(); Ext.resumeLayouts(true); }, onBindStore: function(store, initial) { var me = this, fromField = me.fromField, toField = me.toField; if (fromField) { fromField.store.removeAll(); toField.store.removeAll(); if (store.autoCreated) { fromField.resolveDisplayField(); toField.resolveDisplayField(); me.resolveDisplayField(); } if (!Ext.isDefined(me.valueField)) { me.valueField = me.displayField; } // Add everything to the from field as soon as the Store is loaded if (store.getCount()) { me.populateFromStore(store); } else { me.store.on('load', me.populateFromStore, me); } } }, populateFromStore: function(store) { var fromStore = this.fromField.store; // Flag set when the fromStore has been loaded this.fromStorePopulated = true; fromStore.add(store.getRange()); // setValue waits for the from Store to be loaded fromStore.fireEvent('load', fromStore); }, onEnable: function(){ var me = this; me.callParent(); me.fromField.enable(); me.toField.enable(); Ext.Array.forEach(me.query('[navBtn]'), function(btn){ btn.enable(); }); }, onDisable: function(){ var me = this; me.callParent(); me.fromField.disable(); me.toField.disable(); Ext.Array.forEach(me.query('[navBtn]'), function(btn){ btn.disable(); }); }, doDestroy: function(){ this.bindStore(null); this.callParent(); }});