4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-form-field-ComboBox'>/**
19 </span> * @class Ext.form.field.ComboBox
20 * @extends Ext.form.field.Picker
22 * A combobox control with support for autocomplete, remote loading, and many other features.
24 * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
25 * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
26 * list. The user can input any value by default, even if it does not appear in the selection list;
27 * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
29 * The selection list's options are populated from any {@link Ext.data.Store}, including remote
30 * stores. The data items in the store are mapped to each option's displayed text and backing value via
31 * the {@link #valueField} and {@link #displayField} configurations, respectively.
33 * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
34 * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
36 * {@img Ext.form.ComboBox/Ext.form.ComboBox.png Ext.form.ComboBox component}
40 * // The data store containing the list of states
41 * var states = Ext.create('Ext.data.Store', {
42 * fields: ['abbr', 'name'],
44 * {"abbr":"AL", "name":"Alabama"},
45 * {"abbr":"AK", "name":"Alaska"},
46 * {"abbr":"AZ", "name":"Arizona"}
51 * // Create the combo box, attached to the states data store
52 * Ext.create('Ext.form.ComboBox', {
53 * fieldLabel: 'Choose State',
56 * displayField: 'name',
58 * renderTo: Ext.getBody()
63 * To do something when something in ComboBox is selected, configure the select event:
65 * var cb = Ext.create('Ext.form.ComboBox', {
66 * // all of your config options
69 * 'select': yourFunction
73 * // Alternatively, you can assign events after the object is created:
74 * var cb = new Ext.form.field.ComboBox(yourOptions);
75 * cb.on('select', yourFunction, yourScope);
77 * ## Multiple Selection
79 * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
80 * {@link #multiSelect} config to `true`.
82 * @docauthor Jason Johnston <jason@sencha.com>
84 Ext.define('Ext.form.field.ComboBox', {
85 extend:'Ext.form.field.Picker',
86 requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
87 alternateClassName: 'Ext.form.ComboBox',
88 alias: ['widget.combobox', 'widget.combo'],
90 <span id='Ext-form-field-ComboBox-cfg-triggerCls'> /**
91 </span> * @cfg {String} triggerCls
92 * An additional CSS class used to style the trigger button. The trigger will always get the
93 * {@link #triggerBaseCls} by default and <code>triggerCls</code> will be <b>appended</b> if specified.
94 * Defaults to 'x-form-arrow-trigger' for ComboBox.
96 triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
98 <span id='Ext-form-field-ComboBox-cfg-store'> /**
99 </span> * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to <code>undefined</code>).
100 * Acceptable values for this property are:
101 * <div class="mdetail-params"><ul>
102 * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
103 * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.Store} internally,
104 * automatically generating {@link Ext.data.Field#name field names} to work with all data components.
105 * <div class="mdetail-params"><ul>
106 * <li><b>1-dimensional array</b> : (e.g., <code>['Foo','Bar']</code>)<div class="sub-desc">
107 * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
108 * {@link #valueField} and {@link #displayField})</div></li>
109 * <li><b>2-dimensional array</b> : (e.g., <code>[['f','Foo'],['b','Bar']]</code>)<div class="sub-desc">
110 * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
111 * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
112 * </div></li></ul></div></li></ul></div>
113 * <p>See also <code>{@link #queryMode}</code>.</p>
116 <span id='Ext-form-field-ComboBox-cfg-multiSelect'> /**
117 </span> * @cfg {Boolean} multiSelect
118 * If set to <code>true</code>, allows the combo field to hold more than one value at a time, and allows selecting
119 * multiple items from the dropdown list. The combo's text field will show all selected values separated by
120 * the {@link #delimiter}. (Defaults to <code>false</code>.)
124 <span id='Ext-form-field-ComboBox-cfg-delimiter'> /**
125 </span> * @cfg {String} delimiter
126 * The character(s) used to separate the {@link #displayField display values} of multiple selected items
127 * when <code>{@link #multiSelect} = true</code>. Defaults to <code>', '</code>.
131 <span id='Ext-form-field-ComboBox-cfg-displayField'> /**
132 </span> * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
133 * ComboBox (defaults to 'text').
134 * <p>See also <code>{@link #valueField}</code>.</p>
136 displayField: 'text',
138 <span id='Ext-form-field-ComboBox-cfg-valueField'> /**
139 </span> * @cfg {String} valueField
141 * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
142 * the value of the {@link #displayField} config).
143 * <p><b>Note</b>: use of a <code>valueField</code> requires the user to make a selection in order for a value to be
144 * mapped. See also <code>{@link #displayField}</code>.</p>
147 <span id='Ext-form-field-ComboBox-cfg-triggerAction'> /**
148 </span> * @cfg {String} triggerAction The action to execute when the trigger is clicked.
149 * <div class="mdetail-params"><ul>
150 * <li><b><code>'all'</code></b> : <b>Default</b>
151 * <p class="sub-desc">{@link #doQuery run the query} specified by the <code>{@link #allQuery}</code> config option</p></li>
152 * <li><b><code>'query'</code></b> :
153 * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.</p></li>
154 * </ul></div>
155 * <p>See also <code>{@link #queryParam}</code>.</p>
157 triggerAction: 'all',
159 <span id='Ext-form-field-ComboBox-cfg-allQuery'> /**
160 </span> * @cfg {String} allQuery The text query to send to the server to return all records for the list
161 * with no filtering (defaults to '')
165 <span id='Ext-form-field-ComboBox-cfg-queryParam'> /**
166 </span> * @cfg {String} queryParam Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
167 * <code>{@link #queryMode}: 'remote'</code> (defaults to <code>'query'</code>). If explicitly set to a falsy value it will
172 <span id='Ext-form-field-ComboBox-cfg-queryMode'> /**
173 </span> * @cfg {String} queryMode
174 * The mode in which the ComboBox uses the configured Store. Acceptable values are:
175 * <div class="mdetail-params"><ul>
176 * <li><b><code>'remote'</code></b> : <b>Default</b>
177 * <p>In <code>queryMode: 'remote'</code>, the ComboBox loads its Store dynamically based upon user interaction.</p>
178 * <p>This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link Ext.data.Store#load load}ed.</p>
179 * <p>A parameter containing the typed string is sent in the load request. The default parameter name for the input string is <code>query</code>, but this
180 * can be configured using the {@link #queryParam} config.</p>
181 * <p>In <code>queryMode: 'remote'</code>, the Store may be configured with <code>{@link Ext.data.Store#remoteFilter remoteFilter}: true</code>,
182 * and further filters may be <i>programatically</i> added to the Store which are then passed with every load request which allows the server
183 * to further refine the returned dataset.</p>
184 * <p>Typically, in an autocomplete situation, {@link #hideTrigger} is configured <code>true</code> because it has no meaning for autocomplete.</p></li>
185 * <li><b><code>'local'</code></b> :
186 * <p class="sub-desc">ComboBox loads local data</p>
187 * <pre><code>
188 var combo = new Ext.form.field.ComboBox({
189 renderTo: document.body,
191 store: new Ext.data.ArrayStore({
194 'myId', // numeric value is the key
197 data: [[1, 'item1'], [2, 'item2']] // data is local
200 displayField: 'displayText',
203 * </code></pre></li>
204 * </ul></div>
210 <span id='Ext-form-field-ComboBox-cfg-pageSize'> /**
211 </span> * @cfg {Number} pageSize If greater than <code>0</code>, a {@link Ext.toolbar.Paging} is displayed in the
212 * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
213 * {@link Ext.toolbar.Paging#pageSize limit} parameters. Only applies when <code>{@link #queryMode} = 'remote'</code>
214 * (defaults to <code>0</code>).
218 <span id='Ext-form-field-ComboBox-cfg-queryDelay'> /**
219 </span> * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
220 * sending the query to filter the dropdown list (defaults to <code>500</code> if <code>{@link #queryMode} = 'remote'</code>
221 * or <code>10</code> if <code>{@link #queryMode} = 'local'</code>)
224 <span id='Ext-form-field-ComboBox-cfg-minChars'> /**
225 </span> * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
226 * {@link #typeAhead} activate (defaults to <code>4</code> if <code>{@link #queryMode} = 'remote'</code> or <code>0</code> if
227 * <code>{@link #queryMode} = 'local'</code>, does not apply if <code>{@link Ext.form.field.Trigger#editable editable} = false</code>).
230 <span id='Ext-form-field-ComboBox-cfg-autoSelect'> /**
231 </span> * @cfg {Boolean} autoSelect <code>true</code> to automatically highlight the first result gathered by the data store
232 * in the dropdown list when it is opened. (Defaults to <code>true</code>). A false value would cause nothing in the
233 * list to be highlighted automatically, so the user would have to manually highlight an item before pressing
234 * the enter or {@link #selectOnTab tab} key to select it (unless the value of ({@link #typeAhead}) were true),
235 * or use the mouse to select a value.
239 <span id='Ext-form-field-ComboBox-cfg-typeAhead'> /**
240 </span> * @cfg {Boolean} typeAhead <code>true</code> to populate and autoselect the remainder of the text being
241 * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
242 * to <code>false</code>)
246 <span id='Ext-form-field-ComboBox-cfg-typeAheadDelay'> /**
247 </span> * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
248 * if <code>{@link #typeAhead} = true</code> (defaults to <code>250</code>)
252 <span id='Ext-form-field-ComboBox-cfg-selectOnTab'> /**
253 </span> * @cfg {Boolean} selectOnTab
254 * Whether the Tab key should select the currently highlighted item. Defaults to <code>true</code>.
258 <span id='Ext-form-field-ComboBox-cfg-forceSelection'> /**
259 </span> * @cfg {Boolean} forceSelection <code>true</code> to restrict the selected value to one of the values in the list,
260 * <code>false</code> to allow the user to set arbitrary text into the field (defaults to <code>false</code>)
262 forceSelection: false,
264 <span id='Ext-form-field-ComboBox-cfg-valueNotFoundText'> /**
265 </span> * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
266 * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
267 * default text is used, it means there is no value set and no validation will occur on this field.
270 <span id='Ext-form-field-ComboBox-property-lastQuery'> /**
271 </span> * The value of the match string used to filter the store. Delete this property to force a requery.
273 * <pre><code>
274 var combo = new Ext.form.field.ComboBox({
278 // delete the previous query in the beforequery event or set
279 // combo.lastQuery = null (this will reload the store the next time it expands)
280 beforequery: function(qe){
281 delete qe.combo.lastQuery;
285 * </code></pre>
286 * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
287 * configure the combo with <code>lastQuery=''</code>. Example use:
288 * <pre><code>
289 var combo = new Ext.form.field.ComboBox({
292 triggerAction: 'all',
295 * </code></pre>
296 * @property lastQuery
300 <span id='Ext-form-field-ComboBox-cfg-defaultListConfig'> /**
301 </span> * @cfg {Object} defaultListConfig
302 * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
306 loadingText: 'Loading...',
313 <span id='Ext-form-field-ComboBox-cfg-transform'> /**
314 </span> * @cfg {Mixed} transform
315 * The id, DOM node or {@link Ext.core.Element} of an existing HTML <code>&lt;select&gt;</code> element to
316 * convert into a ComboBox. The target select's options will be used to build the options in the ComboBox
317 * dropdown; a configured {@link #store} will take precedence over this.
320 <span id='Ext-form-field-ComboBox-cfg-listConfig'> /**
321 </span> * @cfg {Object} listConfig
322 * <p>An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s
323 * constructor. Any configuration that is valid for BoundList can be included. Some of the more useful
324 * ones are:</p>
326 * <li>{@link Ext.view.BoundList#cls} - defaults to empty</li>
327 * <li>{@link Ext.view.BoundList#emptyText} - defaults to empty string</li>
328 * <li>{@link Ext.view.BoundList#getInnerTpl} - defaults to the template defined in BoundList</li>
329 * <li>{@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList</li>
330 * <li>{@link Ext.view.BoundList#loadingText} - defaults to <code>'Loading...'</code></li>
331 * <li>{@link Ext.view.BoundList#minWidth} - defaults to <code>70</code></li>
332 * <li>{@link Ext.view.BoundList#maxWidth} - defaults to <code>undefined</code></li>
333 * <li>{@link Ext.view.BoundList#maxHeight} - defaults to <code>300</code></li>
334 * <li>{@link Ext.view.BoundList#resizable} - defaults to <code>false</code></li>
335 * <li>{@link Ext.view.BoundList#shadow} - defaults to <code>'sides'</code></li>
336 * <li>{@link Ext.view.BoundList#width} - defaults to <code>undefined</code> (automatically set to the width
337 * of the ComboBox field if {@link #matchFieldWidth} is true)</li>
344 initComponent: function() {
346 isDefined = Ext.isDefined,
348 transform = me.transform,
349 transformSelect, isLocalMode;
352 if (!store && !transform) {
353 Ext.Error.raise('Either a valid store, or a HTML select to transform, must be configured on the combo.');
355 if (me.typeAhead && me.multiSelect) {
356 Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
358 if (me.typeAhead && !me.editable) {
359 Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
361 if (me.selectOnFocus && !me.editable) {
362 Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
367 // TODO need beforeselect?
369 <span id='Ext-form-field-ComboBox-event-beforequery'> /**
370 </span> * @event beforequery
371 * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
372 * cancel property to true.
373 * @param {Object} queryEvent An object that has these properties:<ul>
374 * <li><code>combo</code> : Ext.form.field.ComboBox <div class="sub-desc">This combo box</div></li>
375 * <li><code>query</code> : String <div class="sub-desc">The query string</div></li>
376 * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
377 * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
384 * Fires when at least one list item is selected.
385 * @param {Ext.form.field.ComboBox} combo This combo box
386 * @param {Array} records The selected records
391 // Build store from 'transform' HTML select element's options
392 if (!store && transform) {
393 transformSelect = Ext.getDom(transform);
394 if (transformSelect) {
395 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
396 return [option.value, option.text];
399 me.name = transformSelect.name;
401 if (!('value' in me)) {
402 me.value = transformSelect.value;
407 me.bindStore(store, true);
409 if (store.autoCreated) {
410 me.queryMode = 'local';
411 me.valueField = me.displayField = 'field1';
412 if (!store.expanded) {
413 me.displayField = 'field2';
418 if (!isDefined(me.valueField)) {
419 me.valueField = me.displayField;
422 isLocalMode = me.queryMode === 'local';
423 if (!isDefined(me.queryDelay)) {
424 me.queryDelay = isLocalMode ? 10 : 500;
426 if (!isDefined(me.minChars)) {
427 me.minChars = isLocalMode ? 0 : 4;
430 if (!me.displayTpl) {
431 me.displayTpl = Ext.create('Ext.XTemplate',
432 '<tpl for=".">' +
433 '{[typeof values === "string" ? values : values.' + me.displayField + ']}' +
434 '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
437 } else if (Ext.isString(me.displayTpl)) {
438 me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
443 me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
445 // store has already been loaded, setValue
446 if (me.store.getCount() > 0) {
447 me.setValue(me.value);
450 // render in place of 'transform' select
451 if (transformSelect) {
452 me.render(transformSelect.parentNode, transformSelect);
453 Ext.removeNode(transformSelect);
458 beforeBlur: function() {
460 me.doQueryTask.cancel();
461 if (me.forceSelection) {
469 assertValue: function() {
471 value = me.getRawValue(),
474 if (me.multiSelect) {
475 // For multiselect, check that the current displayed value matches the current
476 // selection, if it does not then revert to the most recent selection.
477 if (value !== me.getDisplayValue()) {
478 me.setValue(me.lastSelection);
481 // For single-select, match the displayed value to a record and select it,
482 // if it does not match a record then revert to the most recent selection.
483 rec = me.findRecordByDisplay(value);
487 me.setValue(me.lastSelection);
493 onTypeAhead: function() {
495 displayField = me.displayField,
496 record = me.store.findRecord(displayField, me.getRawValue()),
497 boundList = me.getPicker(),
498 newValue, len, selStart;
501 newValue = record.get(displayField);
502 len = newValue.length;
503 selStart = me.getRawValue().length;
505 boundList.highlightItem(boundList.getNode(record));
507 if (selStart !== 0 && selStart !== len) {
508 me.setRawValue(newValue);
509 me.selectText(selStart, newValue.length);
514 // invoked when a different store is bound to this combo
516 resetToDefault: function() {
520 bindStore: function(store, initial) {
524 // this code directly accesses this.picker, bc invoking getPicker
525 // would create it when we may be preping to destroy it
526 if (oldStore && !initial) {
527 if (oldStore !== store && oldStore.autoDestroy) {
533 exception: me.collapse
539 me.picker.bindStore(null);
548 me.store = Ext.data.StoreManager.lookup(store);
552 exception: me.collapse
556 me.picker.bindStore(store);
566 if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
571 <span id='Ext-form-field-ComboBox-method-doRawQuery'> /**
573 * Execute the query with the raw contents within the textfield.
575 doRawQuery: function() {
576 this.doQuery(this.getRawValue());
579 <span id='Ext-form-field-ComboBox-method-doQuery'> /**
580 </span> * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
581 * query allowing the query action to be canceled if needed.
582 * @param {String} queryString The SQL query to execute
583 * @param {Boolean} forceAll <code>true</code> to force the query to execute even if there are currently fewer
584 * characters in the field than the minimum specified by the <code>{@link #minChars}</code> config option. It
585 * also clears any filter previously saved in the current store (defaults to <code>false</code>)
586 * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery} handler.
588 doQuery: function(queryString, forceAll) {
589 queryString = queryString || '';
591 // store in object and pass by reference in 'beforequery'
592 // so that client code can modify values.
601 isLocalMode = me.queryMode === 'local';
603 if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
607 // get back out possibly modified values
608 queryString = qe.query;
609 forceAll = qe.forceAll;
611 // query permitted to run
612 if (forceAll || (queryString.length >= me.minChars)) {
613 // expand before starting query so LoadMask can position itself correctly
616 // make sure they aren't querying the same thing
617 if (!me.queryCaching || me.lastQuery !== queryString) {
618 me.lastQuery = queryString;
621 // forceAll means no filtering - show whole dataset.
625 // Clear filter, but supress event so that the BoundList is not immediately updated.
626 store.clearFilter(true);
627 store.filter(me.displayField, queryString);
630 // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
631 // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
633 params: me.getParams(queryString)
638 // Clear current selection if it does not match the current value in the field
639 if (me.getRawValue() !== me.getDisplayValue()) {
640 me.ignoreSelection++;
641 me.picker.getSelectionModel().deselectAll();
642 me.ignoreSelection--;
656 getParams: function(queryString) {
658 pageSize = this.pageSize,
659 param = this.queryParam;
662 p[param] = queryString;
672 <span id='Ext-form-field-ComboBox-method-doAutoSelect'> /**
674 * If the autoSelect config is true, and the picker is open, highlights the first item.
676 doAutoSelect: function() {
679 lastSelected, itemNode;
680 if (picker && me.autoSelect && me.store.getCount() > 0) {
681 // Highlight the last selected item and scroll it into view
682 lastSelected = picker.getSelectionModel().lastSelected;
683 itemNode = picker.getNode(lastSelected || 0);
685 picker.highlightItem(itemNode);
686 picker.listEl.scrollChildIntoView(itemNode, false);
691 doTypeAhead: function() {
692 if (!this.typeAheadTask) {
693 this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
695 if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
696 this.typeAheadTask.delay(this.typeAheadDelay);
700 onTriggerClick: function() {
702 if (!me.readOnly && !me.disabled) {
707 if (me.triggerAction === 'all') {
708 me.doQuery(me.allQuery, true);
710 me.doQuery(me.getRawValue());
718 // store the last key and doQuery if relevant
719 onKeyUp: function(e, t) {
723 if (!me.readOnly && !me.disabled && me.editable) {
725 // we put this in a task so that we can cancel it if a user is
726 // in and out before the queryDelay elapses
728 // perform query w/ any normal key or backspace or delete
729 if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
730 me.doQueryTask.delay(me.queryDelay);
734 if (me.enableKeyEvents) {
735 me.callParent(arguments);
739 initEvents: function() {
744 * Setup keyboard handling. If enableKeyEvents is true, we already have
745 * a listener on the inputEl for keyup, so don't create a second.
747 if (!me.enableKeyEvents) {
748 me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
752 createPicker: function() {
755 menuCls = Ext.baseCSSPrefix + 'menu',
758 mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
763 cls: me.el.up('.' + menuCls) ? menuCls : '',
765 displayField: me.displayField,
766 focusOnToFront: false,
767 pageSize: me.pageSize,
769 }, me.listConfig, me.defaultListConfig);
771 picker = me.picker = Ext.create('Ext.view.BoundList', opts);
774 itemclick: me.onItemClick,
775 refresh: me.onListRefresh,
779 me.mon(picker.getSelectionModel(), 'selectionchange', me.onListSelectionChange, me);
784 onListRefresh: function() {
786 this.syncSelection();
789 onItemClick: function(picker, record){
791 * If we're doing single selection, the selection change events won't fire when
792 * clicking on the selected element. Detect it here.
795 lastSelection = me.lastSelection,
796 valueField = me.valueField,
799 if (!me.multiSelect && lastSelection) {
800 selected = lastSelection[0];
801 if (selected && (record.get(valueField) === selected.get(valueField))) {
807 onListSelectionChange: function(list, selectedRecords) {
809 isMulti = me.multiSelect,
810 hasRecords = selectedRecords.length > 0;
811 // Only react to selection if it is not called from setValue, and if our list is
812 // expanded (ignores changes to the selection model triggered elsewhere)
813 if (!me.ignoreSelection && me.isExpanded) {
815 Ext.defer(me.collapse, 1, me);
818 * Only set the value here if we're in multi selection mode or we have
819 * a selection. Otherwise setValue will be called with an empty value
820 * which will cause the change event to fire twice.
822 if (isMulti || hasRecords) {
823 me.setValue(selectedRecords, false);
826 me.fireEvent('select', me, selectedRecords);
832 <span id='Ext-form-field-ComboBox-method-onExpand'> /**
834 * Enables the key nav for the BoundList when it is expanded.
836 onExpand: function() {
838 keyNav = me.listKeyNav,
839 selectOnTab = me.selectOnTab,
840 picker = me.getPicker();
842 // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
846 keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
851 this.selectHighlighted(e);
854 // Tab key event is allowed to propagate to field
860 // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
862 me.ignoreMonitorTab = true;
865 Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
869 <span id='Ext-form-field-ComboBox-method-onCollapse'> /**
871 * Disables the key nav for the BoundList when it is collapsed.
873 onCollapse: function() {
875 keyNav = me.listKeyNav;
878 me.ignoreMonitorTab = false;
882 <span id='Ext-form-field-ComboBox-method-select'> /**
883 </span> * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
886 select: function(r) {
887 this.setValue(r, true);
890 <span id='Ext-form-field-ComboBox-method-findRecord'> /**
891 </span> * Find the record by searching for a specific field/value combination
892 * Returns an Ext.data.Record or false
895 findRecord: function(field, value) {
897 idx = ds.findExact(field, value);
898 return idx !== -1 ? ds.getAt(idx) : false;
900 findRecordByValue: function(value) {
901 return this.findRecord(this.valueField, value);
903 findRecordByDisplay: function(value) {
904 return this.findRecord(this.displayField, value);
907 <span id='Ext-form-field-ComboBox-method-setValue'> /**
908 </span> * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
909 * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
910 * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
911 * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
912 * @param {String|Array} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
913 * or an Array of Strings or Models.
914 * @return {Ext.form.field.Field} this
916 setValue: function(value, doSelect) {
918 valueNotFoundText = me.valueNotFoundText,
919 inputEl = me.inputEl,
925 if (me.store.loading) {
926 // Called while the Store is loading. Ensure it is processed by the onLoad method.
931 // This method processes multi-values, so ensure value is an array.
932 value = Ext.Array.from(value);
934 // Loop through values
935 for (i = 0, len = value.length; i < len; i++) {
937 if (!record || !record.isModel) {
938 record = me.findRecordByValue(record);
940 // record found, select it.
943 displayTplData.push(record.data);
944 processedValue.push(record.get(me.valueField));
946 // record was not found, this could happen because
947 // store is not loaded or they set a value not in the store
949 // if valueNotFoundText is defined, display it, otherwise display nothing for this value
950 if (Ext.isDefined(valueNotFoundText)) {
951 displayTplData.push(valueNotFoundText);
953 processedValue.push(value[i]);
957 // Set the value of this field. If we are multiselecting, then that is an array.
958 me.value = me.multiSelect ? processedValue : processedValue[0];
959 if (!Ext.isDefined(me.value)) {
962 me.displayTplData = displayTplData; //store for getDisplayValue method
963 me.lastSelection = me.valueModels = models;
965 if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
966 inputEl.removeCls(me.emptyCls);
969 // Calculate raw value from the collection of Model data
970 me.setRawValue(me.getDisplayValue());
973 if (doSelect !== false) {
981 <span id='Ext-form-field-ComboBox-method-getDisplayValue'> /**
982 </span> * @private Generate the string value to be displayed in the text field for the currently stored value
984 getDisplayValue: function() {
985 return this.displayTpl.apply(this.displayTplData);
988 getValue: function() {
989 // If the user has not changed the raw field value since a value was selected from the list,
990 // then return the structured value from the selection. If the raw field value is different
991 // than what would be displayed due to selection, return that raw value.
994 rawValue = me.getRawValue(), //current value of text field
995 value = me.value; //stored value from last selection or setValue() call
997 if (me.getDisplayValue() !== rawValue) {
999 me.value = me.displayTplData = me.valueModels = null;
1001 me.ignoreSelection++;
1002 picker.getSelectionModel().deselectAll();
1003 me.ignoreSelection--;
1010 getSubmitValue: function() {
1011 return this.getValue();
1014 isEqual: function(v1, v2) {
1015 var fromArray = Ext.Array.from,
1022 if (len !== v2.length) {
1026 for(i = 0; i < len; i++) {
1027 if (v2[i] !== v1[i]) {
1035 <span id='Ext-form-field-ComboBox-method-clearValue'> /**
1036 </span> * Clears any value currently set in the ComboBox.
1038 clearValue: function() {
1042 <span id='Ext-form-field-ComboBox-method-syncSelection'> /**
1043 </span> * @private Synchronizes the selection in the picker to match the current value of the combobox.
1045 syncSelection: function() {
1047 ExtArray = Ext.Array,
1049 selection, selModel;
1051 // From the value, find the Models that are in the store's current data
1053 ExtArray.forEach(me.valueModels || [], function(value) {
1054 if (value && value.isModel && me.store.indexOf(value) >= 0) {
1055 selection.push(value);
1059 // Update the selection to match
1060 me.ignoreSelection++;
1061 selModel = picker.getSelectionModel();
1062 selModel.deselectAll();
1063 if (selection.length) {
1064 selModel.select(selection);
1066 me.ignoreSelection--;