Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / ComboBox.html
1 <!DOCTYPE html>
2 <html>
3 <head>
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; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
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
21  *
22  * A combobox control with support for autocomplete, remote loading, and many other features.
23  *
24  * A ComboBox is like a combination of a traditional HTML text `&lt;input&gt;` field and a `&lt;select&gt;`
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`.
28  *
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.
32  *
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.
35  *
36  * {@img Ext.form.ComboBox/Ext.form.ComboBox.png Ext.form.ComboBox component}
37  *
38  * ## Example usage:
39  *
40  *     // The data store containing the list of states
41  *     var states = Ext.create('Ext.data.Store', {
42  *         fields: ['abbr', 'name'],
43  *         data : [
44  *             {&quot;abbr&quot;:&quot;AL&quot;, &quot;name&quot;:&quot;Alabama&quot;},
45  *             {&quot;abbr&quot;:&quot;AK&quot;, &quot;name&quot;:&quot;Alaska&quot;},
46  *             {&quot;abbr&quot;:&quot;AZ&quot;, &quot;name&quot;:&quot;Arizona&quot;}
47  *             //...
48  *         ]
49  *     });
50  *
51  *     // Create the combo box, attached to the states data store
52  *     Ext.create('Ext.form.ComboBox', {
53  *         fieldLabel: 'Choose State',
54  *         store: states,
55  *         queryMode: 'local',
56  *         displayField: 'name',
57  *         valueField: 'abbr',
58  *         renderTo: Ext.getBody()
59  *     });
60  *
61  * ## Events
62  *
63  * To do something when something in ComboBox is selected, configure the select event:
64  *
65  *     var cb = Ext.create('Ext.form.ComboBox', {
66  *         // all of your config options
67  *         listeners:{
68  *              scope: yourScope,
69  *              'select': yourFunction
70  *         }
71  *     });
72  *
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);
76  *
77  * ## Multiple Selection
78  *
79  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
80  * {@link #multiSelect} config to `true`.
81  *
82  * @docauthor Jason Johnston &lt;jason@sencha.com&gt;
83  */
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'],
89
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 &lt;code&gt;triggerCls&lt;/code&gt; will be &lt;b&gt;appended&lt;/b&gt; if specified.
94      * Defaults to 'x-form-arrow-trigger' for ComboBox.
95      */
96     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
97
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 &lt;code&gt;undefined&lt;/code&gt;).
100      * Acceptable values for this property are:
101      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
102      * &lt;li&gt;&lt;b&gt;any {@link Ext.data.Store Store} subclass&lt;/b&gt;&lt;/li&gt;
103      * &lt;li&gt;&lt;b&gt;an Array&lt;/b&gt; : 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      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
106      * &lt;li&gt;&lt;b&gt;1-dimensional array&lt;/b&gt; : (e.g., &lt;code&gt;['Foo','Bar']&lt;/code&gt;)&lt;div class=&quot;sub-desc&quot;&gt;
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})&lt;/div&gt;&lt;/li&gt;
109      * &lt;li&gt;&lt;b&gt;2-dimensional array&lt;/b&gt; : (e.g., &lt;code&gt;[['f','Foo'],['b','Bar']]&lt;/code&gt;)&lt;div class=&quot;sub-desc&quot;&gt;
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      * &lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
113      * &lt;p&gt;See also &lt;code&gt;{@link #queryMode}&lt;/code&gt;.&lt;/p&gt;
114      */
115
116 <span id='Ext-form-field-ComboBox-cfg-multiSelect'>    /**
117 </span>     * @cfg {Boolean} multiSelect
118      * If set to &lt;code&gt;true&lt;/code&gt;, 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 &lt;code&gt;false&lt;/code&gt;.)
121      */
122     multiSelect: false,
123
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 &lt;code&gt;{@link #multiSelect} = true&lt;/code&gt;. Defaults to &lt;code&gt;', '&lt;/code&gt;.
128      */
129     delimiter: ', ',
130
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      * &lt;p&gt;See also &lt;code&gt;{@link #valueField}&lt;/code&gt;.&lt;/p&gt;
135      */
136     displayField: 'text',
137
138 <span id='Ext-form-field-ComboBox-cfg-valueField'>    /**
139 </span>     * @cfg {String} valueField
140      * @required
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      * &lt;p&gt;&lt;b&gt;Note&lt;/b&gt;: use of a &lt;code&gt;valueField&lt;/code&gt; requires the user to make a selection in order for a value to be
144      * mapped. See also &lt;code&gt;{@link #displayField}&lt;/code&gt;.&lt;/p&gt;
145      */
146
147 <span id='Ext-form-field-ComboBox-cfg-triggerAction'>    /**
148 </span>     * @cfg {String} triggerAction The action to execute when the trigger is clicked.
149      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
150      * &lt;li&gt;&lt;b&gt;&lt;code&gt;'all'&lt;/code&gt;&lt;/b&gt; : &lt;b&gt;Default&lt;/b&gt;
151      * &lt;p class=&quot;sub-desc&quot;&gt;{@link #doQuery run the query} specified by the &lt;code&gt;{@link #allQuery}&lt;/code&gt; config option&lt;/p&gt;&lt;/li&gt;
152      * &lt;li&gt;&lt;b&gt;&lt;code&gt;'query'&lt;/code&gt;&lt;/b&gt; :
153      * &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;
154      * &lt;/ul&gt;&lt;/div&gt;
155      * &lt;p&gt;See also &lt;code&gt;{@link #queryParam}&lt;/code&gt;.&lt;/p&gt;
156      */
157     triggerAction: 'all',
158
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 '')
162      */
163     allQuery: '',
164
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      * &lt;code&gt;{@link #queryMode}: 'remote'&lt;/code&gt; (defaults to &lt;code&gt;'query'&lt;/code&gt;). If explicitly set to a falsy value it will
168      * not be sent.
169      */
170     queryParam: 'query',
171
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      * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
176      * &lt;li&gt;&lt;b&gt;&lt;code&gt;'remote'&lt;/code&gt;&lt;/b&gt; : &lt;b&gt;Default&lt;/b&gt;
177      * &lt;p&gt;In &lt;code&gt;queryMode: 'remote'&lt;/code&gt;, the ComboBox loads its Store dynamically based upon user interaction.&lt;/p&gt;
178      * &lt;p&gt;This is typically used for &quot;autocomplete&quot; type inputs, and after the user finishes typing, the Store is {@link Ext.data.Store#load load}ed.&lt;/p&gt;
179      * &lt;p&gt;A parameter containing the typed string is sent in the load request. The default parameter name for the input string is &lt;code&gt;query&lt;/code&gt;, but this
180      * can be configured using the {@link #queryParam} config.&lt;/p&gt;
181      * &lt;p&gt;In &lt;code&gt;queryMode: 'remote'&lt;/code&gt;, the Store may be configured with &lt;code&gt;{@link Ext.data.Store#remoteFilter remoteFilter}: true&lt;/code&gt;,
182      * and further filters may be &lt;i&gt;programatically&lt;/i&gt; added to the Store which are then passed with every load request which allows the server
183      * to further refine the returned dataset.&lt;/p&gt;
184      * &lt;p&gt;Typically, in an autocomplete situation, {@link #hideTrigger} is configured &lt;code&gt;true&lt;/code&gt; because it has no meaning for autocomplete.&lt;/p&gt;&lt;/li&gt;
185      * &lt;li&gt;&lt;b&gt;&lt;code&gt;'local'&lt;/code&gt;&lt;/b&gt; :
186      * &lt;p class=&quot;sub-desc&quot;&gt;ComboBox loads local data&lt;/p&gt;
187      * &lt;pre&gt;&lt;code&gt;
188 var combo = new Ext.form.field.ComboBox({
189     renderTo: document.body,
190     queryMode: 'local',
191     store: new Ext.data.ArrayStore({
192         id: 0,
193         fields: [
194             'myId',  // numeric value is the key
195             'displayText'
196         ],
197         data: [[1, 'item1'], [2, 'item2']]  // data is local
198     }),
199     valueField: 'myId',
200     displayField: 'displayText',
201     triggerAction: 'all'
202 });
203      * &lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
204      * &lt;/ul&gt;&lt;/div&gt;
205      */
206     queryMode: 'remote',
207
208     queryCaching: true,
209
210 <span id='Ext-form-field-ComboBox-cfg-pageSize'>    /**
211 </span>     * @cfg {Number} pageSize If greater than &lt;code&gt;0&lt;/code&gt;, 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 &lt;code&gt;{@link #queryMode} = 'remote'&lt;/code&gt;
214      * (defaults to &lt;code&gt;0&lt;/code&gt;).
215      */
216     pageSize: 0,
217
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 &lt;code&gt;500&lt;/code&gt; if &lt;code&gt;{@link #queryMode} = 'remote'&lt;/code&gt;
221      * or &lt;code&gt;10&lt;/code&gt; if &lt;code&gt;{@link #queryMode} = 'local'&lt;/code&gt;)
222      */
223
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 &lt;code&gt;4&lt;/code&gt; if &lt;code&gt;{@link #queryMode} = 'remote'&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt; if
227      * &lt;code&gt;{@link #queryMode} = 'local'&lt;/code&gt;, does not apply if &lt;code&gt;{@link Ext.form.field.Trigger#editable editable} = false&lt;/code&gt;).
228      */
229
230 <span id='Ext-form-field-ComboBox-cfg-autoSelect'>    /**
231 </span>     * @cfg {Boolean} autoSelect &lt;code&gt;true&lt;/code&gt; to automatically highlight the first result gathered by the data store
232      * in the dropdown list when it is opened. (Defaults to &lt;code&gt;true&lt;/code&gt;). 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.
236      */
237     autoSelect: true,
238
239 <span id='Ext-form-field-ComboBox-cfg-typeAhead'>    /**
240 </span>     * @cfg {Boolean} typeAhead &lt;code&gt;true&lt;/code&gt; 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 &lt;code&gt;false&lt;/code&gt;)
243      */
244     typeAhead: false,
245
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 &lt;code&gt;{@link #typeAhead} = true&lt;/code&gt; (defaults to &lt;code&gt;250&lt;/code&gt;)
249      */
250     typeAheadDelay: 250,
251
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 &lt;code&gt;true&lt;/code&gt;.
255      */
256     selectOnTab: true,
257
258 <span id='Ext-form-field-ComboBox-cfg-forceSelection'>    /**
259 </span>     * @cfg {Boolean} forceSelection &lt;code&gt;true&lt;/code&gt; to restrict the selected value to one of the values in the list,
260      * &lt;code&gt;false&lt;/code&gt; to allow the user to set arbitrary text into the field (defaults to &lt;code&gt;false&lt;/code&gt;)
261      */
262     forceSelection: false,
263
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.
268      */
269
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.
272      * Example use:
273      * &lt;pre&gt;&lt;code&gt;
274 var combo = new Ext.form.field.ComboBox({
275     ...
276     queryMode: 'remote',
277     listeners: {
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;
282         }
283     }
284 });
285      * &lt;/code&gt;&lt;/pre&gt;
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 &lt;code&gt;lastQuery=''&lt;/code&gt;. Example use:
288      * &lt;pre&gt;&lt;code&gt;
289 var combo = new Ext.form.field.ComboBox({
290     ...
291     queryMode: 'local',
292     triggerAction: 'all',
293     lastQuery: ''
294 });
295      * &lt;/code&gt;&lt;/pre&gt;
296      * @property lastQuery
297      * @type String
298      */
299
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.
303      */
304     defaultListConfig: {
305         emptyText: '',
306         loadingText: 'Loading...',
307         loadingHeight: 70,
308         minWidth: 70,
309         maxHeight: 300,
310         shadow: 'sides'
311     },
312
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 &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 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.
318      */
319
320 <span id='Ext-form-field-ComboBox-cfg-listConfig'>    /**
321 </span>     * @cfg {Object} listConfig
322      * &lt;p&gt;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:&lt;/p&gt;
325      * &lt;ul&gt;
326      *     &lt;li&gt;{@link Ext.view.BoundList#cls} - defaults to empty&lt;/li&gt;
327      *     &lt;li&gt;{@link Ext.view.BoundList#emptyText} - defaults to empty string&lt;/li&gt;
328      *     &lt;li&gt;{@link Ext.view.BoundList#getInnerTpl} - defaults to the template defined in BoundList&lt;/li&gt;
329      *     &lt;li&gt;{@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList&lt;/li&gt;
330      *     &lt;li&gt;{@link Ext.view.BoundList#loadingText} - defaults to &lt;code&gt;'Loading...'&lt;/code&gt;&lt;/li&gt;
331      *     &lt;li&gt;{@link Ext.view.BoundList#minWidth} - defaults to &lt;code&gt;70&lt;/code&gt;&lt;/li&gt;
332      *     &lt;li&gt;{@link Ext.view.BoundList#maxWidth} - defaults to &lt;code&gt;undefined&lt;/code&gt;&lt;/li&gt;
333      *     &lt;li&gt;{@link Ext.view.BoundList#maxHeight} - defaults to &lt;code&gt;300&lt;/code&gt;&lt;/li&gt;
334      *     &lt;li&gt;{@link Ext.view.BoundList#resizable} - defaults to &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
335      *     &lt;li&gt;{@link Ext.view.BoundList#shadow} - defaults to &lt;code&gt;'sides'&lt;/code&gt;&lt;/li&gt;
336      *     &lt;li&gt;{@link Ext.view.BoundList#width} - defaults to &lt;code&gt;undefined&lt;/code&gt; (automatically set to the width
337      *         of the ComboBox field if {@link #matchFieldWidth} is true)&lt;/li&gt;
338      * &lt;/ul&gt;
339      */
340
341     //private
342     ignoreSelection: 0,
343
344     initComponent: function() {
345         var me = this,
346             isDefined = Ext.isDefined,
347             store = me.store,
348             transform = me.transform,
349             transformSelect, isLocalMode;
350
351         //&lt;debug&gt;
352         if (!store &amp;&amp; !transform) {
353             Ext.Error.raise('Either a valid store, or a HTML select to transform, must be configured on the combo.');
354         }
355         if (me.typeAhead &amp;&amp; me.multiSelect) {
356             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
357         }
358         if (me.typeAhead &amp;&amp; !me.editable) {
359             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
360         }
361         if (me.selectOnFocus &amp;&amp; !me.editable) {
362             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
363         }
364         //&lt;/debug&gt;
365
366         this.addEvents(
367             // TODO need beforeselect?
368
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:&lt;ul&gt;
374              * &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;
375              * &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;
376              * &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;
377              * &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;
378              * &lt;/ul&gt;
379              */
380             'beforequery',
381
382             /*
383              * @event select
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
387              */
388             'select'
389         );
390
391         // Build store from 'transform' HTML select element's options
392         if (!store &amp;&amp; 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];
397                 });
398                 if (!me.name) {
399                     me.name = transformSelect.name;
400                 }
401                 if (!('value' in me)) {
402                     me.value = transformSelect.value;
403                 }
404             }
405         }
406
407         me.bindStore(store, true);
408         store = me.store;
409         if (store.autoCreated) {
410             me.queryMode = 'local';
411             me.valueField = me.displayField = 'field1';
412             if (!store.expanded) {
413                 me.displayField = 'field2';
414             }
415         }
416
417
418         if (!isDefined(me.valueField)) {
419             me.valueField = me.displayField;
420         }
421
422         isLocalMode = me.queryMode === 'local';
423         if (!isDefined(me.queryDelay)) {
424             me.queryDelay = isLocalMode ? 10 : 500;
425         }
426         if (!isDefined(me.minChars)) {
427             me.minChars = isLocalMode ? 0 : 4;
428         }
429
430         if (!me.displayTpl) {
431             me.displayTpl = Ext.create('Ext.XTemplate',
432                 '&lt;tpl for=&quot;.&quot;&gt;' +
433                     '{[typeof values === &quot;string&quot; ? values : values.' + me.displayField + ']}' +
434                     '&lt;tpl if=&quot;xindex &lt; xcount&quot;&gt;' + me.delimiter + '&lt;/tpl&gt;' +
435                 '&lt;/tpl&gt;'
436             );
437         } else if (Ext.isString(me.displayTpl)) {
438             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
439         }
440
441         me.callParent();
442
443         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
444
445         // store has already been loaded, setValue
446         if (me.store.getCount() &gt; 0) {
447             me.setValue(me.value);
448         }
449
450         // render in place of 'transform' select
451         if (transformSelect) {
452             me.render(transformSelect.parentNode, transformSelect);
453             Ext.removeNode(transformSelect);
454             delete me.renderTo;
455         }
456     },
457
458     beforeBlur: function() {
459         var me = this;
460         me.doQueryTask.cancel();
461         if (me.forceSelection) {
462             me.assertValue();
463         } else {
464             me.collapse();
465         }
466     },
467
468     // private
469     assertValue: function() {
470         var me = this,
471             value = me.getRawValue(),
472             rec;
473
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);
479             }
480         } else {
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);
484             if (rec) {
485                 me.select(rec);
486             } else {
487                 me.setValue(me.lastSelection);
488             }
489         }
490         me.collapse();
491     },
492
493     onTypeAhead: function() {
494         var me = this,
495             displayField = me.displayField,
496             record = me.store.findRecord(displayField, me.getRawValue()),
497             boundList = me.getPicker(),
498             newValue, len, selStart;
499
500         if (record) {
501             newValue = record.get(displayField);
502             len = newValue.length;
503             selStart = me.getRawValue().length;
504
505             boundList.highlightItem(boundList.getNode(record));
506
507             if (selStart !== 0 &amp;&amp; selStart !== len) {
508                 me.setRawValue(newValue);
509                 me.selectText(selStart, newValue.length);
510             }
511         }
512     },
513
514     // invoked when a different store is bound to this combo
515     // than the original
516     resetToDefault: function() {
517
518     },
519
520     bindStore: function(store, initial) {
521         var me = this,
522             oldStore = me.store;
523
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 &amp;&amp; !initial) {
527             if (oldStore !== store &amp;&amp; oldStore.autoDestroy) {
528                 oldStore.destroy();
529             } else {
530                 oldStore.un({
531                     scope: me,
532                     load: me.onLoad,
533                     exception: me.collapse
534                 });
535             }
536             if (!store) {
537                 me.store = null;
538                 if (me.picker) {
539                     me.picker.bindStore(null);
540                 }
541             }
542         }
543         if (store) {
544             if (!initial) {
545                 me.resetToDefault();
546             }
547
548             me.store = Ext.data.StoreManager.lookup(store);
549             me.store.on({
550                 scope: me,
551                 load: me.onLoad,
552                 exception: me.collapse
553             });
554
555             if (me.picker) {
556                 me.picker.bindStore(store);
557             }
558         }
559     },
560
561     onLoad: function() {
562         var me = this,
563             value = me.value;
564
565         me.syncSelection();
566         if (me.picker &amp;&amp; !me.picker.getSelectionModel().hasSelection()) {
567             me.doAutoSelect();
568         }
569     },
570
571 <span id='Ext-form-field-ComboBox-method-doRawQuery'>    /**
572 </span>     * @private
573      * Execute the query with the raw contents within the textfield.
574      */
575     doRawQuery: function() {
576         this.doQuery(this.getRawValue());
577     },
578
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 &lt;code&gt;true&lt;/code&gt; to force the query to execute even if there are currently fewer
584      * characters in the field than the minimum specified by the &lt;code&gt;{@link #minChars}&lt;/code&gt; config option.  It
585      * also clears any filter previously saved in the current store (defaults to &lt;code&gt;false&lt;/code&gt;)
586      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery} handler.
587      */
588     doQuery: function(queryString, forceAll) {
589         queryString = queryString || '';
590
591         // store in object and pass by reference in 'beforequery'
592         // so that client code can modify values.
593         var me = this,
594             qe = {
595                 query: queryString,
596                 forceAll: forceAll,
597                 combo: me,
598                 cancel: false
599             },
600             store = me.store,
601             isLocalMode = me.queryMode === 'local';
602
603         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
604             return false;
605         }
606
607         // get back out possibly modified values
608         queryString = qe.query;
609         forceAll = qe.forceAll;
610
611         // query permitted to run
612         if (forceAll || (queryString.length &gt;= me.minChars)) {
613             // expand before starting query so LoadMask can position itself correctly
614             me.expand();
615
616             // make sure they aren't querying the same thing
617             if (!me.queryCaching || me.lastQuery !== queryString) {
618                 me.lastQuery = queryString;
619
620                 if (isLocalMode) {
621                     // forceAll means no filtering - show whole dataset.
622                     if (forceAll) {
623                         store.clearFilter();
624                     } else {
625                         // Clear filter, but supress event so that the BoundList is not immediately updated.
626                         store.clearFilter(true);
627                         store.filter(me.displayField, queryString);
628                     }
629                 } else {
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.
632                     store.load({
633                         params: me.getParams(queryString)
634                     });
635                 }
636             }
637
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--;
643             }
644
645             if (isLocalMode) {
646                 me.doAutoSelect();
647             }
648             if (me.typeAhead) {
649                 me.doTypeAhead();
650             }
651         }
652         return true;
653     },
654
655     // private
656     getParams: function(queryString) {
657         var p = {},
658             pageSize = this.pageSize,
659             param = this.queryParam;
660
661         if (param) {
662             p[param] = queryString;
663         }
664
665         if (pageSize) {
666             p.start = 0;
667             p.limit = pageSize;
668         }
669         return p;
670     },
671
672 <span id='Ext-form-field-ComboBox-method-doAutoSelect'>    /**
673 </span>     * @private
674      * If the autoSelect config is true, and the picker is open, highlights the first item.
675      */
676     doAutoSelect: function() {
677         var me = this,
678             picker = me.picker,
679             lastSelected, itemNode;
680         if (picker &amp;&amp; me.autoSelect &amp;&amp; me.store.getCount() &gt; 0) {
681             // Highlight the last selected item and scroll it into view
682             lastSelected = picker.getSelectionModel().lastSelected;
683             itemNode = picker.getNode(lastSelected || 0);
684             if (itemNode) {
685                 picker.highlightItem(itemNode);
686                 picker.listEl.scrollChildIntoView(itemNode, false);
687             }
688         }
689     },
690
691     doTypeAhead: function() {
692         if (!this.typeAheadTask) {
693             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
694         }
695         if (this.lastKey != Ext.EventObject.BACKSPACE &amp;&amp; this.lastKey != Ext.EventObject.DELETE) {
696             this.typeAheadTask.delay(this.typeAheadDelay);
697         }
698     },
699
700     onTriggerClick: function() {
701         var me = this;
702         if (!me.readOnly &amp;&amp; !me.disabled) {
703             if (me.isExpanded) {
704                 me.collapse();
705             } else {
706                 me.onFocus({});
707                 if (me.triggerAction === 'all') {
708                     me.doQuery(me.allQuery, true);
709                 } else {
710                     me.doQuery(me.getRawValue());
711                 }
712             }
713             me.inputEl.focus();
714         }
715     },
716
717
718     // store the last key and doQuery if relevant
719     onKeyUp: function(e, t) {
720         var me = this,
721             key = e.getKey();
722
723         if (!me.readOnly &amp;&amp; !me.disabled &amp;&amp; me.editable) {
724             me.lastKey = key;
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
727
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);
731             }
732         }
733
734         if (me.enableKeyEvents) {
735             me.callParent(arguments);
736         }
737     },
738
739     initEvents: function() {
740         var me = this;
741         me.callParent();
742
743         /*
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.
746          */
747         if (!me.enableKeyEvents) {
748             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
749         }
750     },
751
752     createPicker: function() {
753         var me = this,
754             picker,
755             menuCls = Ext.baseCSSPrefix + 'menu',
756             opts = Ext.apply({
757                 selModel: {
758                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
759                 },
760                 floating: true,
761                 hidden: true,
762                 ownerCt: me.ownerCt,
763                 cls: me.el.up('.' + menuCls) ? menuCls : '',
764                 store: me.store,
765                 displayField: me.displayField,
766                 focusOnToFront: false,
767                 pageSize: me.pageSize,
768                 tpl: me.tpl
769             }, me.listConfig, me.defaultListConfig);
770
771         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
772
773         me.mon(picker, {
774             itemclick: me.onItemClick,
775             refresh: me.onListRefresh,
776             scope: me
777         });
778
779         me.mon(picker.getSelectionModel(), 'selectionchange', me.onListSelectionChange, me);
780
781         return picker;
782     },
783
784     onListRefresh: function() {
785         this.alignPicker();
786         this.syncSelection();
787     },
788
789     onItemClick: function(picker, record){
790         /*
791          * If we're doing single selection, the selection change events won't fire when
792          * clicking on the selected element. Detect it here.
793          */
794         var me = this,
795             lastSelection = me.lastSelection,
796             valueField = me.valueField,
797             selected;
798
799         if (!me.multiSelect &amp;&amp; lastSelection) {
800             selected = lastSelection[0];
801             if (selected &amp;&amp; (record.get(valueField) === selected.get(valueField))) {
802                 me.collapse();
803             }
804         }
805     },
806
807     onListSelectionChange: function(list, selectedRecords) {
808         var me = this,
809             isMulti = me.multiSelect,
810             hasRecords = selectedRecords.length &gt; 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 &amp;&amp; me.isExpanded) {
814             if (!isMulti) {
815                 Ext.defer(me.collapse, 1, me);
816             }
817             /*
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.
821              */
822             if (isMulti || hasRecords) {
823                 me.setValue(selectedRecords, false);
824             }
825             if (hasRecords) {
826                 me.fireEvent('select', me, selectedRecords);
827             }
828             me.inputEl.focus();
829         }
830     },
831
832 <span id='Ext-form-field-ComboBox-method-onExpand'>    /**
833 </span>     * @private
834      * Enables the key nav for the BoundList when it is expanded.
835      */
836     onExpand: function() {
837         var me = this,
838             keyNav = me.listKeyNav,
839             selectOnTab = me.selectOnTab,
840             picker = me.getPicker();
841
842         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
843         if (keyNav) {
844             keyNav.enable();
845         } else {
846             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
847                 boundList: picker,
848                 forceKeyDown: true,
849                 tab: function(e) {
850                     if (selectOnTab) {
851                         this.selectHighlighted(e);
852                         me.triggerBlur();
853                     }
854                     // Tab key event is allowed to propagate to field
855                     return true;
856                 }
857             });
858         }
859
860         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
861         if (selectOnTab) {
862             me.ignoreMonitorTab = true;
863         }
864
865         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
866         me.inputEl.focus();
867     },
868
869 <span id='Ext-form-field-ComboBox-method-onCollapse'>    /**
870 </span>     * @private
871      * Disables the key nav for the BoundList when it is collapsed.
872      */
873     onCollapse: function() {
874         var me = this,
875             keyNav = me.listKeyNav;
876         if (keyNav) {
877             keyNav.disable();
878             me.ignoreMonitorTab = false;
879         }
880     },
881
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.
884      * @param r
885      */
886     select: function(r) {
887         this.setValue(r, true);
888     },
889
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
893      * @private
894      */
895     findRecord: function(field, value) {
896         var ds = this.store,
897             idx = ds.findExact(field, value);
898         return idx !== -1 ? ds.getAt(idx) : false;
899     },
900     findRecordByValue: function(value) {
901         return this.findRecord(this.valueField, value);
902     },
903     findRecordByDisplay: function(value) {
904         return this.findRecord(this.displayField, value);
905     },
906
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
915      */
916     setValue: function(value, doSelect) {
917         var me = this,
918             valueNotFoundText = me.valueNotFoundText,
919             inputEl = me.inputEl,
920             i, len, record,
921             models = [],
922             displayTplData = [],
923             processedValue = [];
924
925         if (me.store.loading) {
926             // Called while the Store is loading. Ensure it is processed by the onLoad method.
927             me.value = value;
928             return me;
929         }
930
931         // This method processes multi-values, so ensure value is an array.
932         value = Ext.Array.from(value);
933
934         // Loop through values
935         for (i = 0, len = value.length; i &lt; len; i++) {
936             record = value[i];
937             if (!record || !record.isModel) {
938                 record = me.findRecordByValue(record);
939             }
940             // record found, select it.
941             if (record) {
942                 models.push(record);
943                 displayTplData.push(record.data);
944                 processedValue.push(record.get(me.valueField));
945             }
946             // record was not found, this could happen because
947             // store is not loaded or they set a value not in the store
948             else {
949                 // if valueNotFoundText is defined, display it, otherwise display nothing for this value
950                 if (Ext.isDefined(valueNotFoundText)) {
951                     displayTplData.push(valueNotFoundText);
952                 }
953                 processedValue.push(value[i]);
954             }
955         }
956
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)) {
960             me.value = null;
961         }
962         me.displayTplData = displayTplData; //store for getDisplayValue method
963         me.lastSelection = me.valueModels = models;
964
965         if (inputEl &amp;&amp; me.emptyText &amp;&amp; !Ext.isEmpty(value)) {
966             inputEl.removeCls(me.emptyCls);
967         }
968
969         // Calculate raw value from the collection of Model data
970         me.setRawValue(me.getDisplayValue());
971         me.checkChange();
972
973         if (doSelect !== false) {
974             me.syncSelection();
975         }
976         me.applyEmptyText();
977
978         return me;
979     },
980
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
983      */
984     getDisplayValue: function() {
985         return this.displayTpl.apply(this.displayTplData);
986     },
987
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.
992         var me = this,
993             picker = me.picker,
994             rawValue = me.getRawValue(), //current value of text field
995             value = me.value; //stored value from last selection or setValue() call
996
997         if (me.getDisplayValue() !== rawValue) {
998             value = rawValue;
999             me.value = me.displayTplData = me.valueModels = null;
1000             if (picker) {
1001                 me.ignoreSelection++;
1002                 picker.getSelectionModel().deselectAll();
1003                 me.ignoreSelection--;
1004             }
1005         }
1006
1007         return value;
1008     },
1009
1010     getSubmitValue: function() {
1011         return this.getValue();
1012     },
1013
1014     isEqual: function(v1, v2) {
1015         var fromArray = Ext.Array.from,
1016             i, len;
1017
1018         v1 = fromArray(v1);
1019         v2 = fromArray(v2);
1020         len = v1.length;
1021
1022         if (len !== v2.length) {
1023             return false;
1024         }
1025
1026         for(i = 0; i &lt; len; i++) {
1027             if (v2[i] !== v1[i]) {
1028                 return false;
1029             }
1030         }
1031
1032         return true;
1033     },
1034
1035 <span id='Ext-form-field-ComboBox-method-clearValue'>    /**
1036 </span>     * Clears any value currently set in the ComboBox.
1037      */
1038     clearValue: function() {
1039         this.setValue([]);
1040     },
1041
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.
1044      */
1045     syncSelection: function() {
1046         var me = this,
1047             ExtArray = Ext.Array,
1048             picker = me.picker,
1049             selection, selModel;
1050         if (picker) {
1051             // From the value, find the Models that are in the store's current data
1052             selection = [];
1053             ExtArray.forEach(me.valueModels || [], function(value) {
1054                 if (value &amp;&amp; value.isModel &amp;&amp; me.store.indexOf(value) &gt;= 0) {
1055                     selection.push(value);
1056                 }
1057             });
1058
1059             // Update the selection to match
1060             me.ignoreSelection++;
1061             selModel = picker.getSelectionModel();
1062             selModel.deselectAll();
1063             if (selection.length) {
1064                 selModel.select(selection);
1065             }
1066             me.ignoreSelection--;
1067         }
1068     }
1069 });
1070 </pre>
1071 </body>
1072 </html>