4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/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> * @docauthor Jason Johnston <jason@sencha.com>
21 * A combobox control with support for autocomplete, remote loading, and many other features.
23 * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
24 * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
25 * list. The user can input any value by default, even if it does not appear in the selection list;
26 * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
28 * The selection list's options are populated from any {@link Ext.data.Store}, including remote
29 * stores. The data items in the store are mapped to each option's displayed text and backing value via
30 * the {@link #valueField} and {@link #displayField} configurations, respectively.
32 * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
33 * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
38 * // The data store containing the list of states
39 * var states = Ext.create('Ext.data.Store', {
40 * fields: ['abbr', 'name'],
42 * {"abbr":"AL", "name":"Alabama"},
43 * {"abbr":"AK", "name":"Alaska"},
44 * {"abbr":"AZ", "name":"Arizona"}
49 * // Create the combo box, attached to the states data store
50 * Ext.create('Ext.form.ComboBox', {
51 * fieldLabel: 'Choose State',
54 * displayField: 'name',
56 * renderTo: Ext.getBody()
61 * To do something when something in ComboBox is selected, configure the select event:
63 * var cb = Ext.create('Ext.form.ComboBox', {
64 * // all of your config options
67 * 'select': yourFunction
71 * // Alternatively, you can assign events after the object is created:
72 * var cb = new Ext.form.field.ComboBox(yourOptions);
73 * cb.on('select', yourFunction, yourScope);
75 * # Multiple Selection
77 * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
78 * {@link #multiSelect} config to `true`.
80 Ext.define('Ext.form.field.ComboBox', {
81 extend:'Ext.form.field.Picker',
82 requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
83 alternateClassName: 'Ext.form.ComboBox',
84 alias: ['widget.combobox', 'widget.combo'],
86 <span id='Ext-form-field-ComboBox-cfg-triggerCls'> /**
87 </span> * @cfg {String} [triggerCls='x-form-arrow-trigger']
88 * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
89 * by default and `triggerCls` will be **appended** if specified.
91 triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
93 <span id='Ext-form-field-ComboBox-cfg-hiddenDataCls'> /**
96 * CSS class used to find the {@link #hiddenDataEl}
98 hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
100 <span id='Ext-form-field-ComboBox-property-fieldSubTpl'> /**
104 '<div class="{hiddenDataCls}" role="presentation"></div>',
105 '<input id="{id}" type="{type}" ',
106 '<tpl if="size">size="{size}" </tpl>',
107 '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
108 'class="{fieldCls} {typeCls}" autocomplete="off" />',
109 '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
111 '<div class="{clearCls}" role="presentation"></div>',
119 getSubTplData: function(){
121 Ext.applyIf(me.subTplData, {
122 hiddenDataCls: me.hiddenDataCls
124 return me.callParent(arguments);
127 afterRender: function(){
129 me.callParent(arguments);
130 me.setHiddenValue(me.value);
133 <span id='Ext-form-field-ComboBox-cfg-store'> /**
134 </span> * @cfg {Ext.data.Store/Array} store
135 * The data source to which this combo is bound. Acceptable values for this property are:
137 * - **any {@link Ext.data.Store Store} subclass**
138 * - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
139 * {@link Ext.data.Field#name field names} to work with all data components.
141 * - **1-dimensional array** : (e.g., `['Foo','Bar']`)
143 * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
144 * {@link #valueField} and {@link #displayField})
146 * - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
148 * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
149 * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
151 * See also {@link #queryMode}.
154 <span id='Ext-form-field-ComboBox-cfg-multiSelect'> /**
155 </span> * @cfg {Boolean} multiSelect
156 * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
157 * items from the dropdown list. The combo's text field will show all selected values separated by the
158 * {@link #delimiter}.
162 <span id='Ext-form-field-ComboBox-cfg-delimiter'> /**
163 </span> * @cfg {String} delimiter
164 * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
165 * `{@link #multiSelect} = true`.
169 <span id='Ext-form-field-ComboBox-cfg-displayField'> /**
170 </span> * @cfg {String} displayField
171 * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
173 * See also `{@link #valueField}`.
175 displayField: 'text',
177 <span id='Ext-form-field-ComboBox-cfg-valueField'> /**
178 </span> * @cfg {String} valueField (required)
179 * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
180 * the value of the {@link #displayField} config).
182 * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
183 * `{@link #displayField}`.
186 <span id='Ext-form-field-ComboBox-cfg-triggerAction'> /**
187 </span> * @cfg {String} triggerAction
188 * The action to execute when the trigger is clicked.
192 * {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
196 * {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
198 * See also `{@link #queryParam}`.
200 triggerAction: 'all',
202 <span id='Ext-form-field-ComboBox-cfg-allQuery'> /**
203 </span> * @cfg {String} allQuery
204 * The text query to send to the server to return all records for the list with no filtering
208 <span id='Ext-form-field-ComboBox-cfg-queryParam'> /**
209 </span> * @cfg {String} queryParam
210 * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
211 * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
215 <span id='Ext-form-field-ComboBox-cfg-queryMode'> /**
216 </span> * @cfg {String} queryMode
217 * The mode in which the ComboBox uses the configured Store. Acceptable values are:
221 * In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
223 * This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
224 * Ext.data.Store#load load}ed.
226 * A parameter containing the typed string is sent in the load request. The default parameter name for the input
227 * string is `query`, but this can be configured using the {@link #queryParam} config.
229 * In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
230 * true`, and further filters may be _programatically_ added to the Store which are then passed with every load
231 * request which allows the server to further refine the returned dataset.
233 * Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
238 * ComboBox loads local data
240 * var combo = new Ext.form.field.ComboBox({
241 * renderTo: document.body,
242 * queryMode: 'local',
243 * store: new Ext.data.ArrayStore({
246 * 'myId', // numeric value is the key
249 * data: [[1, 'item1'], [2, 'item2']] // data is local
251 * valueField: 'myId',
252 * displayField: 'displayText',
253 * triggerAction: 'all'
260 <span id='Ext-form-field-ComboBox-cfg-pageSize'> /**
261 </span> * @cfg {Number} pageSize
262 * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
263 * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
264 * parameters. Only applies when `{@link #queryMode} = 'remote'`.
268 <span id='Ext-form-field-ComboBox-cfg-queryDelay'> /**
269 </span> * @cfg {Number} queryDelay
270 * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
271 * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
274 <span id='Ext-form-field-ComboBox-cfg-minChars'> /**
275 </span> * @cfg {Number} minChars
276 * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
277 * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
278 * `{@link Ext.form.field.Trigger#editable editable} = false`).
281 <span id='Ext-form-field-ComboBox-cfg-autoSelect'> /**
282 </span> * @cfg {Boolean} autoSelect
283 * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
284 * opened. A false value would cause nothing in the list to be highlighted automatically, so
285 * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
286 * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
290 <span id='Ext-form-field-ComboBox-cfg-typeAhead'> /**
291 </span> * @cfg {Boolean} typeAhead
292 * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
293 * ({@link #typeAheadDelay}) if it matches a known value.
297 <span id='Ext-form-field-ComboBox-cfg-typeAheadDelay'> /**
298 </span> * @cfg {Number} typeAheadDelay
299 * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
303 <span id='Ext-form-field-ComboBox-cfg-selectOnTab'> /**
304 </span> * @cfg {Boolean} selectOnTab
305 * Whether the Tab key should select the currently highlighted item.
309 <span id='Ext-form-field-ComboBox-cfg-forceSelection'> /**
310 </span> * @cfg {Boolean} forceSelection
311 * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
312 * arbitrary text into the field.
314 forceSelection: false,
316 <span id='Ext-form-field-ComboBox-cfg-valueNotFoundText'> /**
317 </span> * @cfg {String} valueNotFoundText
318 * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
319 * be displayed as the field text if defined. If this default text is used, it means there
320 * is no value set and no validation will occur on this field.
323 <span id='Ext-form-field-ComboBox-property-lastQuery'> /**
324 </span> * @property {String} lastQuery
325 * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
327 * var combo = new Ext.form.field.ComboBox({
329 * queryMode: 'remote',
331 * // delete the previous query in the beforequery event or set
332 * // combo.lastQuery = null (this will reload the store the next time it expands)
333 * beforequery: function(qe){
334 * delete qe.combo.lastQuery;
339 * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
340 * combo with `lastQuery=''`. Example use:
342 * var combo = new Ext.form.field.ComboBox({
344 * queryMode: 'local',
345 * triggerAction: 'all',
350 <span id='Ext-form-field-ComboBox-cfg-defaultListConfig'> /**
351 </span> * @cfg {Object} defaultListConfig
352 * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
356 loadingText: 'Loading...',
363 <span id='Ext-form-field-ComboBox-cfg-transform'> /**
364 </span> * @cfg {String/HTMLElement/Ext.Element} transform
365 * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
366 * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
367 * will take precedence over this.
370 <span id='Ext-form-field-ComboBox-cfg-listConfig'> /**
371 </span> * @cfg {Object} listConfig
372 * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
373 * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
375 * - {@link Ext.view.BoundList#cls} - defaults to empty
376 * - {@link Ext.view.BoundList#emptyText} - defaults to empty string
377 * - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
378 * - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
379 * - {@link Ext.view.BoundList#minWidth} - defaults to `70`
380 * - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
381 * - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
382 * - {@link Ext.view.BoundList#resizable} - defaults to `false`
383 * - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
384 * - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
385 * field if {@link #matchFieldWidth} is true)
391 initComponent: function() {
393 isDefined = Ext.isDefined,
395 transform = me.transform,
396 transformSelect, isLocalMode;
398 Ext.applyIf(me.renderSelectors, {
399 hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
403 if (me.typeAhead && me.multiSelect) {
404 Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
406 if (me.typeAhead && !me.editable) {
407 Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
409 if (me.selectOnFocus && !me.editable) {
410 Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
415 <span id='Ext-form-field-ComboBox-event-beforequery'> /**
416 </span> * @event beforequery
417 * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
420 * @param {Object} queryEvent An object that has these properties:
422 * - `combo` : Ext.form.field.ComboBox
430 * - `forceAll` : Boolean
432 * True to force "all" query
434 * - `cancel` : Boolean
436 * Set to true to cancel the query
440 <span id='Ext-form-field-ComboBox-event-select'> /**
441 </span> * @event select
442 * Fires when at least one list item is selected.
443 * @param {Ext.form.field.ComboBox} combo This combo box
444 * @param {Array} records The selected records
448 <span id='Ext-form-field-ComboBox-event-beforeselect'> /**
449 </span> * @event beforeselect
450 * Fires before the selected item is added to the collection
451 * @param {Ext.form.field.ComboBox} combo This combo box
452 * @param {Ext.data.Record} record The selected record
453 * @param {Number} index The index of the selected record
457 <span id='Ext-form-field-ComboBox-event-beforedeselect'> /**
458 </span> * @event beforedeselect
459 * Fires before the deselected item is removed from the collection
460 * @param {Ext.form.field.ComboBox} combo This combo box
461 * @param {Ext.data.Record} record The deselected record
462 * @param {Number} index The index of the deselected record
467 // Build store from 'transform' HTML select element's options
469 transformSelect = Ext.getDom(transform);
470 if (transformSelect) {
471 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
472 return [option.value, option.text];
475 me.name = transformSelect.name;
477 if (!('value' in me)) {
478 me.value = transformSelect.value;
483 me.bindStore(store || 'ext-empty-store', true);
485 if (store.autoCreated) {
486 me.queryMode = 'local';
487 me.valueField = me.displayField = 'field1';
488 if (!store.expanded) {
489 me.displayField = 'field2';
494 if (!isDefined(me.valueField)) {
495 me.valueField = me.displayField;
498 isLocalMode = me.queryMode === 'local';
499 if (!isDefined(me.queryDelay)) {
500 me.queryDelay = isLocalMode ? 10 : 500;
502 if (!isDefined(me.minChars)) {
503 me.minChars = isLocalMode ? 0 : 4;
506 if (!me.displayTpl) {
507 me.displayTpl = Ext.create('Ext.XTemplate',
508 '<tpl for=".">' +
509 '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
510 '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
513 } else if (Ext.isString(me.displayTpl)) {
514 me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
519 me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
521 // store has already been loaded, setValue
522 if (me.store.getCount() > 0) {
523 me.setValue(me.value);
526 // render in place of 'transform' select
527 if (transformSelect) {
528 me.render(transformSelect.parentNode, transformSelect);
529 Ext.removeNode(transformSelect);
534 <span id='Ext-form-field-ComboBox-method-getStore'> /**
535 </span> * Returns the store associated with this ComboBox.
536 * @return {Ext.data.Store} The store
538 getStore : function(){
542 beforeBlur: function() {
543 this.doQueryTask.cancel();
548 assertValue: function() {
550 value = me.getRawValue(),
553 if (me.forceSelection) {
554 if (me.multiSelect) {
555 // For multiselect, check that the current displayed value matches the current
556 // selection, if it does not then revert to the most recent selection.
557 if (value !== me.getDisplayValue()) {
558 me.setValue(me.lastSelection);
561 // For single-select, match the displayed value to a record and select it,
562 // if it does not match a record then revert to the most recent selection.
563 rec = me.findRecordByDisplay(value);
567 me.setValue(me.lastSelection);
574 onTypeAhead: function() {
576 displayField = me.displayField,
577 record = me.store.findRecord(displayField, me.getRawValue()),
578 boundList = me.getPicker(),
579 newValue, len, selStart;
582 newValue = record.get(displayField);
583 len = newValue.length;
584 selStart = me.getRawValue().length;
586 boundList.highlightItem(boundList.getNode(record));
588 if (selStart !== 0 && selStart !== len) {
589 me.setRawValue(newValue);
590 me.selectText(selStart, newValue.length);
595 // invoked when a different store is bound to this combo
597 resetToDefault: function() {
601 bindStore: function(store, initial) {
605 // this code directly accesses this.picker, bc invoking getPicker
606 // would create it when we may be preping to destroy it
607 if (oldStore && !initial) {
608 if (oldStore !== store && oldStore.autoDestroy) {
609 oldStore.destroyStore();
614 exception: me.collapse
620 me.picker.bindStore(null);
629 me.store = Ext.data.StoreManager.lookup(store);
633 exception: me.collapse
637 me.picker.bindStore(store);
646 // If performing a remote query upon the raw value...
650 if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
654 // If store initial load or triggerAction: 'all' trigger click.
656 // Set the value on load
658 me.setValue(me.value);
661 // Highlight the first item in the list if autoSelect: true
662 if (me.store.getCount()) {
671 <span id='Ext-form-field-ComboBox-method-doRawQuery'> /**
673 * Execute the query with the raw contents within the textfield.
675 doRawQuery: function() {
676 this.doQuery(this.getRawValue(), false, true);
679 <span id='Ext-form-field-ComboBox-method-doQuery'> /**
680 </span> * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
681 * allowing the query action to be canceled if needed.
683 * @param {String} queryString The SQL query to execute
684 * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
685 * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
686 * previously saved in the current store.
687 * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
688 * resulting store load to leave the raw value undisturbed.
689 * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
692 doQuery: function(queryString, forceAll, rawQuery) {
693 queryString = queryString || '';
695 // store in object and pass by reference in 'beforequery'
696 // so that client code can modify values.
705 isLocalMode = me.queryMode === 'local';
707 if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
711 // get back out possibly modified values
712 queryString = qe.query;
713 forceAll = qe.forceAll;
715 // query permitted to run
716 if (forceAll || (queryString.length >= me.minChars)) {
717 // expand before starting query so LoadMask can position itself correctly
720 // make sure they aren't querying the same thing
721 if (!me.queryCaching || me.lastQuery !== queryString) {
722 me.lastQuery = queryString;
725 // forceAll means no filtering - show whole dataset.
729 // Clear filter, but supress event so that the BoundList is not immediately updated.
730 store.clearFilter(true);
731 store.filter(me.displayField, queryString);
734 // Set flag for onLoad handling to know how the Store was loaded
735 me.rawQuery = rawQuery;
737 // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
738 // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
740 // if we're paging, we've changed the query so start at page 1.
744 params: me.getParams(queryString)
750 // Clear current selection if it does not match the current value in the field
751 if (me.getRawValue() !== me.getDisplayValue()) {
752 me.ignoreSelection++;
753 me.picker.getSelectionModel().deselectAll();
754 me.ignoreSelection--;
767 loadPage: function(pageNum){
768 this.store.loadPage(pageNum, {
769 params: this.getParams(this.lastQuery)
773 onPageChange: function(toolbar, newPage){
775 * Return false here so we can call load ourselves and inject the query param.
776 * We don't want to do this for every store load since the developer may load
777 * the store through some other means so we won't add the query param.
779 this.loadPage(newPage);
784 getParams: function(queryString) {
786 param = this.queryParam;
789 params[param] = queryString;
794 <span id='Ext-form-field-ComboBox-method-doAutoSelect'> /**
796 * If the autoSelect config is true, and the picker is open, highlights the first item.
798 doAutoSelect: function() {
801 lastSelected, itemNode;
802 if (picker && me.autoSelect && me.store.getCount() > 0) {
803 // Highlight the last selected item and scroll it into view
804 lastSelected = picker.getSelectionModel().lastSelected;
805 itemNode = picker.getNode(lastSelected || 0);
807 picker.highlightItem(itemNode);
808 picker.listEl.scrollChildIntoView(itemNode, false);
813 doTypeAhead: function() {
814 if (!this.typeAheadTask) {
815 this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
817 if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
818 this.typeAheadTask.delay(this.typeAheadDelay);
822 onTriggerClick: function() {
824 if (!me.readOnly && !me.disabled) {
829 if (me.triggerAction === 'all') {
830 me.doQuery(me.allQuery, true);
832 me.doQuery(me.getRawValue(), false, true);
840 // store the last key and doQuery if relevant
841 onKeyUp: function(e, t) {
845 if (!me.readOnly && !me.disabled && me.editable) {
847 // we put this in a task so that we can cancel it if a user is
848 // in and out before the queryDelay elapses
850 // perform query w/ any normal key or backspace or delete
851 if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
852 me.doQueryTask.delay(me.queryDelay);
856 if (me.enableKeyEvents) {
857 me.callParent(arguments);
861 initEvents: function() {
866 * Setup keyboard handling. If enableKeyEvents is true, we already have
867 * a listener on the inputEl for keyup, so don't create a second.
869 if (!me.enableKeyEvents) {
870 me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
874 onDestroy: function(){
875 this.bindStore(null);
879 createPicker: function() {
882 menuCls = Ext.baseCSSPrefix + 'menu',
886 mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
891 cls: me.el.up('.' + menuCls) ? menuCls : '',
893 displayField: me.displayField,
894 focusOnToFront: false,
895 pageSize: me.pageSize,
897 }, me.listConfig, me.defaultListConfig);
899 picker = me.picker = Ext.create('Ext.view.BoundList', opts);
901 picker.pagingToolbar.on('beforechange', me.onPageChange, me);
905 itemclick: me.onItemClick,
906 refresh: me.onListRefresh,
910 me.mon(picker.getSelectionModel(), {
911 'beforeselect': me.onBeforeSelect,
912 'beforedeselect': me.onBeforeDeselect,
913 'selectionchange': me.onListSelectionChange,
920 alignPicker: function(){
923 heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
924 heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
925 space = Math.max(heightAbove, heightBelow);
928 if (picker.getHeight() > space) {
929 picker.setHeight(space - 5); // have some leeway so we aren't flush against
934 onListRefresh: function() {
936 this.syncSelection();
939 onItemClick: function(picker, record){
941 * If we're doing single selection, the selection change events won't fire when
942 * clicking on the selected element. Detect it here.
945 lastSelection = me.lastSelection,
946 valueField = me.valueField,
949 if (!me.multiSelect && lastSelection) {
950 selected = lastSelection[0];
951 if (selected && (record.get(valueField) === selected.get(valueField))) {
952 // Make sure we also update the display value if it's only partial
953 me.displayTplData = [record.data];
954 me.setRawValue(me.getDisplayValue());
960 onBeforeSelect: function(list, record) {
961 return this.fireEvent('beforeselect', this, record, record.index);
964 onBeforeDeselect: function(list, record) {
965 return this.fireEvent('beforedeselect', this, record, record.index);
968 onListSelectionChange: function(list, selectedRecords) {
970 isMulti = me.multiSelect,
971 hasRecords = selectedRecords.length > 0;
972 // Only react to selection if it is not called from setValue, and if our list is
973 // expanded (ignores changes to the selection model triggered elsewhere)
974 if (!me.ignoreSelection && me.isExpanded) {
976 Ext.defer(me.collapse, 1, me);
979 * Only set the value here if we're in multi selection mode or we have
980 * a selection. Otherwise setValue will be called with an empty value
981 * which will cause the change event to fire twice.
983 if (isMulti || hasRecords) {
984 me.setValue(selectedRecords, false);
987 me.fireEvent('select', me, selectedRecords);
993 <span id='Ext-form-field-ComboBox-method-onExpand'> /**
995 * Enables the key nav for the BoundList when it is expanded.
997 onExpand: function() {
999 keyNav = me.listKeyNav,
1000 selectOnTab = me.selectOnTab,
1001 picker = me.getPicker();
1003 // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
1007 keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
1012 this.selectHighlighted(e);
1015 // Tab key event is allowed to propagate to field
1021 // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
1023 me.ignoreMonitorTab = true;
1026 Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
1030 <span id='Ext-form-field-ComboBox-method-onCollapse'> /**
1032 * Disables the key nav for the BoundList when it is collapsed.
1034 onCollapse: function() {
1036 keyNav = me.listKeyNav;
1039 me.ignoreMonitorTab = false;
1043 <span id='Ext-form-field-ComboBox-method-select'> /**
1044 </span> * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
1047 select: function(r) {
1048 this.setValue(r, true);
1051 <span id='Ext-form-field-ComboBox-method-findRecord'> /**
1052 </span> * Finds the record by searching for a specific field/value combination.
1053 * @param {String} field The name of the field to test.
1054 * @param {Object} value The value to match the field against.
1055 * @return {Ext.data.Model} The matched record or false.
1057 findRecord: function(field, value) {
1058 var ds = this.store,
1059 idx = ds.findExact(field, value);
1060 return idx !== -1 ? ds.getAt(idx) : false;
1063 <span id='Ext-form-field-ComboBox-method-findRecordByValue'> /**
1064 </span> * Finds the record by searching values in the {@link #valueField}.
1065 * @param {Object} value The value to match the field against.
1066 * @return {Ext.data.Model} The matched record or false.
1068 findRecordByValue: function(value) {
1069 return this.findRecord(this.valueField, value);
1072 <span id='Ext-form-field-ComboBox-method-findRecordByDisplay'> /**
1073 </span> * Finds the record by searching values in the {@link #displayField}.
1074 * @param {Object} value The value to match the field against.
1075 * @return {Ext.data.Model} The matched record or false.
1077 findRecordByDisplay: function(value) {
1078 return this.findRecord(this.displayField, value);
1081 <span id='Ext-form-field-ComboBox-method-setValue'> /**
1082 </span> * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
1083 * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
1084 * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
1085 * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
1086 * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
1087 * or an Array of Strings or Models.
1088 * @return {Ext.form.field.Field} this
1090 setValue: function(value, doSelect) {
1092 valueNotFoundText = me.valueNotFoundText,
1093 inputEl = me.inputEl,
1096 displayTplData = [],
1097 processedValue = [];
1099 if (me.store.loading) {
1100 // Called while the Store is loading. Ensure it is processed by the onLoad method.
1102 me.setHiddenValue(me.value);
1106 // This method processes multi-values, so ensure value is an array.
1107 value = Ext.Array.from(value);
1109 // Loop through values
1110 for (i = 0, len = value.length; i < len; i++) {
1112 if (!record || !record.isModel) {
1113 record = me.findRecordByValue(record);
1115 // record found, select it.
1117 models.push(record);
1118 displayTplData.push(record.data);
1119 processedValue.push(record.get(me.valueField));
1121 // record was not found, this could happen because
1122 // store is not loaded or they set a value not in the store
1124 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
1125 if (!me.forceSelection) {
1126 displayTplData.push(value[i]);
1127 processedValue.push(value[i]);
1129 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
1130 else if (Ext.isDefined(valueNotFoundText)) {
1131 displayTplData.push(valueNotFoundText);
1136 // Set the value of this field. If we are multiselecting, then that is an array.
1137 me.setHiddenValue(processedValue);
1138 me.value = me.multiSelect ? processedValue : processedValue[0];
1139 if (!Ext.isDefined(me.value)) {
1142 me.displayTplData = displayTplData; //store for getDisplayValue method
1143 me.lastSelection = me.valueModels = models;
1145 if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
1146 inputEl.removeCls(me.emptyCls);
1149 // Calculate raw value from the collection of Model data
1150 me.setRawValue(me.getDisplayValue());
1153 if (doSelect !== false) {
1156 me.applyEmptyText();
1161 <span id='Ext-form-field-ComboBox-method-setHiddenValue'> /**
1163 * Set the value of {@link #hiddenDataEl}
1164 * Dynamically adds and removes input[type=hidden] elements
1166 setHiddenValue: function(values){
1168 if (!me.hiddenDataEl) {
1171 values = Ext.Array.from(values);
1172 var dom = me.hiddenDataEl.dom,
1173 childNodes = dom.childNodes,
1174 input = childNodes[0],
1175 valueCount = values.length,
1176 childrenCount = childNodes.length;
1178 if (!input && valueCount > 0) {
1179 me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
1181 input = dom.firstChild;
1183 while (childrenCount > valueCount) {
1184 dom.removeChild(childNodes[0]);
1187 while (childrenCount < valueCount) {
1188 dom.appendChild(input.cloneNode(true));
1191 for (i = 0; i < valueCount; i++) {
1192 childNodes[i].value = values[i];
1196 <span id='Ext-form-field-ComboBox-method-getDisplayValue'> /**
1197 </span> * @private Generates the string value to be displayed in the text field for the currently stored value
1199 getDisplayValue: function() {
1200 return this.displayTpl.apply(this.displayTplData);
1203 getValue: function() {
1204 // If the user has not changed the raw field value since a value was selected from the list,
1205 // then return the structured value from the selection. If the raw field value is different
1206 // than what would be displayed due to selection, return that raw value.
1209 rawValue = me.getRawValue(), //current value of text field
1210 value = me.value; //stored value from last selection or setValue() call
1212 if (me.getDisplayValue() !== rawValue) {
1214 me.value = me.displayTplData = me.valueModels = null;
1216 me.ignoreSelection++;
1217 picker.getSelectionModel().deselectAll();
1218 me.ignoreSelection--;
1225 getSubmitValue: function() {
1226 return this.getValue();
1229 isEqual: function(v1, v2) {
1230 var fromArray = Ext.Array.from,
1237 if (len !== v2.length) {
1241 for(i = 0; i < len; i++) {
1242 if (v2[i] !== v1[i]) {
1250 <span id='Ext-form-field-ComboBox-method-clearValue'> /**
1251 </span> * Clears any value currently set in the ComboBox.
1253 clearValue: function() {
1257 <span id='Ext-form-field-ComboBox-method-syncSelection'> /**
1258 </span> * @private Synchronizes the selection in the picker to match the current value of the combobox.
1260 syncSelection: function() {
1262 ExtArray = Ext.Array,
1264 selection, selModel;
1266 // From the value, find the Models that are in the store's current data
1268 ExtArray.forEach(me.valueModels || [], function(value) {
1269 if (value && value.isModel && me.store.indexOf(value) >= 0) {
1270 selection.push(value);
1274 // Update the selection to match
1275 me.ignoreSelection++;
1276 selModel = picker.getSelectionModel();
1277 selModel.deselectAll();
1278 if (selection.length) {
1279 selModel.select(selection);
1281 me.ignoreSelection--;