| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-form-field-ComboBox'>/**</span> * @docauthor Jason Johnston <jason@sencha.com> * * A combobox control with support for autocomplete, remote loading, and many other features. * * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>` * field; the user is able to type freely into the field, and/or pick values from a dropdown selection * list. The user can input any value by default, even if it does not appear in the selection list; * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`. * * The selection list's options are populated from any {@link Ext.data.Store}, including remote * stores. The data items in the store are mapped to each option's displayed text and backing value via * the {@link #valueField} and {@link #displayField} configurations, respectively. * * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user. * * # Example usage: * *     @example *     // The data store containing the list of states *     var states = Ext.create('Ext.data.Store', { *         fields: ['abbr', 'name'], *         data : [ *             {"abbr":"AL", "name":"Alabama"}, *             {"abbr":"AK", "name":"Alaska"}, *             {"abbr":"AZ", "name":"Arizona"} *             //... *         ] *     }); * *     // Create the combo box, attached to the states data store *     Ext.create('Ext.form.ComboBox', { *         fieldLabel: 'Choose State', *         store: states, *         queryMode: 'local', *         displayField: 'name', *         valueField: 'abbr', *         renderTo: Ext.getBody() *     }); * * # Events * * To do something when something in ComboBox is selected, configure the select event: * *     var cb = Ext.create('Ext.form.ComboBox', { *         // all of your config options *         listeners:{ *              scope: yourScope, *              'select': yourFunction *         } *     }); * *     // Alternatively, you can assign events after the object is created: *     var cb = new Ext.form.field.ComboBox(yourOptions); *     cb.on('select', yourFunction, yourScope); * * # Multiple Selection * * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the * {@link #multiSelect} config to `true`. *  * # Filtered Stores *  * If you have a local store that is already filtered, you can use the {@link #lastQuery} config option * to prevent the store from having the filter being cleared on first expand. * * ## Customized combobox * * Both the text shown in dropdown menu and text field can be easily customized: * *     @example *     var states = Ext.create('Ext.data.Store', { *         fields: ['abbr', 'name'], *         data : [ *             {"abbr":"AL", "name":"Alabama"}, *             {"abbr":"AK", "name":"Alaska"}, *             {"abbr":"AZ", "name":"Arizona"} *         ] *     }); * *     Ext.create('Ext.form.ComboBox', { *         fieldLabel: 'Choose State', *         store: states, *         queryMode: 'local', *         valueField: 'abbr', *         renderTo: Ext.getBody(), *         // Template for the dropdown menu. *         // Note the use of "x-boundlist-item" class, *         // this is required to make the items selectable. *         tpl: Ext.create('Ext.XTemplate', *             '<tpl for=".">', *                 '<div class="x-boundlist-item">{abbr} - {name}</div>', *             '</tpl>' *         ), *         // template for the content inside text field *         displayTpl: Ext.create('Ext.XTemplate', *             '<tpl for=".">', *                 '{abbr} - {name}', *             '</tpl>' *         ) *     }); * * See also the {@link #listConfig} option for additional configuration of the dropdown. * */Ext.define('Ext.form.field.ComboBox', {    extend:'Ext.form.field.Picker',    requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager', 'Ext.layout.component.field.ComboBox'],    alternateClassName: 'Ext.form.ComboBox',    alias: ['widget.combobox', 'widget.combo'],    mixins: {        bindable: 'Ext.util.Bindable'        },    componentLayout: 'combobox',<span id='Ext-form-field-ComboBox-cfg-triggerCls'>    /**</span>     * @cfg {String} [triggerCls='x-form-arrow-trigger']     * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}     * by default and `triggerCls` will be **appended** if specified.     */    triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',    <span id='Ext-form-field-ComboBox-cfg-hiddenName'>    /**</span>     * @cfg {String} [hiddenName=""]     * The name of an underlying hidden field which will be synchronized with the underlying value of the combo.     * This option is useful if the combo is part of a form element doing a regular form post. The hidden field     * will not be created unless a hiddenName is specified.     */    hiddenName: '',    <span id='Ext-form-field-ComboBox-property-hiddenDataEl'>    /**</span>     * @property {Ext.Element} hiddenDataEl     * @private     */<span id='Ext-form-field-ComboBox-cfg-hiddenDataCls'>    /**</span>     * @private     * @cfg {String}     * CSS class used to find the {@link #hiddenDataEl}     */    hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',<span id='Ext-form-field-ComboBox-cfg-fieldSubTpl'>    /**</span>     * @cfg     * @inheritdoc     */    fieldSubTpl: [        '<div class="{hiddenDataCls}" role="presentation"></div>',        '<input id="{id}" type="{type}" {inputAttrTpl} class="{fieldCls} {typeCls}" autocomplete="off"',            '<tpl if="value"> value="{[Ext.util.Format.htmlEncode(values.value)]}"</tpl>',            '<tpl if="name"> name="{name}"</tpl>',            '<tpl if="placeholder"> placeholder="{placeholder}"</tpl>',            '<tpl if="size"> size="{size}"</tpl>',            '<tpl if="maxLength !== undefined"> maxlength="{maxLength}"</tpl>',            '<tpl if="readOnly"> readonly="readonly"</tpl>',            '<tpl if="disabled"> disabled="disabled"</tpl>',            '<tpl if="tabIdx"> tabIndex="{tabIdx}"</tpl>',            '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',            '/>',        {            compiled: true,            disableFormats: true        }    ],    getSubTplData: function(){        var me = this;        Ext.applyIf(me.subTplData, {            hiddenDataCls: me.hiddenDataCls        });        return me.callParent(arguments);    },    afterRender: function(){        var me = this;        me.callParent(arguments);        me.setHiddenValue(me.value);    },<span id='Ext-form-field-ComboBox-cfg-store'>    /**</span>     * @cfg {Ext.data.Store/Array} store     * The data source to which this combo is bound. Acceptable values for this property are:     *     *   - **any {@link Ext.data.Store Store} subclass**     *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating     *     {@link Ext.data.Field#name field names} to work with all data components.     *     *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)     *     *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo     *       {@link #valueField} and {@link #displayField})     *     *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)     *     *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo     *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.     *     * See also {@link #queryMode}.     */<span id='Ext-form-field-ComboBox-cfg-multiSelect'>    /**</span>     * @cfg {Boolean} multiSelect     * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple     * items from the dropdown list. The combo's text field will show all selected values separated by the     * {@link #delimiter}.     */    multiSelect: false,    //<locale><span id='Ext-form-field-ComboBox-cfg-delimiter'>    /**</span>     * @cfg {String} delimiter     * The character(s) used to separate the {@link #displayField display values} of multiple selected items when     * `{@link #multiSelect} = true`.     */    delimiter: ', ',    //</locale><span id='Ext-form-field-ComboBox-cfg-displayField'>    /**</span>     * @cfg {String} displayField     * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.     *     * See also `{@link #valueField}`.     */    displayField: 'text',<span id='Ext-form-field-ComboBox-cfg-valueField'>    /**</span>     * @cfg {String} valueField (required)     * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox.     *     * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also     * `{@link #displayField}`.     *     * Defaults to match the value of the {@link #displayField} config.     */<span id='Ext-form-field-ComboBox-cfg-triggerAction'>    /**</span>     * @cfg {String} triggerAction     * The action to execute when the trigger is clicked.     *     *   - **`'all'`** :     *     *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option     *     *   - **`'query'`** :     *     *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.     *     * See also `{@link #queryParam}`.     */    triggerAction: 'all',<span id='Ext-form-field-ComboBox-cfg-allQuery'>    /**</span>     * @cfg {String} allQuery     * The text query to send to the server to return all records for the list with no filtering     */    allQuery: '',<span id='Ext-form-field-ComboBox-cfg-queryParam'>    /**</span>     * @cfg {String} queryParam     * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with     * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.     */    queryParam: 'query',<span id='Ext-form-field-ComboBox-cfg-queryMode'>    /**</span>     * @cfg {String} queryMode     * The mode in which the ComboBox uses the configured Store. Acceptable values are:     *     *   - **`'remote'`** :     *     *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.     *     *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link     *     Ext.data.Store#method-load load}ed.     *     *     A parameter containing the typed string is sent in the load request. The default parameter name for the input     *     string is `query`, but this can be configured using the {@link #queryParam} config.     *     *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:     *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load     *     request which allows the server to further refine the returned dataset.     *     *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for     *     autocomplete.     *     *   - **`'local'`** :     *     *     ComboBox loads local data     *     *         var combo = new Ext.form.field.ComboBox({     *             renderTo: document.body,     *             queryMode: 'local',     *             store: new Ext.data.ArrayStore({     *                 id: 0,     *                 fields: [     *                     'myId',  // numeric value is the key     *                     'displayText'     *                 ],     *                 data: [[1, 'item1'], [2, 'item2']]  // data is local     *             }),     *             valueField: 'myId',     *             displayField: 'displayText',     *             triggerAction: 'all'     *         });     */    queryMode: 'remote',<span id='Ext-form-field-ComboBox-cfg-queryCaching'>    /**</span>     * @cfg {Boolean} [queryCaching=true]     * When true, this prevents the combo from re-querying (either locally or remotely) when the current query     * is the same as the previous query.     */    queryCaching: true,<span id='Ext-form-field-ComboBox-cfg-pageSize'>    /**</span>     * @cfg {Number} pageSize     * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the     * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}     * parameters. Only applies when `{@link #queryMode} = 'remote'`.     */    pageSize: 0,<span id='Ext-form-field-ComboBox-cfg-queryDelay'>    /**</span>     * @cfg {Number} queryDelay     * The length of time in milliseconds to delay between the start of typing and sending the query to filter the     * dropdown list.     *     * Defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`     */<span id='Ext-form-field-ComboBox-cfg-minChars'>    /**</span>     * @cfg {Number} minChars     * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate.     *     * Defaults to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`,     * does not apply if `{@link Ext.form.field.Trigger#editable editable} = false`.     */<span id='Ext-form-field-ComboBox-cfg-autoSelect'>    /**</span>     * @cfg {Boolean} autoSelect     * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is     * opened. A false value would cause nothing in the list to be highlighted automatically, so     * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to     * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.     */    autoSelect: true,<span id='Ext-form-field-ComboBox-cfg-typeAhead'>    /**</span>     * @cfg {Boolean} typeAhead     * `true` to populate and autoselect the remainder of the text being typed after a configurable delay     * ({@link #typeAheadDelay}) if it matches a known value.     */    typeAhead: false,<span id='Ext-form-field-ComboBox-cfg-typeAheadDelay'>    /**</span>     * @cfg {Number} typeAheadDelay     * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`     */    typeAheadDelay: 250,<span id='Ext-form-field-ComboBox-cfg-selectOnTab'>    /**</span>     * @cfg {Boolean} selectOnTab     * Whether the Tab key should select the currently highlighted item.     */    selectOnTab: true,<span id='Ext-form-field-ComboBox-cfg-forceSelection'>    /**</span>     * @cfg {Boolean} forceSelection     * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set     * arbitrary text into the field.     */    forceSelection: false,<span id='Ext-form-field-ComboBox-cfg-growToLongestValue'>    /**</span>     * @cfg {Boolean} growToLongestValue     * `false` to not allow the component to resize itself when its data changes     * (and its {@link #grow} property is `true`)     */    growToLongestValue: true,<span id='Ext-form-field-ComboBox-cfg-valueNotFoundText'>    /**</span>     * @cfg {String} valueNotFoundText     * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will     * be displayed as the field text if defined. If this default text is used, it means there     * is no value set and no validation will occur on this field.     */<span id='Ext-form-field-ComboBox-property-lastQuery'>    /**</span>     * @property {String} lastQuery     * The value of the match string used to filter the store. Delete this property to force a requery. Example use:     *     *     var combo = new Ext.form.field.ComboBox({     *         ...     *         queryMode: 'remote',     *         listeners: {     *             // delete the previous query in the beforequery event or set     *             // combo.lastQuery = null (this will reload the store the next time it expands)     *             beforequery: function(qe){     *                 delete qe.combo.lastQuery;     *             }     *         }     *     });     *     * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the     * combo with `lastQuery=''`. Example use:     *     *     var combo = new Ext.form.field.ComboBox({     *         ...     *         queryMode: 'local',     *         triggerAction: 'all',     *         lastQuery: ''     *     });     */<span id='Ext-form-field-ComboBox-cfg-defaultListConfig'>    /**</span>     * @cfg {Object} defaultListConfig     * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.     */    defaultListConfig: {        loadingHeight: 70,        minWidth: 70,        maxHeight: 300,        shadow: 'sides'    },<span id='Ext-form-field-ComboBox-cfg-transform'>    /**</span>     * @cfg {String/HTMLElement/Ext.Element} transform     * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The     * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}     * will take precedence over this.     */<span id='Ext-form-field-ComboBox-cfg-listConfig'>    /**</span>     * @cfg {Object} listConfig     * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.     * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:     *     *   - {@link Ext.view.BoundList#cls cls} - defaults to empty     *   - {@link Ext.view.BoundList#emptyText emptyText} - defaults to empty string     *   - {@link Ext.view.BoundList#itemSelector itemSelector} - defaults to the value defined in BoundList     *   - {@link Ext.view.BoundList#loadingText loadingText} - defaults to `'Loading...'`     *   - {@link Ext.view.BoundList#minWidth minWidth} - defaults to `70`     *   - {@link Ext.view.BoundList#maxWidth maxWidth} - defaults to `undefined`     *   - {@link Ext.view.BoundList#maxHeight maxHeight} - defaults to `300`     *   - {@link Ext.view.BoundList#resizable resizable} - defaults to `false`     *   - {@link Ext.view.BoundList#shadow shadow} - defaults to `'sides'`     *   - {@link Ext.view.BoundList#width width} - defaults to `undefined` (automatically set to the width of the ComboBox     *     field if {@link #matchFieldWidth} is true)     */    //private    ignoreSelection: 0,    //private, tells the layout to recalculate its startingWidth when a record is removed from its bound store    removingRecords: null,    //private helper    resizeComboToGrow: function () {        var me = this;        return me.grow && me.growToLongestValue;    },    initComponent: function() {        var me = this,            isDefined = Ext.isDefined,            store = me.store,            transform = me.transform,            transformSelect, isLocalMode;        Ext.applyIf(me.renderSelectors, {            hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')        });                //<debug>        if (me.typeAhead && me.multiSelect) {            Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');        }        if (me.typeAhead && !me.editable) {            Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');        }        if (me.selectOnFocus && !me.editable) {            Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');        }        //</debug>        this.addEvents(<span id='Ext-form-field-ComboBox-event-beforequery'>            /**</span>             * @event beforequery             * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel             * property to true.             *             * @param {Object} queryEvent An object that has these properties:             *             *   - `combo` : Ext.form.field.ComboBox             *             *     This combo box             *             *   - `query` : String             *             *     The query string             *             *   - `forceAll` : Boolean             *             *     True to force "all" query             *             *   - `cancel` : Boolean             *             *     Set to true to cancel the query             */            'beforequery',<span id='Ext-form-field-ComboBox-event-select'>            /**</span>             * @event select             * Fires when at least one list item is selected.             * @param {Ext.form.field.ComboBox} combo This combo box             * @param {Array} records The selected records             */            'select',<span id='Ext-form-field-ComboBox-event-beforeselect'>            /**</span>             * @event beforeselect             * Fires before the selected item is added to the collection             * @param {Ext.form.field.ComboBox} combo This combo box             * @param {Ext.data.Record} record The selected record             * @param {Number} index The index of the selected record             */            'beforeselect',<span id='Ext-form-field-ComboBox-event-beforedeselect'>            /**</span>             * @event beforedeselect             * Fires before the deselected item is removed from the collection             * @param {Ext.form.field.ComboBox} combo This combo box             * @param {Ext.data.Record} record The deselected record             * @param {Number} index The index of the deselected record             */            'beforedeselect'        );        // Build store from 'transform' HTML select element's options        if (transform) {            transformSelect = Ext.getDom(transform);            if (transformSelect) {                if (!me.store) {                    store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option){                        return [option.value, option.text];                    });                }                if (!me.name) {                    me.name = transformSelect.name;                }                if (!('value' in me)) {                    me.value = transformSelect.value;                }            }        }        me.bindStore(store || 'ext-empty-store', true);        store = me.store;        if (store.autoCreated) {            me.queryMode = 'local';            me.valueField = me.displayField = 'field1';            if (!store.expanded) {                me.displayField = 'field2';            }        }        if (!isDefined(me.valueField)) {            me.valueField = me.displayField;        }        isLocalMode = me.queryMode === 'local';        if (!isDefined(me.queryDelay)) {            me.queryDelay = isLocalMode ? 10 : 500;        }        if (!isDefined(me.minChars)) {            me.minChars = isLocalMode ? 0 : 4;        }        if (!me.displayTpl) {            me.displayTpl = new Ext.XTemplate(                '<tpl for=".">' +                    '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +                    '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +                '</tpl>'            );        } else if (Ext.isString(me.displayTpl)) {            me.displayTpl = new Ext.XTemplate(me.displayTpl);        }        me.callParent();        me.doQueryTask = new Ext.util.DelayedTask(me.doRawQuery, me);        // store has already been loaded, setValue        if (me.store.getCount() > 0) {            me.setValue(me.value);        }        // render in place of 'transform' select        if (transformSelect) {            me.render(transformSelect.parentNode, transformSelect);            Ext.removeNode(transformSelect);            delete me.renderTo;        }    },<span id='Ext-form-field-ComboBox-method-getStore'>    /**</span>     * Returns the store associated with this ComboBox.     * @return {Ext.data.Store} The store     */    getStore : function(){        return this.store;    },    beforeBlur: function() {        this.doQueryTask.cancel();        this.assertValue();    },    // private    assertValue: function() {        var me = this,            value = me.getRawValue(),            rec;        if (me.forceSelection) {            if (me.multiSelect) {                // For multiselect, check that the current displayed value matches the current                // selection, if it does not then revert to the most recent selection.                if (value !== me.getDisplayValue()) {                    me.setValue(me.lastSelection);                }            } else {                // For single-select, match the displayed value to a record and select it,                // if it does not match a record then revert to the most recent selection.                rec = me.findRecordByDisplay(value);                if (rec) {                    me.select(rec);                } else {                    me.setValue(me.lastSelection);                }            }        }        me.collapse();    },    onTypeAhead: function() {        var me = this,            displayField = me.displayField,            record = me.store.findRecord(displayField, me.getRawValue()),            boundList = me.getPicker(),            newValue, len, selStart;        if (record) {            newValue = record.get(displayField);            len = newValue.length;            selStart = me.getRawValue().length;            boundList.highlightItem(boundList.getNode(record));            if (selStart !== 0 && selStart !== len) {                me.setRawValue(newValue);                me.selectText(selStart, newValue.length);            }        }    },    // invoked when a different store is bound to this combo    // than the original    resetToDefault: Ext.emptyFn,        beforeReset: function() {        this.callParent();        this.clearFilter();        },        onUnbindStore: function(store) {        var picker = this.picker;        if (!store && picker) {            picker.bindStore(null);        }        this.clearFilter();    },        onBindStore: function(store, initial) {        var picker = this.picker;        if (!initial) {            this.resetToDefault();        }        if (picker) {            picker.bindStore(store);        }    },        getStoreListeners: function() {        var me = this;                return {            beforeload: me.onBeforeLoad,            clear: me.onClear,            datachanged: me.onDataChanged,            load: me.onLoad,            exception: me.onException,            remove: me.onRemove        };     },        onBeforeLoad: function(){        // If we're remote loading, the load mask will show which will trigger a deslectAll.        // This selection change will trigger the collapse in onListSelectionChange. As such        // we'll veto it for now and restore selection listeners when we've loaded.        ++this.ignoreSelection;        },        onDataChanged: function() {        var me = this;        if (me.resizeComboToGrow()) {            me.updateLayout();        }    },    onClear: function() {        var me = this;        if (me.resizeComboToGrow()) {            me.removingRecords = true;            me.onDataChanged();        }    },    onRemove: function() {        var me = this;        if (me.resizeComboToGrow()) {            me.removingRecords = true;        }    },    onException: function(){        if (this.ignoreSelection > 0) {            --this.ignoreSelection;        }        this.collapse();        },    onLoad: function() {        var me = this,            value = me.value;        if (me.ignoreSelection > 0) {            --me.ignoreSelection;        }        // If performing a remote query upon the raw value...        if (me.rawQuery) {            me.rawQuery = false;            me.syncSelection();            if (me.picker && !me.picker.getSelectionModel().hasSelection()) {                me.doAutoSelect();            }        }        // If store initial load or triggerAction: 'all' trigger click.        else {            // Set the value on load            if (me.value || me.value === 0) {                me.setValue(me.value);            } else {                // There's no value.                // Highlight the first item in the list if autoSelect: true                if (me.store.getCount()) {                    me.doAutoSelect();                } else {                    // assign whatever empty value we have to prevent change from firing                    me.setValue(me.value);                }            }        }    },<span id='Ext-form-field-ComboBox-method-doRawQuery'>    /**</span>     * @private     * Execute the query with the raw contents within the textfield.     */    doRawQuery: function() {        this.doQuery(this.getRawValue(), false, true);    },<span id='Ext-form-field-ComboBox-method-doQuery'>    /**</span>     * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query     * allowing the query action to be canceled if needed.     *     * @param {String} queryString The SQL query to execute     * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in     * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter     * previously saved in the current store.     * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the     * resulting store load to leave the raw value undisturbed.     * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}     * handler.     */    doQuery: function(queryString, forceAll, rawQuery) {        queryString = queryString || '';        // store in object and pass by reference in 'beforequery'        // so that client code can modify values.        var me = this,            qe = {                query: queryString,                forceAll: forceAll,                combo: me,                cancel: false            },            store = me.store,            isLocalMode = me.queryMode === 'local',            needsRefresh;        if (me.fireEvent('beforequery', qe) === false || qe.cancel) {            return false;        }        // get back out possibly modified values        queryString = qe.query;        forceAll = qe.forceAll;        // query permitted to run        if (forceAll || (queryString.length >= me.minChars)) {            // expand before starting query so LoadMask can position itself correctly            me.expand();            // make sure they aren't querying the same thing            if (!me.queryCaching || me.lastQuery !== queryString) {                me.lastQuery = queryString;                if (isLocalMode) {                    // forceAll means no filtering - show whole dataset.                    store.suspendEvents();                    needsRefresh = me.clearFilter();                    if (queryString || !forceAll) {                        me.activeFilter = new Ext.util.Filter({                            root: 'data',                            property: me.displayField,                            value: queryString                        });                        store.filter(me.activeFilter);                        needsRefresh = true;                    } else {                        delete me.activeFilter;                    }                    store.resumeEvents();                    if (me.rendered && needsRefresh) {                        me.getPicker().refresh();                    }                } else {                    // Set flag for onLoad handling to know how the Store was loaded                    me.rawQuery = rawQuery;                    // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,                    // and these are automatically passed as params with every load call, so we do *not* call clearFilter.                    if (me.pageSize) {                        // if we're paging, we've changed the query so start at page 1.                        me.loadPage(1);                    } else {                        store.load({                            params: me.getParams(queryString)                        });                    }                }            }            // Clear current selection if it does not match the current value in the field            if (me.getRawValue() !== me.getDisplayValue()) {                me.ignoreSelection++;                me.picker.getSelectionModel().deselectAll();                me.ignoreSelection--;            }            if (isLocalMode) {                me.doAutoSelect();            }            if (me.typeAhead) {                me.doTypeAhead();            }        }        return true;    },    <span id='Ext-form-field-ComboBox-method-clearFilter'>    /**</span>     * Clears any previous filters applied by the combo to the store     * @private     * @return {Boolean} True if a filter was removed     */    clearFilter: function() {        var store = this.store,            filter = this.activeFilter,            filters = store.filters,            remaining;                    if (filter) {            if (filters.getCount() > 1) {                // More than 1 existing filter                filters.remove(filter);                remaining = filters.getRange();            }            store.clearFilter(true);            if (remaining) {                store.filter(remaining);            }        }        return !!filter;    },    loadPage: function(pageNum){        this.store.loadPage(pageNum, {            params: this.getParams(this.lastQuery)        });    },    onPageChange: function(toolbar, newPage){        /*         * Return false here so we can call load ourselves and inject the query param.         * We don't want to do this for every store load since the developer may load         * the store through some other means so we won't add the query param.         */        this.loadPage(newPage);        return false;    },    // private    getParams: function(queryString) {        var params = {},            param = this.queryParam;        if (param) {            params[param] = queryString;        }        return params;    },<span id='Ext-form-field-ComboBox-method-doAutoSelect'>    /**</span>     * @private     * If the autoSelect config is true, and the picker is open, highlights the first item.     */    doAutoSelect: function() {        var me = this,            picker = me.picker,            lastSelected, itemNode;        if (picker && me.autoSelect && me.store.getCount() > 0) {            // Highlight the last selected item and scroll it into view            lastSelected = picker.getSelectionModel().lastSelected;            itemNode = picker.getNode(lastSelected || 0);            if (itemNode) {                picker.highlightItem(itemNode);                picker.listEl.scrollChildIntoView(itemNode, false);            }        }    },    doTypeAhead: function() {        if (!this.typeAheadTask) {            this.typeAheadTask = new Ext.util.DelayedTask(this.onTypeAhead, this);        }        if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {            this.typeAheadTask.delay(this.typeAheadDelay);        }    },    onTriggerClick: function() {        var me = this;        if (!me.readOnly && !me.disabled) {            if (me.isExpanded) {                me.collapse();            } else {                me.onFocus({});                if (me.triggerAction === 'all') {                    me.doQuery(me.allQuery, true);                } else {                    me.doQuery(me.getRawValue(), false, true);                }            }            me.inputEl.focus();        }    },    // store the last key and doQuery if relevant    onKeyUp: function(e, t) {        var me = this,            key = e.getKey();        if (!me.readOnly && !me.disabled && me.editable) {            me.lastKey = key;            // we put this in a task so that we can cancel it if a user is            // in and out before the queryDelay elapses            // perform query w/ any normal key or backspace or delete            if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {                me.doQueryTask.delay(me.queryDelay);            }        }        if (me.enableKeyEvents) {            me.callParent(arguments);        }    },    initEvents: function() {        var me = this;        me.callParent();        /*         * Setup keyboard handling. If enableKeyEvents is true, we already have         * a listener on the inputEl for keyup, so don't create a second.         */        if (!me.enableKeyEvents) {            me.mon(me.inputEl, 'keyup', me.onKeyUp, me);        }    },    onDestroy: function() {        this.bindStore(null);        this.callParent();    },    // The picker (the dropdown) must have its zIndex managed by the same ZIndexManager which is    // providing the zIndex of our Container.    onAdded: function() {        var me = this;        me.callParent(arguments);        if (me.picker) {            me.picker.ownerCt = me.up('[floating]');            me.picker.registerWithOwnerCt();        }    },    createPicker: function() {        var me = this,            picker,            pickerCfg = Ext.apply({                xtype: 'boundlist',                pickerField: me,                selModel: {                    mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'                },                floating: true,                hidden: true,                store: me.store,                displayField: me.displayField,                focusOnToFront: false,                pageSize: me.pageSize,                tpl: me.tpl            }, me.listConfig, me.defaultListConfig);        picker = me.picker = Ext.widget(pickerCfg);        if (me.pageSize) {            picker.pagingToolbar.on('beforechange', me.onPageChange, me);        }        me.mon(picker, {            itemclick: me.onItemClick,            refresh: me.onListRefresh,            scope: me        });        me.mon(picker.getSelectionModel(), {            beforeselect: me.onBeforeSelect,            beforedeselect: me.onBeforeDeselect,            selectionchange: me.onListSelectionChange,            scope: me        });        return picker;    },    alignPicker: function(){        var me = this,            picker = me.getPicker(),            heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,            heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),            space = Math.max(heightAbove, heightBelow);        // Allow the picker to height itself naturally.        if (picker.height) {            delete picker.height;            picker.updateLayout();        }        // Then ensure that vertically, the dropdown will fit into the space either above or below the inputEl.        if (picker.getHeight() > space - 5) {            picker.setHeight(space - 5); // have some leeway so we aren't flush against        }        me.callParent();    },    onListRefresh: function() {        this.alignPicker();        this.syncSelection();    },    onItemClick: function(picker, record){        /*         * If we're doing single selection, the selection change events won't fire when         * clicking on the selected element. Detect it here.         */        var me = this,            selection = me.picker.getSelectionModel().getSelection(),            valueField = me.valueField;        if (!me.multiSelect && selection.length) {            if (record.get(valueField) === selection[0].get(valueField)) {                // Make sure we also update the display value if it's only partial                me.displayTplData = [record.data];                me.setRawValue(me.getDisplayValue());                me.collapse();            }        }    },    onBeforeSelect: function(list, record) {        return this.fireEvent('beforeselect', this, record, record.index);    },    onBeforeDeselect: function(list, record) {        return this.fireEvent('beforedeselect', this, record, record.index);    },    onListSelectionChange: function(list, selectedRecords) {        var me = this,            isMulti = me.multiSelect,            hasRecords = selectedRecords.length > 0;        // Only react to selection if it is not called from setValue, and if our list is        // expanded (ignores changes to the selection model triggered elsewhere)        if (!me.ignoreSelection && me.isExpanded) {            if (!isMulti) {                Ext.defer(me.collapse, 1, me);            }            /*             * Only set the value here if we're in multi selection mode or we have             * a selection. Otherwise setValue will be called with an empty value             * which will cause the change event to fire twice.             */            if (isMulti || hasRecords) {                me.setValue(selectedRecords, false);            }            if (hasRecords) {                me.fireEvent('select', me, selectedRecords);            }            me.inputEl.focus();        }    },<span id='Ext-form-field-ComboBox-method-onExpand'>    /**</span>     * @private     * Enables the key nav for the BoundList when it is expanded.     */    onExpand: function() {        var me = this,            keyNav = me.listKeyNav,            selectOnTab = me.selectOnTab,            picker = me.getPicker();        // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.        if (keyNav) {            keyNav.enable();        } else {            keyNav = me.listKeyNav = new Ext.view.BoundListKeyNav(this.inputEl, {                boundList: picker,                forceKeyDown: true,                tab: function(e) {                    if (selectOnTab) {                        this.selectHighlighted(e);                        me.triggerBlur();                    }                    // Tab key event is allowed to propagate to field                    return true;                }            });        }        // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab        if (selectOnTab) {            me.ignoreMonitorTab = true;        }        Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker        me.inputEl.focus();    },<span id='Ext-form-field-ComboBox-method-onCollapse'>    /**</span>     * @private     * Disables the key nav for the BoundList when it is collapsed.     */    onCollapse: function() {        var me = this,            keyNav = me.listKeyNav;        if (keyNav) {            keyNav.disable();            me.ignoreMonitorTab = false;        }    },<span id='Ext-form-field-ComboBox-method-select'>    /**</span>     * Selects an item by a {@link Ext.data.Model Model}, or by a key value.     * @param {Object} r     */    select: function(r) {        this.setValue(r, true);    },<span id='Ext-form-field-ComboBox-method-findRecord'>    /**</span>     * Finds the record by searching for a specific field/value combination.     * @param {String} field The name of the field to test.     * @param {Object} value The value to match the field against.     * @return {Ext.data.Model} The matched record or false.     */    findRecord: function(field, value) {        var ds = this.store,            idx = ds.findExact(field, value);        return idx !== -1 ? ds.getAt(idx) : false;    },<span id='Ext-form-field-ComboBox-method-findRecordByValue'>    /**</span>     * Finds the record by searching values in the {@link #valueField}.     * @param {Object} value The value to match the field against.     * @return {Ext.data.Model} The matched record or false.     */    findRecordByValue: function(value) {        return this.findRecord(this.valueField, value);    },<span id='Ext-form-field-ComboBox-method-findRecordByDisplay'>    /**</span>     * Finds the record by searching values in the {@link #displayField}.     * @param {Object} value The value to match the field against.     * @return {Ext.data.Model} The matched record or false.     */    findRecordByDisplay: function(value) {        return this.findRecord(this.displayField, value);    },<span id='Ext-form-field-ComboBox-method-setValue'>    /**</span>     * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that     * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the     * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be     * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.     * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},     * or an Array of Strings or Models.     * @return {Ext.form.field.Field} this     */    setValue: function(value, doSelect) {        var me = this,            valueNotFoundText = me.valueNotFoundText,            inputEl = me.inputEl,            i, len, record,            dataObj,            matchedRecords = [],            displayTplData = [],            processedValue = [];        if (me.store.loading) {            // Called while the Store is loading. Ensure it is processed by the onLoad method.            me.value = value;            me.setHiddenValue(me.value);            return me;        }        // This method processes multi-values, so ensure value is an array.        value = Ext.Array.from(value);        // Loop through values, matching each from the Store, and collecting matched records        for (i = 0, len = value.length; i < len; i++) {            record = value[i];            if (!record || !record.isModel) {                record = me.findRecordByValue(record);            }            // record found, select it.            if (record) {                matchedRecords.push(record);                displayTplData.push(record.data);                processedValue.push(record.get(me.valueField));            }            // record was not found, this could happen because            // store is not loaded or they set a value not in the store            else {                // If we are allowing insertion of values not represented in the Store, then push the value and                // create a fake record data object to push as a display value for use by the displayTpl                if (!me.forceSelection) {                    processedValue.push(value[i]);                    dataObj = {};                    dataObj[me.displayField] = value[i];                    displayTplData.push(dataObj);                    // TODO: Add config to create new records on selection of a value that has no match in the Store                }                // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value                else if (Ext.isDefined(valueNotFoundText)) {                    displayTplData.push(valueNotFoundText);                }            }        }        // Set the value of this field. If we are multiselecting, then that is an array.        me.setHiddenValue(processedValue);        me.value = me.multiSelect ? processedValue : processedValue[0];        if (!Ext.isDefined(me.value)) {            me.value = null;        }        me.displayTplData = displayTplData; //store for getDisplayValue method        me.lastSelection = me.valueModels = matchedRecords;        if (inputEl && me.emptyText && !Ext.isEmpty(value)) {            inputEl.removeCls(me.emptyCls);        }        // Calculate raw value from the collection of Model data        me.setRawValue(me.getDisplayValue());        me.checkChange();        if (doSelect !== false) {            me.syncSelection();        }        me.applyEmptyText();        return me;    },<span id='Ext-form-field-ComboBox-method-setHiddenValue'>    /**</span>     * @private     * Set the value of {@link #hiddenDataEl}     * Dynamically adds and removes input[type=hidden] elements     */    setHiddenValue: function(values){        var me = this,            name = me.hiddenName,             i,            dom, childNodes, input, valueCount, childrenCount;                    if (!me.hiddenDataEl || !name) {            return;        }        values = Ext.Array.from(values);        dom = me.hiddenDataEl.dom;        childNodes = dom.childNodes;        input = childNodes[0];        valueCount = values.length;        childrenCount = childNodes.length;                if (!input && valueCount > 0) {            me.hiddenDataEl.update(Ext.DomHelper.markup({                tag: 'input',                 type: 'hidden',                 name: name            }));            childrenCount = 1;            input = dom.firstChild;        }        while (childrenCount > valueCount) {            dom.removeChild(childNodes[0]);            -- childrenCount;        }        while (childrenCount < valueCount) {            dom.appendChild(input.cloneNode(true));            ++ childrenCount;        }        for (i = 0; i < valueCount; i++) {            childNodes[i].value = values[i];        }    },<span id='Ext-form-field-ComboBox-method-getDisplayValue'>    /**</span>     * @private Generates the string value to be displayed in the text field for the currently stored value     */    getDisplayValue: function() {        return this.displayTpl.apply(this.displayTplData);    },    getValue: function() {        // If the user has not changed the raw field value since a value was selected from the list,        // then return the structured value from the selection. If the raw field value is different        // than what would be displayed due to selection, return that raw value.        var me = this,            picker = me.picker,            rawValue = me.getRawValue(), //current value of text field            value = me.value; //stored value from last selection or setValue() call        if (me.getDisplayValue() !== rawValue) {            value = rawValue;            me.value = me.displayTplData = me.valueModels = null;            if (picker) {                me.ignoreSelection++;                picker.getSelectionModel().deselectAll();                me.ignoreSelection--;            }        }        return value;    },    getSubmitValue: function() {        return this.getValue();    },    isEqual: function(v1, v2) {        var fromArray = Ext.Array.from,            i, len;        v1 = fromArray(v1);        v2 = fromArray(v2);        len = v1.length;        if (len !== v2.length) {            return false;        }        for(i = 0; i < len; i++) {            if (v2[i] !== v1[i]) {                return false;            }        }        return true;    },<span id='Ext-form-field-ComboBox-method-clearValue'>    /**</span>     * Clears any value currently set in the ComboBox.     */    clearValue: function() {        this.setValue([]);    },<span id='Ext-form-field-ComboBox-method-syncSelection'>    /**</span>     * @private Synchronizes the selection in the picker to match the current value of the combobox.     */    syncSelection: function() {        var me = this,            picker = me.picker,            selection, selModel,            values = me.valueModels || [],            vLen  = values.length, v, value;        if (picker) {            // From the value, find the Models that are in the store's current data            selection = [];            for (v = 0; v < vLen; v++) {                value = values[v];                if (value && value.isModel && me.store.indexOf(value) >= 0) {                    selection.push(value);                }            }            // Update the selection to match            me.ignoreSelection++;            selModel = picker.getSelectionModel();            selModel.deselectAll();            if (selection.length) {                selModel.select(selection);            }            me.ignoreSelection--;        }    },        onEditorTab: function(e){        var keyNav = this.listKeyNav;                if (this.selectOnTab && keyNav) {            keyNav.selectHighlighted(e);        }    }});</pre></body></html>
 |