/** * Simple Select field wrapper. Example usage: * * @example * Ext.create('Ext.form.Panel', { * fullscreen: true, * items: [{ * xtype: 'fieldset', * title: 'Select', * items: [{ * xtype: 'selectfield', * label: 'Choose one', * options: [{ * text: 'First Option', * value: 'first' * }, { * text: 'Second Option', * value: 'second' * }, { * text: 'Third Option', * value: 'third' * }] * }] * }] * }); */Ext.define('Ext.field.Select', { extend: 'Ext.field.Picker', xtype: 'selectfield', alternateClassName: 'Ext.form.Select', requires: [ 'Ext.Panel', 'Ext.picker.Picker', 'Ext.data.Store', 'Ext.data.StoreManager', 'Ext.dataview.List' ], /** * @event change * Fires when an option selection has changed * @param {Ext.field.Select} this * @param {Ext.data.Model} newValue The corresponding record for the new value * @param {Ext.data.Model} oldValue The corresponding record for the old value */ /** * @event focus * Fires when this field receives input focus. This happens both when you tap on the field and when you focus on the field by using * 'next' or 'tab' on a keyboard. * * Please note that this event is not very reliable on Android. For example, if your Select field is second in your form panel, * you cannot use the Next button to get to this select field. This functionality works as expected on iOS. * @param {Ext.field.Select} this This field * @param {Ext.event.Event} e */ config: { /** * @cfg {Boolean} useClearIcon */ /** * @cfg {String/Number} valueField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this * Select control. * @accessor */ valueField: 'value', /** * @cfg {String/Number} displayField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this * Select control. This resolved value is the visibly rendered value of the available selection options. * @accessor */ displayField: 'text', /** * @cfg {Ext.data.Store/Object/String} store The store to provide selection options data. * Either a Store instance, configuration object or store ID. * @accessor */ store: null, /** * @cfg {Array} options An array of select options. * * [ * {text: 'First Option', value: 'first'}, * {text: 'Second Option', value: 'second'}, * {text: 'Third Option', value: 'third'} * ] * * __Note:__ Option object member names should correspond with defined {@link #valueField valueField} and {@link #displayField displayField} values. * This config will be ignored if a {@link #store store} instance is provided. * @accessor */ options: null, /** * @cfg {String} hiddenName Specify a `hiddenName` if you're using the {@link Ext.form.Panel#standardSubmit standardSubmit} option. * This name will be used to post the underlying value of the select to the server. * @accessor */ hiddenName: null, /** * @cfg {Boolean} autoSelect * `true` to auto select the first value in the {@link #store} or {@link #options} when they are changed. Only happens when * the {@link #value} is set to `null`. */ autoSelect: true, /** * @cfg * @inheritdoc */ name: 'picker', /** * @cfg {Ext.data.Model} selection * @accessor * The selected model. `null` if no value exists. */ selection: null }, classCls: Ext.baseCSSPrefix + 'selectfield', twoWayBindable: { selection: 1 }, publishes: { selection: 1 }, /** * @private */ applyValue: function(value) { var me = this, record = value, index, store; //we call this so that the options configruation gets intiailized, so that a store exists, and we can //find the correct value me.getOptions(); store = me.getStore(); if(!store && (value || value === 0)){ // the store might be updated later so we need to cache this value and apply it later me.cachedValue = value; } if ((value || value === 0) && !value.isModel && store) { index = store.find(me.getValueField(), value, null, null, null, true); if (index === -1) { index = store.find(me.getDisplayField(), value, null, null, null, true); } record = store.getAt(index); } return record; }, updateValue: function(value, oldValue) { var me = this, component = me.getComponent(), displayValue = ''; if (value === null || (value && value.isModel)) { me.settingSelection = true; me.setSelection(value); me.settingSelection = false; if (value) { displayValue = value.get(me.getDisplayField()); } } if (component) { component.setValue(displayValue); } if (me.initialized) { me.fireEvent('change', me, value, oldValue); } }, /** * Gets the currently selected value * @returns {String} Value Field from Selected Model */ getValue: function() { var selection = this.getSelection(); return selection ? selection.get(this.getValueField()) : null; }, applySelection: function(selection) { return selection || null; }, updateSelection: function(selection) { if (!this.settingSelection) { this.setValue(selection ? selection.get(this.getValueField()) : null); } }, /** * @private * Scrolls to selection, if set (applies to tablet picker only) */ scrollToSelection: function() { var me = this, picker = me.getTabletPicker(), list = picker.down('list'), selection = me.getSelection(); if (selection && list.listItems.length) { list.scrollToRecord(selection); } }, /** * @private */ getPhonePicker: function() { var me = this, phonePicker = me.phonePicker, config; if (!phonePicker) { config = me.getDefaultPhonePickerConfig(); me.phonePicker = phonePicker = Ext.create('Ext.picker.Picker', Ext.merge({ slots: [{ align: me.getPickerSlotAlign(), name: me.getName(), valueField: me.getValueField(), displayField: me.getDisplayField(), value: me.getValue(), store: me.getStore() }], listeners: { change: me.onPickerChange, scope: me } }, config)); } return phonePicker; }, /** * @private */ getTabletPicker: function() { var me = this, tabletPicker = me.tabletPicker, config; if (!tabletPicker) { config = me.getDefaultTabletPickerConfig(); me.tabletPicker = tabletPicker = Ext.create('Ext.Panel', Ext.merge({ floated: true, modal: true, anchor: true, cls: Ext.baseCSSPrefix + 'selectfield-overlay', layout: 'fit', hideOnMaskTap: true, width: Ext.os.is.Phone ? '14em' : '18em', height: (Ext.os.is.BlackBerry && Ext.os.version.getMajor() === 10) ? '12em' : (Ext.os.is.Phone ? '12.5em' : '22em'), listeners: { resize: { fn: 'onTabletPickerResize', single: true }, hiddenchange: 'onTabletPickerHiddenChange', scope: me }, items: { xtype: 'list', store: me.getStore(), itemTpl: '<span class="x-list-label">{' + me.getDisplayField() + ':htmlEncode}</span>', listeners: { select: me.onListSelect, itemtap: me.onListTap, scope: me } } }, config)); } return tabletPicker; }, /** * Shows the picker for the select field, whether that is a {@link Ext.picker.Picker} or a simple * {@link Ext.List list}. */ showPicker: function() { var me = this, store = me.getStore(), value = me.getValue(), picker, name, pickerValue, list, index, record; //check if the store is empty, if it is, return if (!store || store.getCount() === 0) { return; } if (me.getReadOnly()) { return; } me.isFocused = true; if (me.getUsePicker()) { picker = me.getPhonePicker(); name = me.getName(); pickerValue = {}; pickerValue[name] = value; picker.setValue(pickerValue); if (!picker.getParent()) { Ext.Viewport.add(picker); } picker.show(); } else { picker = me.getTabletPicker(); list = picker.down('list'); if (!picker.getParent()) { Ext.Viewport.add(picker); } picker.showBy(me.getComponent(), null); if (value || me.getAutoSelect()) { store = list.getStore(); index = store.find(me.getValueField(), value, null, null, null, true); record = store.getAt(index); if (record) { list.select(record, null, true); } } } }, /** * @private */ onListSelect: function(item, record) { var me = this; if (record) { me.setValue(record); } }, /** * @private */ onTabletPickerResize: function() { this.scrollToSelection(); }, /** * @private */ onTabletPickerHiddenChange: function(picker, hidden) { if (!hidden) { this.scrollToSelection(); } }, onListTap: function() { this.tabletPicker.hide({ type: 'fade', out: true, scope: this }); }, /** * @private */ onPickerChange: function(picker, value) { var me = this, newValue = value[me.getName()], store = me.getStore(), index = store.find(me.getValueField(), newValue, null, null, null, true), record = store.getAt(index); me.setValue(record); }, /** * Updates the underlying `<options>` list with new values. * * @param {Array} newOptions An array of options configurations to insert or append. * * selectBox.setOptions([ * {text: 'First Option', value: 'first'}, * {text: 'Second Option', value: 'second'}, * {text: 'Third Option', value: 'third'} * ]).setValue('third'); * * __Note:__ option object member names should correspond with defined {@link #valueField valueField} and * {@link #displayField displayField} values. * * @return {Ext.field.Select} this */ updateOptions: function(newOptions) { var store = this.getStore(); if (!store) { this.setStore(true); store = this._store; } if (!newOptions) { store.clearData(); } else { store.setData(newOptions); this.onStoreDataChanged(store); } return this; }, applyStore: function(store) { if (store === true) { store = Ext.create('Ext.data.Store', { fields: [this.getValueField(), this.getDisplayField()], autoDestroy: true }); } if (store) { store = Ext.data.StoreManager.lookup(store); } return store; }, updateStore: function(store, oldStore) { var me = this, tabletPicker = me.tabletPicker, phonePicker = me.phonePicker; if (oldStore && oldStore.getAutoDestroy()) { oldStore.destroy(); } if (store) { store.on({ scope: this, add: 'onStoreDataChanged', remove: 'onStoreDataChanged', update: 'onStoreDataChanged', refresh: 'onStoreDataChanged' }); me.onStoreDataChanged(store); } if (me.getUsePicker() && phonePicker) { phonePicker.down('pickerslot').setStore(store); } else if (tabletPicker) { tabletPicker.down('dataview').setStore(store); } }, /** * Called when the internal {@link #store}'s data has changed. */ onStoreDataChanged: function(store) { var me = this, initialConfig = me.getInitialConfig(), value = me.getValue(); if (value || value === 0) { me.setValue(value); } if (me.getValue() === null) { if(me.cachedValue || me.cachedValue === 0){ me.setValue(me.cachedValue); me.cachedValue = null; }else if (initialConfig.hasOwnProperty('value')) { me.setValue(initialConfig.value); } if (me.getValue() === null && me.getAutoSelect()) { if (store.getCount() > 0) { me.setValue(store.getAt(0)); } } } }, /** * Resets the Select field to the value of the first record in the store. * @return {Ext.field.Select} this * @chainable */ reset: function() { var me = this, record, store, usePicker, picker; if (me.getAutoSelect()) { store = me.getStore(); record = me.originalValue ? me.originalValue : store.getAt(0); } else { usePicker = me.getUsePicker(); picker = usePicker ? me.phonePicker : me.tabletPicker; if (picker) { picker = picker.child(usePicker ? 'pickerslot' : 'dataview'); picker.deselectAll(); } record = null; } me.setValue(record); return me; }, doDestroy: function() { var store = this.getStore(); if (store && store.getAutoDestroy()) { store.destroy(); } Ext.destroy(this.phonePicker, this.tabletPicker); this.callParent(); }});