Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / ComboBox.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-form.field.ComboBox-method-constructor'><span id='Ext-form.field.ComboBox'>/**
2 </span></span> * @class Ext.form.field.ComboBox
3  * @extends Ext.form.field.Picker
4  *
5  * A combobox control with support for autocomplete, remote loading, and many other features.
6  *
7  * A ComboBox is like a combination of a traditional HTML text `&amp;lt;input&amp;gt;` field and a `&amp;lt;select&amp;gt;`
8  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
9  * list. The user can input any value by default, even if it does not appear in the selection list;
10  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
11  *
12  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
13  * stores. The data items in the store are mapped to each option's displayed text and backing value via
14  * the {@link #valueField} and {@link #displayField} configurations, respectively.
15  *
16  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
17  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
18  *
19  * {@img Ext.form.ComboBox/Ext.form.ComboBox.png Ext.form.ComboBox component}
20  *
21  * ## Example usage:
22  *
23  *     // The data store containing the list of states
24  *     var states = Ext.create('Ext.data.Store', {
25  *         fields: ['abbr', 'name'],
26  *         data : [
27  *             {&quot;abbr&quot;:&quot;AL&quot;, &quot;name&quot;:&quot;Alabama&quot;},
28  *             {&quot;abbr&quot;:&quot;AK&quot;, &quot;name&quot;:&quot;Alaska&quot;},
29  *             {&quot;abbr&quot;:&quot;AZ&quot;, &quot;name&quot;:&quot;Arizona&quot;}
30  *             //...
31  *         ]
32  *     });
33  *
34  *     // Create the combo box, attached to the states data store
35  *     Ext.create('Ext.form.ComboBox', {
36  *         fieldLabel: 'Choose State',
37  *         store: states,
38  *         queryMode: 'local',
39  *         displayField: 'name',
40  *         valueField: 'abbr',
41  *         renderTo: Ext.getBody()
42  *     });
43  *
44  * ## Events
45  *
46  * To do something when something in ComboBox is selected, configure the select event:
47  *
48  *     var cb = Ext.create('Ext.form.ComboBox', {
49  *         // all of your config options
50  *         listeners:{
51  *              scope: yourScope,
52  *              'select': yourFunction
53  *         }
54  *     });
55  *
56  *     // Alternatively, you can assign events after the object is created:
57  *     var cb = new Ext.form.field.ComboBox(yourOptions);
58  *     cb.on('select', yourFunction, yourScope);
59  *
60  * ## Multiple Selection
61  *
62  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
63  * {@link #multiSelect} config to `true`.
64  *
65  * @constructor
66  * Create a new ComboBox.
67  * @param {Object} config Configuration options
68  * @xtype combo
69  * @docauthor Jason Johnston &lt;jason@sencha.com&gt;
70  */
71 Ext.define('Ext.form.field.ComboBox', {
72     extend:'Ext.form.field.Picker',
73     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
74     alternateClassName: 'Ext.form.ComboBox',
75     alias: ['widget.combobox', 'widget.combo'],
76
77 <span id='Ext-form.field.ComboBox-cfg-triggerCls'>    /**
78 </span>     * @cfg {String} triggerCls
79      * An additional CSS class used to style the trigger button. The trigger will always get the
80      * {@link #triggerBaseCls} by default and &lt;tt&gt;triggerCls&lt;/tt&gt; will be &lt;b&gt;appended&lt;/b&gt; if specified.
81      * Defaults to 'x-form-arrow-trigger' for ComboBox.
82      */
83     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
84
85 <span id='Ext-form.field.ComboBox-cfg-store'>    /**
86 </span>     * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to &lt;tt&gt;undefined&lt;/tt&gt;).
87      * Acceptable values for this property are:
88      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
89      * &lt;li&gt;&lt;b&gt;any {@link Ext.data.Store Store} subclass&lt;/b&gt;&lt;/li&gt;
90      * &lt;li&gt;&lt;b&gt;an Array&lt;/b&gt; : Arrays will be converted to a {@link Ext.data.Store} internally,
91      * automatically generating {@link Ext.data.Field#name field names} to work with all data components.
92      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
93      * &lt;li&gt;&lt;b&gt;1-dimensional array&lt;/b&gt; : (e.g., &lt;tt&gt;['Foo','Bar']&lt;/tt&gt;)&lt;div class=&quot;sub-desc&quot;&gt;
94      * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
95      * {@link #valueField} and {@link #displayField})&lt;/div&gt;&lt;/li&gt;
96      * &lt;li&gt;&lt;b&gt;2-dimensional array&lt;/b&gt; : (e.g., &lt;tt&gt;[['f','Foo'],['b','Bar']]&lt;/tt&gt;)&lt;div class=&quot;sub-desc&quot;&gt;
97      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
98      * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
99      * &lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
100      * &lt;p&gt;See also &lt;tt&gt;{@link #queryMode}&lt;/tt&gt;.&lt;/p&gt;
101      */
102
103 <span id='Ext-form.field.ComboBox-cfg-multiSelect'>    /**
104 </span>     * @cfg {Boolean} multiSelect
105      * If set to &lt;tt&gt;true&lt;/tt&gt;, allows the combo field to hold more than one value at a time, and allows selecting
106      * multiple items from the dropdown list. The combo's text field will show all selected values separated by
107      * the {@link #delimiter}. (Defaults to &lt;tt&gt;false&lt;/tt&gt;.)
108      */
109     multiSelect: false,
110
111 <span id='Ext-form.field.ComboBox-cfg-delimiter'>    /**
112 </span>     * @cfg {String} delimiter
113      * The character(s) used to separate the {@link #displayField display values} of multiple selected items
114      * when &lt;tt&gt;{@link #multiSelect} = true&lt;/tt&gt;. Defaults to &lt;tt&gt;', '&lt;/tt&gt;.
115      */
116     delimiter: ', ',
117
118 <span id='Ext-form.field.ComboBox-cfg-displayField'>    /**
119 </span>     * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
120      * ComboBox (defaults to 'text').
121      * &lt;p&gt;See also &lt;tt&gt;{@link #valueField}&lt;/tt&gt;.&lt;/p&gt;
122      */
123     displayField: 'text',
124
125 <span id='Ext-form.field.ComboBox-cfg-valueField'>    /**
126 </span>     * @cfg {String} valueField
127      * @required
128      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
129      * the value of the {@link #displayField} config).
130      * &lt;p&gt;&lt;b&gt;Note&lt;/b&gt;: use of a &lt;tt&gt;valueField&lt;/tt&gt; requires the user to make a selection in order for a value to be
131      * mapped. See also &lt;tt&gt;{@link #displayField}&lt;/tt&gt;.&lt;/p&gt;
132      */
133
134 <span id='Ext-form.field.ComboBox-cfg-triggerAction'>    /**
135 </span>     * @cfg {String} triggerAction The action to execute when the trigger is clicked.
136      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
137      * &lt;li&gt;&lt;b&gt;&lt;tt&gt;'all'&lt;/tt&gt;&lt;/b&gt; : &lt;b&gt;Default&lt;/b&gt;
138      * &lt;p class=&quot;sub-desc&quot;&gt;{@link #doQuery run the query} specified by the &lt;tt&gt;{@link #allQuery}&lt;/tt&gt; config option&lt;/p&gt;&lt;/li&gt;
139      * &lt;li&gt;&lt;b&gt;&lt;tt&gt;'query'&lt;/tt&gt;&lt;/b&gt; :
140      * &lt;p class=&quot;sub-desc&quot;&gt;{@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.&lt;/p&gt;&lt;/li&gt;
141      * &lt;/ul&gt;&lt;/div&gt;
142      * &lt;p&gt;See also &lt;code&gt;{@link #queryParam}&lt;/code&gt;.&lt;/p&gt;
143      */
144     triggerAction: 'all',
145
146 <span id='Ext-form.field.ComboBox-cfg-allQuery'>    /**
147 </span>     * @cfg {String} allQuery The text query to send to the server to return all records for the list
148      * with no filtering (defaults to '')
149      */
150     allQuery: '',
151
152 <span id='Ext-form.field.ComboBox-cfg-queryParam'>    /**
153 </span>     * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store)
154      * as it will be passed on the querystring (defaults to &lt;tt&gt;'query'&lt;/tt&gt;)
155      */
156     queryParam: 'query',
157
158 <span id='Ext-form.field.ComboBox-cfg-queryMode'>    /**
159 </span>     * @cfg {String} queryMode
160      * The mode for queries. Acceptable values are:
161      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
162      * &lt;li&gt;&lt;b&gt;&lt;tt&gt;'remote'&lt;/tt&gt;&lt;/b&gt; : &lt;b&gt;Default&lt;/b&gt;
163      * &lt;p class=&quot;sub-desc&quot;&gt;Automatically loads the &lt;tt&gt;{@link #store}&lt;/tt&gt; the &lt;b&gt;first&lt;/b&gt; time the trigger
164      * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
165      * clicked, set to &lt;tt&gt;'local'&lt;/tt&gt; and manually load the store.  To force a requery of the store
166      * &lt;b&gt;every&lt;/b&gt; time the trigger is clicked see &lt;tt&gt;{@link #lastQuery}&lt;/tt&gt;.&lt;/p&gt;&lt;/li&gt;
167      * &lt;li&gt;&lt;b&gt;&lt;tt&gt;'local'&lt;/tt&gt;&lt;/b&gt; :
168      * &lt;p class=&quot;sub-desc&quot;&gt;ComboBox loads local data&lt;/p&gt;
169      * &lt;pre&gt;&lt;code&gt;
170 var combo = new Ext.form.field.ComboBox({
171     renderTo: document.body,
172     queryMode: 'local',
173     store: new Ext.data.ArrayStore({
174         id: 0,
175         fields: [
176             'myId',  // numeric value is the key
177             'displayText'
178         ],
179         data: [[1, 'item1'], [2, 'item2']]  // data is local
180     }),
181     valueField: 'myId',
182     displayField: 'displayText',
183     triggerAction: 'all'
184 });
185      * &lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
186      * &lt;/ul&gt;&lt;/div&gt;
187      */
188     queryMode: 'remote',
189
190     queryCaching: true,
191
192 <span id='Ext-form.field.ComboBox-cfg-pageSize'>    /**
193 </span>     * @cfg {Number} pageSize If greater than &lt;tt&gt;0&lt;/tt&gt;, a {@link Ext.toolbar.Paging} is displayed in the
194      * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
195      * {@link Ext.toolbar.Paging#pageSize limit} parameters. Only applies when &lt;tt&gt;{@link #queryMode} = 'remote'&lt;/tt&gt;
196      * (defaults to &lt;tt&gt;0&lt;/tt&gt;).
197      */
198     pageSize: 0,
199
200 <span id='Ext-form.field.ComboBox-cfg-queryDelay'>    /**
201 </span>     * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
202      * sending the query to filter the dropdown list (defaults to &lt;tt&gt;500&lt;/tt&gt; if &lt;tt&gt;{@link #queryMode} = 'remote'&lt;/tt&gt;
203      * or &lt;tt&gt;10&lt;/tt&gt; if &lt;tt&gt;{@link #queryMode} = 'local'&lt;/tt&gt;)
204      */
205
206 <span id='Ext-form.field.ComboBox-cfg-minChars'>    /**
207 </span>     * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
208      * {@link #typeAhead} activate (defaults to &lt;tt&gt;4&lt;/tt&gt; if &lt;tt&gt;{@link #queryMode} = 'remote'&lt;/tt&gt; or &lt;tt&gt;0&lt;/tt&gt; if
209      * &lt;tt&gt;{@link #queryMode} = 'local'&lt;/tt&gt;, does not apply if &lt;tt&gt;{@link Ext.form.field.Trigger#editable editable} = false&lt;/tt&gt;).
210      */
211
212 <span id='Ext-form.field.ComboBox-cfg-autoSelect'>    /**
213 </span>     * @cfg {Boolean} autoSelect &lt;tt&gt;true&lt;/tt&gt; to select the first result gathered by the data store (defaults
214      * to &lt;tt&gt;true&lt;/tt&gt;).  A false value would require a manual selection from the dropdown list to set the components value
215      * unless the value of ({@link #typeAhead}) were true.
216      */
217     autoSelect: true,
218
219 <span id='Ext-form.field.ComboBox-cfg-typeAhead'>    /**
220 </span>     * @cfg {Boolean} typeAhead &lt;tt&gt;true&lt;/tt&gt; to populate and autoselect the remainder of the text being
221      * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
222      * to &lt;tt&gt;false&lt;/tt&gt;)
223      */
224     typeAhead: false,
225
226 <span id='Ext-form.field.ComboBox-cfg-typeAheadDelay'>    /**
227 </span>     * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
228      * if &lt;tt&gt;{@link #typeAhead} = true&lt;/tt&gt; (defaults to &lt;tt&gt;250&lt;/tt&gt;)
229      */
230     typeAheadDelay: 250,
231
232 <span id='Ext-form.field.ComboBox-cfg-selectOnTab'>    /**
233 </span>     * @cfg {Boolean} selectOnTab
234      * Whether the Tab key should select the currently highlighted item. Defaults to &lt;tt&gt;true&lt;/tt&gt;.
235      */
236     selectOnTab: true,
237
238 <span id='Ext-form.field.ComboBox-cfg-forceSelection'>    /**
239 </span>     * @cfg {Boolean} forceSelection &lt;tt&gt;true&lt;/tt&gt; to restrict the selected value to one of the values in the list,
240      * &lt;tt&gt;false&lt;/tt&gt; to allow the user to set arbitrary text into the field (defaults to &lt;tt&gt;false&lt;/tt&gt;)
241      */
242     forceSelection: false,
243
244 <span id='Ext-form.field.ComboBox-cfg-valueNotFoundText'>    /**
245 </span>     * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
246      * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
247      * default text is used, it means there is no value set and no validation will occur on this field.
248      */
249
250 <span id='Ext-form.field.ComboBox-property-lastQuery'>    /**
251 </span>     * The value of the match string used to filter the store. Delete this property to force a requery.
252      * Example use:
253      * &lt;pre&gt;&lt;code&gt;
254 var combo = new Ext.form.field.ComboBox({
255     ...
256     queryMode: 'remote',
257     listeners: {
258         // delete the previous query in the beforequery event or set
259         // combo.lastQuery = null (this will reload the store the next time it expands)
260         beforequery: function(qe){
261             delete qe.combo.lastQuery;
262         }
263     }
264 });
265      * &lt;/code&gt;&lt;/pre&gt;
266      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
267      * configure the combo with &lt;tt&gt;lastQuery=''&lt;/tt&gt;. Example use:
268      * &lt;pre&gt;&lt;code&gt;
269 var combo = new Ext.form.field.ComboBox({
270     ...
271     queryMode: 'local',
272     triggerAction: 'all',
273     lastQuery: ''
274 });
275      * &lt;/code&gt;&lt;/pre&gt;
276      * @property lastQuery
277      * @type String
278      */
279
280 <span id='Ext-form.field.ComboBox-cfg-defaultListConfig'>    /**
281 </span>     * @cfg {Object} defaultListConfig
282      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
283      */
284     defaultListConfig: {
285         emptyText: '',
286         loadingText: 'Loading...',
287         loadingHeight: 70,
288         minWidth: 70,
289         maxHeight: 300,
290         shadow: 'sides'
291     },
292
293 <span id='Ext-form.field.ComboBox-cfg-transform'>    /**
294 </span>     * @cfg {Mixed} transform
295      * The id, DOM node or {@link Ext.core.Element} of an existing HTML &lt;tt&gt;&amp;lt;select&amp;gt;&lt;/tt&gt; element to
296      * convert into a ComboBox. The target select's options will be used to build the options in the ComboBox
297      * dropdown; a configured {@link #store} will take precedence over this.
298      */
299
300 <span id='Ext-form.field.ComboBox-cfg-listConfig'>    /**
301 </span>     * @cfg {Object} listConfig
302      * &lt;p&gt;An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s
303      * constructor. Any configuration that is valid for BoundList can be included. Some of the more useful
304      * ones are:&lt;/p&gt;
305      * &lt;ul&gt;
306      *     &lt;li&gt;{@link Ext.view.BoundList#cls} - defaults to empty&lt;/li&gt;
307      *     &lt;li&gt;{@link Ext.view.BoundList#emptyText} - defaults to empty string&lt;/li&gt;
308      *     &lt;li&gt;{@link Ext.view.BoundList#getInnerTpl} - defaults to the template defined in BoundList&lt;/li&gt;
309      *     &lt;li&gt;{@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList&lt;/li&gt;
310      *     &lt;li&gt;{@link Ext.view.BoundList#loadingText} - defaults to &lt;tt&gt;'Loading...'&lt;/tt&gt;&lt;/li&gt;
311      *     &lt;li&gt;{@link Ext.view.BoundList#minWidth} - defaults to &lt;tt&gt;70&lt;/tt&gt;&lt;/li&gt;
312      *     &lt;li&gt;{@link Ext.view.BoundList#maxWidth} - defaults to &lt;tt&gt;undefined&lt;/tt&gt;&lt;/li&gt;
313      *     &lt;li&gt;{@link Ext.view.BoundList#maxHeight} - defaults to &lt;tt&gt;300&lt;/tt&gt;&lt;/li&gt;
314      *     &lt;li&gt;{@link Ext.view.BoundList#resizable} - defaults to &lt;tt&gt;false&lt;/tt&gt;&lt;/li&gt;
315      *     &lt;li&gt;{@link Ext.view.BoundList#shadow} - defaults to &lt;tt&gt;'sides'&lt;/tt&gt;&lt;/li&gt;
316      *     &lt;li&gt;{@link Ext.view.BoundList#width} - defaults to &lt;tt&gt;undefined&lt;/tt&gt; (automatically set to the width
317      *         of the ComboBox field if {@link #matchFieldWidth} is true)&lt;/li&gt;
318      * &lt;/ul&gt;
319      */
320
321     //private
322     ignoreSelection: 0,
323
324     initComponent: function() {
325         var me = this,
326             isDefined = Ext.isDefined,
327             store = me.store,
328             transform = me.transform,
329             transformSelect, isLocalMode;
330
331         //&lt;debug&gt;
332         if (!store &amp;&amp; !transform) {
333             Ext.Error.raise('Either a valid store, or a HTML select to transform, must be configured on the combo.');
334         }
335         if (me.typeAhead &amp;&amp; me.multiSelect) {
336             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
337         }
338         if (me.typeAhead &amp;&amp; !me.editable) {
339             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
340         }
341         if (me.selectOnFocus &amp;&amp; !me.editable) {
342             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
343         }
344         //&lt;/debug&gt;
345
346         this.addEvents(
347             // TODO need beforeselect?
348
349 <span id='Ext-form.field.ComboBox-event-beforequery'>            /**
350 </span>             * @event beforequery
351              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
352              * cancel property to true.
353              * @param {Object} queryEvent An object that has these properties:&lt;ul&gt;
354              * &lt;li&gt;&lt;code&gt;combo&lt;/code&gt; : Ext.form.field.ComboBox &lt;div class=&quot;sub-desc&quot;&gt;This combo box&lt;/div&gt;&lt;/li&gt;
355              * &lt;li&gt;&lt;code&gt;query&lt;/code&gt; : String &lt;div class=&quot;sub-desc&quot;&gt;The query string&lt;/div&gt;&lt;/li&gt;
356              * &lt;li&gt;&lt;code&gt;forceAll&lt;/code&gt; : Boolean &lt;div class=&quot;sub-desc&quot;&gt;True to force &quot;all&quot; query&lt;/div&gt;&lt;/li&gt;
357              * &lt;li&gt;&lt;code&gt;cancel&lt;/code&gt; : Boolean &lt;div class=&quot;sub-desc&quot;&gt;Set to true to cancel the query&lt;/div&gt;&lt;/li&gt;
358              * &lt;/ul&gt;
359              */
360             'beforequery',
361
362             /*
363              * @event select
364              * Fires when at least one list item is selected.
365              * @param {Ext.form.field.ComboBox} combo This combo box
366              * @param {Array} records The selected records
367              */
368             'select'
369         );
370
371         // Build store from 'transform' HTML select element's options
372         if (!store &amp;&amp; transform) {
373             transformSelect = Ext.getDom(transform);
374             if (transformSelect) {
375                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
376                     return [option.value, option.text];
377                 });
378                 if (!me.name) {
379                     me.name = transformSelect.name;
380                 }
381                 if (!('value' in me)) {
382                     me.value = transformSelect.value;
383                 }
384             }
385         }
386
387         me.bindStore(store, true);
388         store = me.store;
389         if (store.autoCreated) {
390             me.queryMode = 'local';
391             me.valueField = me.displayField = 'field1';
392             if (!store.expanded) {
393                 me.displayField = 'field2';
394             }
395         }
396
397
398         if (!isDefined(me.valueField)) {
399             me.valueField = me.displayField;
400         }
401
402         isLocalMode = me.queryMode === 'local';
403         if (!isDefined(me.queryDelay)) {
404             me.queryDelay = isLocalMode ? 10 : 500;
405         }
406         if (!isDefined(me.minChars)) {
407             me.minChars = isLocalMode ? 0 : 4;
408         }
409
410         if (!me.displayTpl) {
411             me.displayTpl = Ext.create('Ext.XTemplate',
412                 '&lt;tpl for=&quot;.&quot;&gt;' +
413                     '{[typeof values === &quot;string&quot; ? values : values.' + me.displayField + ']}' +
414                     '&lt;tpl if=&quot;xindex &lt; xcount&quot;&gt;' + me.delimiter + '&lt;/tpl&gt;' +
415                 '&lt;/tpl&gt;'
416             );
417         } else if (Ext.isString(me.displayTpl)) {
418             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
419         }
420
421         me.callParent();
422
423         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
424
425         // store has already been loaded, setValue
426         if (me.store.getCount() &gt; 0) {
427             me.setValue(me.value);
428         }
429
430         // render in place of 'transform' select
431         if (transformSelect) {
432             me.render(transformSelect.parentNode, transformSelect);
433             Ext.removeNode(transformSelect);
434             delete me.renderTo;
435         }
436     },
437
438     beforeBlur: function() {
439         var me = this;
440         me.doQueryTask.cancel();
441         if (me.forceSelection) {
442             me.assertValue();
443         } else {
444             me.collapse();
445         }
446     },
447
448     // private
449     assertValue: function() {
450         var me = this,
451             value = me.getRawValue(),
452             rec;
453
454         if (me.multiSelect) {
455             // For multiselect, check that the current displayed value matches the current
456             // selection, if it does not then revert to the most recent selection.
457             if (value !== me.getDisplayValue()) {
458                 me.setValue(me.lastSelection);
459             }
460         } else {
461             // For single-select, match the displayed value to a record and select it,
462             // if it does not match a record then revert to the most recent selection.
463             rec = me.findRecordByDisplay(value);
464             if (rec) {
465                 me.select(rec);
466             } else {
467                 me.setValue(me.lastSelection);
468             }
469         }
470         me.collapse();
471     },
472
473     onTypeAhead: function() {
474         var me = this,
475             displayField = me.displayField,
476             record = me.store.findRecord(displayField, me.getRawValue()),
477             boundList = me.getPicker(),
478             newValue, len, selStart;
479
480         if (record) {
481             newValue = record.get(displayField);
482             len = newValue.length;
483             selStart = me.getRawValue().length;
484
485             boundList.highlightItem(boundList.getNode(record));
486
487             if (selStart !== 0 &amp;&amp; selStart !== len) {
488                 me.setRawValue(newValue);
489                 me.selectText(selStart, newValue.length);
490             }
491         }
492     },
493
494     // invoked when a different store is bound to this combo
495     // than the original
496     resetToDefault: function() {
497
498     },
499
500     bindStore: function(store, initial) {
501         var me = this,
502             oldStore = me.store;
503
504         // this code directly accesses this.picker, bc invoking getPicker
505         // would create it when we may be preping to destroy it
506         if (oldStore &amp;&amp; !initial) {
507             if (oldStore !== store &amp;&amp; oldStore.autoDestroy) {
508                 oldStore.destroy();
509             } else {
510                 oldStore.un({
511                     scope: me,
512                     load: me.onLoad,
513                     exception: me.collapse
514                 });
515             }
516             if (!store) {
517                 me.store = null;
518                 if (me.picker) {
519                     me.picker.bindStore(null);
520                 }
521             }
522         }
523         if (store) {
524             if (!initial) {
525                 me.resetToDefault();
526             }
527
528             me.store = Ext.data.StoreManager.lookup(store);
529             me.store.on({
530                 scope: me,
531                 load: me.onLoad,
532                 exception: me.collapse
533             });
534
535             if (me.picker) {
536                 me.picker.bindStore(store);
537             }
538         }
539     },
540
541     onLoad: function() {
542         var me = this,
543             value = me.value;
544
545         me.syncSelection();
546         if (me.picker &amp;&amp; !me.picker.getSelectionModel().hasSelection()) {
547             me.doAutoSelect();
548         }
549     },
550
551 <span id='Ext-form.field.ComboBox-method-doRawQuery'>    /**
552 </span>     * @private
553      * Execute the query with the raw contents within the textfield.
554      */
555     doRawQuery: function() {
556         this.doQuery(this.getRawValue());
557     },
558
559 <span id='Ext-form.field.ComboBox-method-doQuery'>    /**
560 </span>     * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
561      * query allowing the query action to be canceled if needed.
562      * @param {String} queryString The SQL query to execute
563      * @param {Boolean} forceAll &lt;tt&gt;true&lt;/tt&gt; to force the query to execute even if there are currently fewer
564      * characters in the field than the minimum specified by the &lt;tt&gt;{@link #minChars}&lt;/tt&gt; config option.  It
565      * also clears any filter previously saved in the current store (defaults to &lt;tt&gt;false&lt;/tt&gt;)
566      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery} handler.
567      */
568     doQuery: function(queryString, forceAll) {
569         queryString = queryString || '';
570
571         // store in object and pass by reference in 'beforequery'
572         // so that client code can modify values.
573         var me = this,
574             qe = {
575                 query: queryString,
576                 forceAll: forceAll,
577                 combo: me,
578                 cancel: false
579             },
580             store = me.store,
581             isLocalMode = me.queryMode === 'local';
582
583         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
584             return false;
585         }
586
587         // get back out possibly modified values
588         queryString = qe.query;
589         forceAll = qe.forceAll;
590
591         // query permitted to run
592         if (forceAll || (queryString.length &gt;= me.minChars)) {
593             // expand before starting query so LoadMask can position itself correctly
594             me.expand();
595
596             // make sure they aren't querying the same thing
597             if (!me.queryCaching || me.lastQuery !== queryString) {
598                 me.lastQuery = queryString;
599                 store.clearFilter(!forceAll);
600                 if (isLocalMode) {
601                     if (!forceAll) {
602                         store.filter(me.displayField, queryString);
603                     }
604                 } else {
605                     store.load({
606                         params: me.getParams(queryString)
607                     });
608                 }
609             }
610
611             // Clear current selection if it does not match the current value in the field
612             if (me.getRawValue() !== me.getDisplayValue()) {
613                 me.ignoreSelection++;
614                 me.picker.getSelectionModel().deselectAll();
615                 me.ignoreSelection--;
616             }
617
618             if (isLocalMode) {
619                 me.doAutoSelect();
620             }
621             if (me.typeAhead) {
622                 me.doTypeAhead();
623             }
624         }
625         return true;
626     },
627
628     // private
629     getParams: function(queryString) {
630         var p = {},
631             pageSize = this.pageSize;
632         p[this.queryParam] = queryString;
633         if (pageSize) {
634             p.start = 0;
635             p.limit = pageSize;
636         }
637         return p;
638     },
639
640 <span id='Ext-form.field.ComboBox-method-doAutoSelect'>    /**
641 </span>     * @private
642      * If the autoSelect config is true, and the picker is open, highlights the first item.
643      */
644     doAutoSelect: function() {
645         var me = this,
646             picker = me.picker,
647             lastSelected, itemNode;
648         if (picker &amp;&amp; me.autoSelect &amp;&amp; me.store.getCount() &gt; 0) {
649             // Highlight the last selected item and scroll it into view
650             lastSelected = picker.getSelectionModel().lastSelected;
651             itemNode = picker.getNode(lastSelected || 0);
652             if (itemNode) {
653                 picker.highlightItem(itemNode);
654                 picker.listEl.scrollChildIntoView(itemNode, false);
655             }
656         }
657     },
658
659     doTypeAhead: function() {
660         if (!this.typeAheadTask) {
661             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
662         }
663         if (this.lastKey != Ext.EventObject.BACKSPACE &amp;&amp; this.lastKey != Ext.EventObject.DELETE) {
664             this.typeAheadTask.delay(this.typeAheadDelay);
665         }
666     },
667
668     onTriggerClick: function() {
669         var me = this;
670         if (!me.readOnly &amp;&amp; !me.disabled) {
671             if (me.isExpanded) {
672                 me.collapse();
673             } else {
674                 me.onFocus({});
675                 if (me.triggerAction === 'all') {
676                     me.doQuery(me.allQuery, true);
677                 } else {
678                     me.doQuery(me.getRawValue());
679                 }
680             }
681             me.inputEl.focus();
682         }
683     },
684
685
686     // store the last key and doQuery if relevant
687     onKeyUp: function(e, t) {
688         var me = this,
689             key = e.getKey();
690
691         if (!me.readOnly &amp;&amp; !me.disabled &amp;&amp; me.editable) {
692             me.lastKey = key;
693             // we put this in a task so that we can cancel it if a user is
694             // in and out before the queryDelay elapses
695
696             // perform query w/ any normal key or backspace or delete
697             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
698                 me.doQueryTask.delay(me.queryDelay);
699             }
700         }
701     },
702
703     initEvents: function() {
704         var me = this;
705         me.callParent();
706
707         // setup keyboard handling
708         me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
709     },
710
711     createPicker: function() {
712         var me = this,
713             picker,
714             menuCls = Ext.baseCSSPrefix + 'menu',
715             opts = Ext.apply({
716                 selModel: {
717                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
718                 },
719                 floating: true,
720                 hidden: true,
721                 ownerCt: me.ownerCt,
722                 cls: me.el.up('.' + menuCls) ? menuCls : '',
723                 store: me.store,
724                 displayField: me.displayField,
725                 focusOnToFront: false,
726                 pageSize: me.pageSize
727             }, me.listConfig, me.defaultListConfig);
728
729         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
730
731         me.mon(picker, {
732             itemclick: me.onItemClick,
733             refresh: me.onListRefresh,
734             scope: me
735         });
736
737         me.mon(picker.getSelectionModel(), {
738             selectionChange: me.onListSelectionChange,
739             scope: me
740         });
741
742         return picker;
743     },
744
745     onListRefresh: function() {
746         this.alignPicker();
747         this.syncSelection();
748     },
749     
750     onItemClick: function(picker, record){
751         /*
752          * If we're doing single selection, the selection change events won't fire when
753          * clicking on the selected element. Detect it here.
754          */
755         var me = this,
756             lastSelection = me.lastSelection,
757             valueField = me.valueField,
758             selected;
759         
760         if (!me.multiSelect &amp;&amp; lastSelection) {
761             selected = lastSelection[0];
762             if (record.get(valueField) === selected.get(valueField)) {
763                 me.collapse();
764             }
765         }   
766     },
767
768     onListSelectionChange: function(list, selectedRecords) {
769         var me = this;
770         // Only react to selection if it is not called from setValue, and if our list is
771         // expanded (ignores changes to the selection model triggered elsewhere)
772         if (!me.ignoreSelection &amp;&amp; me.isExpanded) {
773             if (!me.multiSelect) {
774                 Ext.defer(me.collapse, 1, me);
775             }
776             me.setValue(selectedRecords, false);
777             if (selectedRecords.length &gt; 0) {
778                 me.fireEvent('select', me, selectedRecords);
779             }
780             me.inputEl.focus();
781         }
782     },
783
784 <span id='Ext-form.field.ComboBox-method-onExpand'>    /**
785 </span>     * @private
786      * Enables the key nav for the BoundList when it is expanded.
787      */
788     onExpand: function() {
789         var me = this,
790             keyNav = me.listKeyNav,
791             selectOnTab = me.selectOnTab,
792             picker = me.getPicker();
793
794         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
795         if (keyNav) {
796             keyNav.enable();
797         } else {
798             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
799                 boundList: picker,
800                 forceKeyDown: true,
801                 tab: function(e) {
802                     if (selectOnTab) {
803                         this.selectHighlighted(e);
804                         me.triggerBlur();
805                     }
806                     // Tab key event is allowed to propagate to field
807                     return true;
808                 }
809             });
810         }
811
812         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
813         if (selectOnTab) {
814             me.ignoreMonitorTab = true;
815         }
816
817         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
818         me.inputEl.focus();
819     },
820
821 <span id='Ext-form.field.ComboBox-method-onCollapse'>    /**
822 </span>     * @private
823      * Disables the key nav for the BoundList when it is collapsed.
824      */
825     onCollapse: function() {
826         var me = this,
827             keyNav = me.listKeyNav;
828         if (keyNav) {
829             keyNav.disable();
830             me.ignoreMonitorTab = false;
831         }
832     },
833
834 <span id='Ext-form.field.ComboBox-method-select'>    /**
835 </span>     * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
836      * @param r
837      */
838     select: function(r) {
839         this.setValue(r, true);
840     },
841
842 <span id='Ext-form.field.ComboBox-method-findRecord'>    /**
843 </span>     * Find the record by searching for a specific field/value combination
844      * Returns an Ext.data.Record or false
845      * @private
846      */
847     findRecord: function(field, value) {
848         var ds = this.store,
849             idx = ds.findExact(field, value);
850         return idx !== -1 ? ds.getAt(idx) : false;
851     },
852     findRecordByValue: function(value) {
853         return this.findRecord(this.valueField, value);
854     },
855     findRecordByDisplay: function(value) {
856         return this.findRecord(this.displayField, value);
857     },
858
859 <span id='Ext-form.field.ComboBox-method-setValue'>    /**
860 </span>     * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
861      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
862      * field.  If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
863      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
864      * @param {String|Array} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
865      * or an Array of Strings or Models.
866      * @return {Ext.form.field.Field} this
867      */
868     setValue: function(value, doSelect) {
869         var me = this,
870             valueNotFoundText = me.valueNotFoundText,
871             inputEl = me.inputEl,
872             i, len, record,
873             models = [],
874             displayTplData = [],
875             processedValue = [];
876
877         if (me.store.loading) {
878             // Called while the Store is loading. Ensure it is processed by the onLoad method.
879             me.value = value;
880             return me;
881         }
882
883         // This method processes multi-values, so ensure value is an array.
884         value = Ext.Array.from(value);
885
886         // Loop through values
887         for (i = 0, len = value.length; i &lt; len; i++) {
888             record = value[i];
889             if (!record || !record.isModel) {
890                 record = me.findRecordByValue(record);
891             }
892             // record found, select it.
893             if (record) {
894                 models.push(record);
895                 displayTplData.push(record.data);
896                 processedValue.push(record.get(me.valueField));
897             }
898             // record was not found, this could happen because
899             // store is not loaded or they set a value not in the store
900             else {
901                 // if valueNotFoundText is defined, display it, otherwise display nothing for this value
902                 if (Ext.isDefined(valueNotFoundText)) {
903                     displayTplData.push(valueNotFoundText);
904                 }
905                 processedValue.push(value[i]);
906             }
907         }
908
909         // Set the value of this field. If we are multiselecting, then that is an array.
910         me.value = me.multiSelect ? processedValue : processedValue[0];
911         if (!Ext.isDefined(me.value)) {
912             me.value = null;
913         }
914         me.displayTplData = displayTplData; //store for getDisplayValue method
915         me.lastSelection = me.valueModels = models;
916
917         if (inputEl &amp;&amp; me.emptyText &amp;&amp; !Ext.isEmpty(value)) {
918             inputEl.removeCls(me.emptyCls);
919         }
920
921         // Calculate raw value from the collection of Model data
922         me.setRawValue(me.getDisplayValue());
923         me.checkChange();
924
925         if (doSelect !== false) {
926             me.syncSelection();
927         }
928         me.applyEmptyText();
929
930         return me;
931     },
932
933 <span id='Ext-form.field.ComboBox-method-getDisplayValue'>    /**
934 </span>     * @private Generate the string value to be displayed in the text field for the currently stored value
935      */
936     getDisplayValue: function() {
937         return this.displayTpl.apply(this.displayTplData);
938     },
939
940     getValue: function() {
941         // If the user has not changed the raw field value since a value was selected from the list,
942         // then return the structured value from the selection. If the raw field value is different
943         // than what would be displayed due to selection, return that raw value.
944         var me = this,
945             picker = me.picker,
946             rawValue = me.getRawValue(), //current value of text field
947             value = me.value; //stored value from last selection or setValue() call
948
949         if (me.getDisplayValue() !== rawValue) {
950             value = rawValue;
951             me.value = me.displayTplData = me.valueModels = null;
952             if (picker) {
953                 me.ignoreSelection++;
954                 picker.getSelectionModel().deselectAll();
955                 me.ignoreSelection--;
956             }
957         }
958
959         return value;
960     },
961
962     getSubmitValue: function() {
963         return this.getValue();
964     },
965
966     isEqual: function(v1, v2) {
967         var fromArray = Ext.Array.from,
968             i, len;
969
970         v1 = fromArray(v1);
971         v2 = fromArray(v2);
972         len = v1.length;
973
974         if (len !== v2.length) {
975             return false;
976         }
977
978         for(i = 0; i &lt; len; i++) {
979             if (v2[i] !== v1[i]) {
980                 return false;
981             }
982         }
983
984         return true;
985     },
986
987 <span id='Ext-form.field.ComboBox-method-clearValue'>    /**
988 </span>     * Clears any value currently set in the ComboBox.
989      */
990     clearValue: function() {
991         this.setValue([]);
992     },
993
994 <span id='Ext-form.field.ComboBox-method-syncSelection'>    /**
995 </span>     * @private Synchronizes the selection in the picker to match the current value of the combobox.
996      */
997     syncSelection: function() {
998         var me = this,
999             ExtArray = Ext.Array,
1000             picker = me.picker,
1001             selection, selModel;
1002         if (picker) {
1003             // From the value, find the Models that are in the store's current data
1004             selection = [];
1005             ExtArray.forEach(me.valueModels || [], function(value) {
1006                 if (value &amp;&amp; value.isModel &amp;&amp; me.store.indexOf(value) &gt;= 0) {
1007                     selection.push(value);
1008                 }
1009             });
1010
1011             // Update the selection to match
1012             me.ignoreSelection++;
1013             selModel = picker.getSelectionModel();
1014             selModel.deselectAll();
1015             if (selection.length) {
1016                 selModel.select(selection);
1017             }
1018             me.ignoreSelection--;
1019         }
1020     }
1021 });
1022 </pre></pre></body></html>