/** * 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.picker.Tablet', 'Ext.data.Store', 'Ext.data.StoreManager', 'Ext.dataview.BoundList' ], /** * @property {Boolean} isSelectField * `true` to identify an object as an instance of this class, or a subclass thereof. */ isSelectField: true, /** * @event change * Fires when selection has changed. * * This includes keystrokes that edit the text (if editable). * @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 select * Fires when an option from the drop down list has been selected. * @param {Ext.field.Select} this * @param {Ext.data.Model} newValue The corresponding record for the new 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 {Object|Ext.util.Collection} valueCollection * A {@link Ext.util.Collection collection} instance, or configuration object used * to create the collection of selected records. * * This is used by the {@link #cfg!picker} as the core of its selection handling, * and also as the collection of selected values for this widget. * * @readonly * @private * @since 6.5.0 */ valueCollection: true, /** * @cfg {String/Number} valueField * The underlying {@link Ext.data.Field#name data value name} to bind to this * Select control. If configured as `null`, the {@link #cfg!displayField} is * used. * @accessor */ valueField: 'value', /** * @cfg {String/Ext.XTemplate} itemTpl * An XTemplate definition string (Or an {@link Ext.XTemplate}) which specifies * how to display a list item from a record values object. This is automatically * generated to display the {@link #cfg!displayField} if not specified. */ itemTpl: false, /** * @cfg {String/String[]/Ext.XTemplate} displayTpl * The template to be used to display the selected record inside the text field. * * If not specified, the {@link #cfg!displayField} is shown in the text field. */ displayTpl: null, /** * @cfg {String/Number} displayField * The underlying {@link Ext.data.Field#name data value name} to bind to this * Select control. If configured as `null`, the {@link #cfg!valueField} is used. * * 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 is mutually exclusive with the {@link #cfg!store} config. Specifying * them both is unssupported and will produce undefined behaviour. * @accessor */ options: null, /** * @cfg {String} hiddenName * Specify a `hiddenName` if you're using the {@link Ext.form.Panel#standardSubmit} * option. This name will be used to post the underlying value of the select to * the server. * @accessor */ hiddenName: null, /** * @cfg {Boolean/'initial'} autoSelect * `true` to auto select the first value in the {@link #store} or {@link #options} * when they are changed. This settings attempts to avoid the {@link #value} being * set to `null`, unless {@link #clearable clearable} is also `true` in which case * only other changes (such as store load) will trigger auto-selection. * * If this value is `'initial'` then auto selection will only occur on the first * opportunity (such as initial store load). This config will then be set to * `false`. */ autoSelect: false, /** * @cfg {Boolean} autoFocus * `true` to automatically focus the first result gathered by the data store in the * dropdown list when it is opened. A false value would cause nothing in the list * to be highlighted automatically, so the user would have to manually highlight an * item before pressing the enter or {@link #selectOnTab tab} key to select it * (unless the value of ({@link #typeAhead}) were true), or use the mouse to select * a value. */ autoFocus: true, /** * @cfg {Boolean} autoFocusLast * When `true`, the last selected record in the dropdown list will be re-selected * upon {@link #autoFocus}. Set to `false` to always select the first record in * the drop-down list. For accessible applications it is recommended to set this * option to `false`. */ autoFocusLast: true, /** * @cfg {Ext.data.Model} selection * @accessor * The selected model. `null` if no value exists. */ selection: null, /** * @cfg {Boolean} autoLoadOnValue * This option controls whether to initially load the store when a value is set so * that the display value can be determined from the appropriate record. * * The store will only be loaded in a limited set of circumstances: * - The store is not currently loading. * - The store does not have a pending {@link Ext.data.Store#autoLoad}. * - The store has not been loaded before. */ autoLoadOnValue: false, /** * @cfg {Boolean} forceSelection * By default the value must always be the {@link #cfg!valueField} of one of the * records in the store. Configure as `false` to allow the value to be set to * arbitrary text, and have this component auto create an associated record with * the typed value as the {@link #cfg!valueField}. * * This config is only supported for use in {@link Ext.field.ComboBox} but is defined * here (as private) because of its many entanglements with value processing. * @private * @since 6.5.0 */ forceSelection: true, /** * @cfg {String} valueNotFoundText * The message to display if the value passed to `setValue` is not found in the store. */ valueNotFoundText: null, /** * @cfg {Boolean} selectOnTab * Whether the Tab key should select the currently highlighted item. */ selectOnTab: true }, /** * @cfg editable * @inheritdoc */ editable: false, /** * @cfg floatedPicker * @inheritdoc */ floatedPicker: { xtype: 'boundlist', infinite: false, // BoundListNavigationModel binds to input field // Must only be enabled when list is visible navigationModel: { disabled: true }, scrollToTopOnRefresh: false, loadingHeight: 70, maxHeight: 300, floated: true, axisLock: true, hideAnimation: null }, /** * @cfg edgePicker * @inheritdoc */ edgePicker: { xtype: 'picker', cover: true }, /** * @property classCls * @inheritdoc */ classCls: Ext.baseCSSPrefix + 'selectfield', /** * @cfg twoWayBindable * @inheritdoc */ twoWayBindable: { selection: 1 }, /** * @cfg publishes * @inheritdoc */ publishes: { selection: 1 }, applyValueCollection: function(valueCollection) { if (!valueCollection.isCollection) { valueCollection = new Ext.util.Collection(valueCollection); } // Add this SelectField as an observer immediately so that we are informed of any // mutations which occur in this event run. // We must sync the selection property and the rawValue upon mutation. valueCollection.addObserver(this); return valueCollection; }, /** * This method is called to create a temporary record when the value entered does not * match a record in the `store` (when {@link #cfg!forceSelection} is `false`). * * The `data` object passed contains the typed value in both the {@link #cfg!valueField} * and the {@link #cfg!displayField}. * * The record created and returned from this method will be the {@link #cfg!selection} * value in this non-matching state. * * @param data The data object used to create the new record. * @return {Ext.data.Model} The new record. * @template * @since 6.5.1 */ createSelectionRecord: function (data) { var Model = this.getStore().getModel(); return new Model(data); }, completeEdit: Ext.emptyFn, expand: function() { // If we do not yet have a store (binding not arrived yet), we cannot expand. if (this.getStore()) { this.callParent(); } }, /** * @private */ maybeCollapse: function(event) { var record = event.to && event.to.record, selection = this.getSelection(); if (record === selection) { this.collapse(); } }, /** * @private * Respond to deselection. Call the onItemDeselect template method */ onCollectionRemove: function(valueCollection, chunk) { var selection = valueCollection.getRange(); // If this remove is part of a splice, wait until the collection add to sync the selection. if (!chunk.replacement) { // Must ensure that null is passed if the valueCollection is empty this.setSelection(selection.length ? selection[0] : null); } }, /** * @private * Respond to selection. Call the onItemSelect template method */ onCollectionAdd: function(valueCollection, adds) { var selection = valueCollection.getRange(); this.setSelection(selection[0]); }, clearValue: function () { var me = this; // We clear things differently vs superclass. The value of Select fields depends // upon the value collection. me.forceInputChange = true; me.setValue(null); me.forceInputChange = false; me.syncEmptyState(); }, /* TODO fixup these docs and move to value config * Sets the value of the field. * @param {Mixed/Ext.data.Model} newValue The new value. This may be specified as either * an existing store record, or the required {@link #cfg!valueField} value. * * Either way, both {@link #cfg!valueField} value *and* the associated record will be ascertained. * * The {@link #cfg!valueField} value is published to the ViewModel as is the {@link #cfg-selection associated record}. * * The record published to the selection property will be `null` if the value did not * match a record, and the field is not configured to create new records for unmatched * values using `{@link #cfg!forceSelection}: false` */ applyValue: function(value, oldValue) { // Ensure that a store is formed from any options before we get the store. this.getOptions(); var me = this, store = me.getStore(); // syncValue must now prioritize the value over the inputValue me.syncMode = 'value'; // We were passed a record. // Set the selection which updates the value from the valueField. if (value && value.isEntity) { me.setSelection(value); return; } if (me.isConfiguring) { me.originalValue = value; } // Kick off a load unless we are clearing the value. // Doesn't matter whether proxy is remote - it needs loading // so we can select the correct record for the value in the load event handler. if (store && value) { // If we are configured to autoLoad when the value arrives, prepare to do so if (me.getAutoLoadOnValue() && !store.isLoaded() && !store.hasPendingLoad()) { store.load(); } } return me.transformValue(value); }, updateValue: function(value, oldValue) { this.syncValue(); // Note that we must not invoke superclass updateValue because that updates the // field UI in ways that SelectFields cannot handle. // We must directly invoke the base class's updateValue. That fires the change // event and validates the value which we still need to happen. Ext.field.Field.prototype.updateValue.call(this, value, oldValue); }, transformValue: function (value) { if (value == null || value === '') { value = this.getForceSelection() ? null : ''; } return value; }, /** * Finds the record in the {@link #cfg!store}, or the {@link #cfg!valueCollection} which has the {@link #cfg!valueField} * matching the passed value. * * The {@link #cfg!valueCollection} is included because of the {@link #cfg!createNewOnEnter}, * {@link #cfg!createNewOnBlur}, and {@link #cfg!forceSelection} configs which allow for insertion into the * {@link #cfg!valueCollection} of newly created records which are not in the configured {@link #cfg!store}. * * Also, a currently selected value may be filtered out of visibility in the configured {@link #cfg!store} * * @param {String} value The value to match the {@link #valueField} against. * @return {Ext.data.Model} The matched record or null. */ findRecordByValue: function(value) { var me = this, store = me.getStore(), valueField = me.getValueField(), result, ret = null; if (store) { result = store.byValue.get(value); // If there are duplicate keys, tested behaviour is to return the *first* match. if (result) { ret = result[0] || result; } } // Not found in the base store. // See if there's a match in the valueCollection. // This is because we allow new records to be created if forceSelection is false // And we allow value to be set to a record which is then inserted into the valueCollection. if (!ret) { ret = me.getValueCollection().findBy(function(record) { return record.get(valueField) === value; }); } return ret; }, /** * Finds the record by searching values in the {@link #displayField}. * @param {Object} value The value to match the field against. * @return {Ext.data.Model/false} The matched record or `false`. */ findRecordByDisplay: function(value) { var store = this.getStore(), result, ret = false; if (store) { result = store.byText.get(value); // If there are duplicate keys, tested behaviour is to return the *first* match. if (result) { ret = result[0] || result; } } return ret; }, /** * @private * Update the UI to reflect the new selection. The selection arrives as mutation notifications * from the {@link #cfg!valueCollection} which is the {@link Ext.util.Collection} at the heart * of the picker's {@link Ext.mixin.Selectable} persona. */ updateSelection: function(selection, oldSelection) { var me = this, isNull = selection == null, valueCollection = me.getValueCollection(), valueField = me.getValueField(), oldValue = me._value, newValue = null, picker, spliceArgs; if (me._ignoreSelection || me.destroyed || me.destroying) { return; } if (isNull || !valueCollection.containsAll(selection)) { spliceArgs = [0, valueCollection.getCount()]; // If the selection isNull, do not append the final "toAdd" argument. // That would attempt to add null which would throw an error. if (!isNull) { spliceArgs.push(selection); } // Replace all valueCollection content with the new selection. // We are an observer of the valueCollection. // // This will feed through to our onCollectionRemove, which will only // push through to the selection property if there's no upcoming add. // // If there's an add, then our onCollectionAdd will be called // which will push the valueCollection's data through to // our selection property. valueCollection.splice.apply(valueCollection, spliceArgs); // In case splice user event handler destroyed us. if (me.destroyed) { return; } } if (selection) { if (valueField) { newValue = selection.get(valueField); me.setValue(newValue); } // Allow selection to be vetoed, in which case fall back to oldValue if (me.fireEvent('select', me, selection) === false) { me.setValue(oldValue); me._selection = oldSelection; } } else { me.clearValue(); } // Event handlers may destroy this component if (me.destroyed) { return; } // Update the field's input UI. // Note that this may be a DOM <input> value, but may also // be a UI like a TagField which is produced from the // selected record(s) me.setFieldDisplay(selection); // Only get the picker if it has been created. picker = me.getConfig('picker', false, true); // If the picker has been created, either collapse it, // or scroll to the latest selection. if (picker && picker.isVisible()) { // The setter's equality test cannot tell if the single selected record // is in effect unchanged. We only need to collapse if a *new* value has // been set, that is, the user has selected a record with a different id. // We can get here when the selection is refreshed due to record add/remove // when the record *instance* is renewed, but it is the same id. // In that case, all we need is a refresh of the data in case the record's // data payload changed. // // If unchanged, it's possible that other data in the record may have changed // which could affect the BoundList, so refresh that if (selection && oldSelection && selection.id === oldSelection.id) { picker.refresh(); } else { // If it's a single select, dynamically created record, this is due // to typing, so do not collapse. if (!(selection && selection.isEntered)) { me.collapse(); } } } }, /** * Gets data for each record to be used for constructing the display value with * the {@link #displayTpl}. This may be overridden to provide access to associated records. * @param {Ext.data.Model} record The record. * @return {Object} The data to be passed for each record to the {@link #displayTpl}. * * @protected * @template */ getRecordDisplayData: function(record) { return record.getData(); }, createFloatedPicker: function() { var me = this, result = Ext.merge({ ownerCmp: me, store: me._pickerStore || me.getStore(), selectable: { selected: me.getValueCollection(), selectedRecord: me.getSelection(), mode: 'single', deselectable: false }, itemTpl: me.getItemTpl() }, me.getFloatedPicker()); // Allow SPACE to navigate unless it's needed // to edit the inputElement. result.navigationModel.navigateOnSpace = !me.getEditable(); return result; }, createEdgePicker: function() { var me = this; return Ext.merge({ ownerCmp: me, slots: [{ align: me.getPickerSlotAlign(), name: me.getValueField(), valueField: me.getValueField(), displayField: me.getDisplayField(), value: me.getValue(), store: me._pickerStore || me.getStore() }], listeners: { change: me.onPickerChange, scope: me }, setStore: function(store) { this.child('pickerslot').setStore(store); }, deselectAll: function() { this.child('pickerslot').deselectAll(); } }, me.getEdgePicker()); }, setPickerLocation: function(fromKeyboard) { var me = this, picker = me.getConfig('picker', false, true), store, location; if (picker && me.expanded) { // If an edge picker, access the slot which is a List if (picker.isPicker) { picker = picker.innerItems[0]; } store = picker.getStore(); if (picker.getViewItems().length) { // If there's a selection, we always move focus to it location = picker.getSelectable().getLastSelected(); // If there's no selection, or the selection is not in the picker store, // then autoFocusLast attempts to focus the last known focused location. // And the fallback is autoFocus focusing record 0. if (!location || !store.contains(location)) { if (fromKeyboard || me.getAutoFocusLast()) { location = picker.getNavigationModel().lastLocation; if (location) { location = location.refresh(); } } if (!location && (fromKeyboard || me.getAutoFocus())) { location = store.getAt(0); } } picker.getNavigationModel().setLocation(location); } } }, updatePickerValue: function (picker, value) { var name = this.getValueField(), pickerValue = {}; if (!value) { value = this.getValue(); } pickerValue[name] = value; picker.setValue(pickerValue); }, onPickerShow: function(picker) { this.callParent([picker]); // Enable the picker's key mappings in this field's KeyMap, // unless it's an edge picker that doesn't support keyboard if (this.pickerType === 'floated') { picker.getNavigationModel().enable(); } }, onPickerHide: function(picker) { var navModel; this.callParent([picker]); // Set the location to null because there's no onFocusLeave // to do this because the picker does not get focused. // Disable the picker's key mappings in this field's KeyMap if (!picker.destroying && this.pickerType === 'floated') { navModel = picker.getNavigationModel(); navModel.setLocation(null); navModel.disable(); } }, /** * @private * Used when the edge picker is used. */ onPickerChange: function(picker, value) { this.setValue(this.findRecordByValue(value[this.getValueField()])); }, applyItemTpl: function (itemTpl) { if (itemTpl === false) { itemTpl = '<span class="x-list-label">{' + this.getDisplayField() + ':htmlEncode}</span>'; } return itemTpl; }, applyDisplayTpl: function (displayTpl) { if (displayTpl && !displayTpl.isTemplate) { displayTpl = new Ext.XTemplate(displayTpl); } return displayTpl; }, applyOptions: function(options) { if (options) { var len = options.length, valueField = this.getValueField(), displayField = this.getDisplayField(), i, value, option; // Convert an array of primitives to record data objects options = Ext.Array.slice(options); for (i = 0; i < len; i++) { value = options[i]; if (Ext.isPrimitive(value)) { options[i] = option = {}; option.id = value; option[valueField] = value; if (displayField && displayField !== valueField) { option[displayField] = value; } } } options = Ext.data.StoreManager.lookup({ fields: [valueField, displayField], data: options }); } return options; }, updateOptions: function(options, oldOptions) { if (options) { this.setStore(options); } else { if (oldOptions === this.getStore()) { this.setStore(null); } } }, applyStore: function(store) { if (store) { store = Ext.data.StoreManager.lookup(store); } return store; }, updateStore: function(store, oldStore) { var me = this, valueField = me.getValueField(), displayField = me.getDisplayField(), extraKeySpec; if (oldStore) { if (oldStore.getAutoDestroy()) { oldStore.destroy(); } else { oldStore.byValue = oldStore.byText = Ext.destroy(oldStore.byValue, oldStore.byText); } } if (store) { // Add a byValue index to the store so that we can efficiently look up records by the value field // when setValue passes string value(s). // The two indices (Ext.util.CollectionKeys) are configured unique: false, so that if duplicate keys // are found, they are all returned by the get call. // This is so that findByText and findByValue are able to return the *FIRST* matching value. By default, // if unique is true, CollectionKey keeps the *last* matching value. extraKeySpec = { byValue: { rootProperty: 'data', unique: false, property: valueField } }; if (displayField !== valueField) { extraKeySpec.byText = { rootProperty: 'data', unique: false, property: displayField }; } store.setExtraKeys(extraKeySpec); // If display and value fields are the same, the same index goes by both names. if (displayField === valueField) { store.byText = store.byValue; } store.on({ scope: me, add: 'onStoreDataChanged', remove: 'onStoreDataChanged', update: 'onStoreRecordUpdated', // Must be informed after list, and selection has been updated load: { fn: 'onStoreLoad', priority: -1 } }); // If the store is already loaded, fix up any value we may have. // cachedValue will be set if there was no store at init time. // If we had a selected record, rematch it. // Otherwise auto select first record if configured to do so. if (store.isLoaded() && !store.hasPendingLoad()) { me.syncValue(); } // If not loaded, and there's a value waiting to be matched // and we should autoload on value, load the store and onStoreLoad // will match it up. else if (me.getValue() != null && me.getAutoLoadOnValue() && !store.isLoaded() && !store.hasPendingLoad()) { store.load(); } } // Depending upon configurations, we may need a ChainedStore to drive // the picker. me.updatePickerStore(); }, applyValueField: function(valueField) { // If either valueField or displayField are configured as null, then // this Select component uses the remaining configured field name for both purposes. if (valueField == null) { valueField = this.getDisplayField(); } return valueField; }, updateValueField: function(valueField) { var store = this.getStore(); // Keep the byValue index synced if (store && !this.isConfiguring) { store.byValue.setCollection(null); store.setExtraKeys({ byValue: { rootProperty: 'data', unique: false, property: valueField } }); } }, applyDisplayField: function(displayField) { // If either valueField or displayField are configured as null, then // this Select component uses the remaining configured field name for both purposes. if (displayField == null) { displayField = this.getValueField(); } return displayField; }, updateDisplayField: function(displayField) { var store = this.getStore(); // Keep the byValue index synced if (store && !this.isConfiguring) { store.byText.setCollection(null); store.setExtraKeys({ byText: { rootProperty: 'data', unique: false, property: displayField } }); } }, /** * @private * Whenever the store loads, we need to refresh the selection by pushing a value through * the setValue machinery. Upon initialization, there may be a cached initial value. * Otherwise use the current value. */ onStoreLoad: function (store, records, success) { var filtering = this.isFiltering; this.isFiltering = false; if (success) { // The isFilering flag is set in doFilter if the store // if using remote filters and the primaryFilter has a value. this.syncMode = filtering ? 'filter' : 'store'; this.syncValue(); } }, syncValue: function() { var me = this, store = me.getStore(), valueField = me.getValueField(), displayField = me.getDisplayField(), forceSelection = me.getForceSelection(), valueNotFoundText = me.getValueNotFoundText(), is, isCleared, isInput, value, matchedRecord, dataObj; // If we are not ready to reconcile values for any reason. // We are in the middle of value syncing // Store has not arrived from bind // Store has not been loaded // Store is currently loading // Then we cannot recconcile values now, this will be called later // when the store arrives, or is loaded. if (me.reconcilingValue || !store || !store.isLoaded() || store.hasPendingLoad()) { return; } me.reconcilingValue = true; me.getSelection(); // make sure selection config is flushed is = {}; is[me.syncMode] = true; value = (isInput = is.input || is.filter) ? me.getInputValue() : me.getValue(); isCleared = value == null || value === ''; // Get the record that matches our input value if (!isCleared) { matchedRecord = (isInput ? store.byText : store.byValue).get(value); if (matchedRecord) { if (!matchedRecord.isEntity) { // Since we lookup values not id's there can be multiple matching // records... so if we get back something that isn't a record, it is // an array. matchedRecord = matchedRecord[0]; } } else if (!forceSelection) { // Not found in the regular indexes which index the store. // If we are potentially inserting unmatched values as new "isEntered" // records, then find a match in the valueCollection if possible. matchedRecord = me.findRecordByValue(value); } } // Either user has typed something (isInput), or we've had a setValue // to a value which has no match in the store, and we are not forceSelection: true. // We create a new record. if (!isCleared && !matchedRecord && !forceSelection) { dataObj = {}; dataObj[displayField] = value; if (valueField && displayField !== valueField) { dataObj[valueField] = value; } matchedRecord = me.createSelectionRecord(dataObj); matchedRecord.isEntered = true; } else { // Not in an record.isEntered situation. // Value is the typed value. if (isInput || is.store) { if (!matchedRecord && forceSelection) { me.setValue(null); me.setSelection(null); // If we're processing a store load in response to remote filtering // then we must not clear the input value used to kick off that filter. // If they blur the field now, completeEdit will clear the value as unmatched. if (!is.filter) { me.setFieldDisplay(); } } } // Value is the set value. else { if (isCleared) { if (me.mustAutoSelect()) { matchedRecord = store.first(); if (me.getAutoSelect() === 'initial') { me.setAutoSelect(false); } } else { me.setSelection(null); } } // We have a value, so get the record that matches our current value. // Note that setValue can else if (!matchedRecord && valueNotFoundText) { me.setError(valueNotFoundText); } } } if (matchedRecord) { me.setSelection(matchedRecord); } me.reconcilingValue = false; }, /** * @private * Called when the internal {@link #store}'s data has changed. */ onStoreDataChanged: function () { if (this.getForceSelection()) { var value = this.getValue(); // Push the textual value from the selected record through applyValue // to match with a new record from the new data. if (value != null) { this.setValue(value); } } }, /** * @private * Called when a internal {@link #store}'s record has been mutated. * Keep the field UI synced */ onStoreRecordUpdated: function(store, record) { if (this.getValueCollection().contains(record)) { this.updateSelection(this.getSelection()); } }, /** * 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, picker = me.getConfig('picker', false, true), record = me.originalValue || null, store; if (me.getAutoSelect()) { store = me.getStore(); record = (record != null) ? record : store && store.getAt(0); } else { if (picker) { picker.deselectAll(); } } me.setValue(record); return me; }, doDestroy: function() { var store = this.getStore(); if (store && !store.destroyed && store.getAutoDestroy()) { store.destroy(); } this.destroyMembers('options'); this.callParent(); }, privates: { syncMode: null, mustAutoSelect: function () { var me = this, autoSelect = me.getAutoSelect(); if (autoSelect && !(me.isConfiguring || autoSelect === 'initial')) { autoSelect = !me.getClearable() && me.getRequired(); } return !!autoSelect; }, /** * Returns ths Store used to drive the BoundList. * * When the supplied store is `queryMode: 'local'`, this will be a ChainedStore sources from the * configured store. * @private */ updatePickerStore: function() { var me = this, picker = me.getConfig('picker', false, true), store = me.getStore(), localFiltering = me.getQueryMode && me.getQueryMode() === 'local', result = store; // If we need to be adding local filters, then we need to chain off a store based // on the supplied store so that we can own the filtering. if (localFiltering) { // Already got a ChainedStore - just reconfigure it. if (me._pickerStore && me._pickerStore.isChainedStore) { me._pickerStore.setConfig({ source: store }); } // Create a ChainedStore based on our store else { me._pickerStore = result = Ext.data.StoreManager.lookup({ type: 'chained', source: store }, null, me); } } // The _pickerStore is the base store. else { me._pickerStore = result = store; } // Bind the picker to the correct store. If it is the default store, this // will be a no-op. if (picker) { picker.setStore(result); } }, /** * Updates the fields input UI according to the current selection. * * @param selection * @private */ setFieldDisplay: function (selection) { var me = this, inputValue = '', displayTpl; if (selection) { displayTpl = me.getDisplayTpl(); if (displayTpl) { inputValue = displayTpl.apply(me.getRecordDisplayData(selection)); } else { inputValue = selection.get(me.getDisplayField()); } } me.setInputValue(inputValue); // Ensure clear icon is synced me.syncEmptyState(); } }});