<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
- <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
- <script type="text/javascript" src="../prettify/prettify.js"></script>
+ <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>
</head>
<body onload="prettyPrint(); highlight();">
- <pre class="prettyprint lang-js"><span id='Ext-form-field-ComboBox-method-constructor'><span id='Ext-form-field-ComboBox'>/**
-</span></span> * @class Ext.form.field.ComboBox
- * @extends Ext.form.field.Picker
+ <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.
*
* 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.
*
- * {@img Ext.form.ComboBox/Ext.form.ComboBox.png Ext.form.ComboBox component}
- *
- * ## Example usage:
+ * # Example usage:
*
+ * @example
* // The data store containing the list of states
* var states = Ext.create('Ext.data.Store', {
* fields: ['abbr', 'name'],
* renderTo: Ext.getBody()
* });
*
- * ## Events
+ * # Events
*
* To do something when something in ComboBox is selected, configure the select event:
*
* var cb = new Ext.form.field.ComboBox(yourOptions);
* cb.on('select', yourFunction, yourScope);
*
- * ## Multiple Selection
+ * # Multiple Selection
*
* ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
* {@link #multiSelect} config to `true`.
- *
- * @constructor
- * Create a new ComboBox.
- * @param {Object} config Configuration options
- * @xtype combo
- * @docauthor Jason Johnston <jason@sencha.com>
*/
Ext.define('Ext.form.field.ComboBox', {
extend:'Ext.form.field.Picker',
alias: ['widget.combobox', 'widget.combo'],
<span id='Ext-form-field-ComboBox-cfg-triggerCls'> /**
-</span> * @cfg {String} triggerCls
- * An additional CSS class used to style the trigger button. The trigger will always get the
- * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
- * Defaults to 'x-form-arrow-trigger' for ComboBox.
+</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-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-property-fieldSubTpl'> /**
+</span> * @override
+ */
+ fieldSubTpl: [
+ '<div class="{hiddenDataCls}" role="presentation"></div>',
+ '<input id="{id}" type="{type}" ',
+ '<tpl if="size">size="{size}" </tpl>',
+ '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
+ 'class="{fieldCls} {typeCls}" autocomplete="off" />',
+ '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
+ '{triggerEl}',
+ '<div class="{clearCls}" role="presentation"></div>',
+ '</div>',
+ {
+ 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 (defaults to <tt>undefined</tt>).
- * Acceptable values for this property are:
- * <div class="mdetail-params"><ul>
- * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
- * <li><b>an Array</b> : 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.
- * <div class="mdetail-params"><ul>
- * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
- * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
- * {@link #valueField} and {@link #displayField})</div></li>
- * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
- * 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}.
- * </div></li></ul></div></li></ul></div>
- * <p>See also <tt>{@link #queryMode}</tt>.</p>
+</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 <tt>true</tt>, 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}. (Defaults to <tt>false</tt>.)
+ * 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,
<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 <tt>{@link #multiSelect} = true</tt>. Defaults to <tt>', '</tt>.
+ * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
+ * `{@link #multiSelect} = true`.
*/
delimiter: ', ',
<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 (defaults to 'text').
- * <p>See also <tt>{@link #valueField}</tt>.</p>
+</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
+</span> * @cfg {String} valueField (required)
* The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
* the value of the {@link #displayField} config).
- * <p><b>Note</b>: use of a <tt>valueField</tt> requires the user to make a selection in order for a value to be
- * mapped. See also <tt>{@link #displayField}</tt>.</p>
+ *
+ * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
+ * `{@link #displayField}`.
*/
<span id='Ext-form-field-ComboBox-cfg-triggerAction'> /**
-</span> * @cfg {String} triggerAction The action to execute when the trigger is clicked.
- * <div class="mdetail-params"><ul>
- * <li><b><tt>'all'</tt></b> : <b>Default</b>
- * <p class="sub-desc">{@link #doQuery run the query} specified by the <tt>{@link #allQuery}</tt> config option</p></li>
- * <li><b><tt>'query'</tt></b> :
- * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.</p></li>
- * </ul></div>
- * <p>See also <code>{@link #queryParam}</code>.</p>
+</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 (defaults to '')
+</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 query ({@link Ext.data.proxy.Proxy#extraParam extraParam} name for the store)
- * as it will be passed on the querystring (defaults to <tt>'query'</tt>). If explicitly set to a falsey value it will
- * not be send.
+</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 for queries. Acceptable values are:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>'remote'</tt></b> : <b>Default</b>
- * <p class="sub-desc">Automatically loads the <tt>{@link #store}</tt> the <b>first</b> time the trigger
- * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
- * clicked, set to <tt>'local'</tt> and manually load the store. To force a requery of the store
- * <b>every</b> time the trigger is clicked see <tt>{@link #lastQuery}</tt>.</p></li>
- * <li><b><tt>'local'</tt></b> :
- * <p class="sub-desc">ComboBox loads local data</p>
- * <pre><code>
-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'
-});
- * </code></pre></li>
- * </ul></div>
+ * 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#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',
queryCaching: true,
<span id='Ext-form-field-ComboBox-cfg-pageSize'> /**
-</span> * @cfg {Number} pageSize If greater than <tt>0</tt>, 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.toolbar.Paging#pageSize limit} parameters. Only applies when <tt>{@link #queryMode} = 'remote'</tt>
- * (defaults to <tt>0</tt>).
+</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 <tt>500</tt> if <tt>{@link #queryMode} = 'remote'</tt>
- * or <tt>10</tt> if <tt>{@link #queryMode} = 'local'</tt>)
+</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 <tt>4</tt> if <tt>{@link #queryMode} = 'remote'</tt> or <tt>0</tt> if
- * <tt>{@link #queryMode} = 'local'</tt>, does not apply if <tt>{@link Ext.form.field.Trigger#editable editable} = false</tt>).
+</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 <tt>true</tt> to automatically highlight the first result gathered by the data store
- * in the dropdown list when it is opened. (Defaults to <tt>true</tt>). 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.
+</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 <tt>true</tt> to populate and autoselect the remainder of the text being
- * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
- * to <tt>false</tt>)
+</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 <tt>{@link #typeAhead} = true</tt> (defaults to <tt>250</tt>)
+</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. Defaults to <tt>true</tt>.
+ * Whether the Tab key should select the currently highlighted item.
*/
selectOnTab: true,
<span id='Ext-form-field-ComboBox-cfg-forceSelection'> /**
-</span> * @cfg {Boolean} forceSelection <tt>true</tt> to restrict the selected value to one of the values in the list,
- * <tt>false</tt> to allow the user to set arbitrary text into the field (defaults to <tt>false</tt>)
+</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-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 (defaults to undefined). If this
- * default text is used, it means there is no value set and no validation will occur on this field.
+</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> * The value of the match string used to filter the store. Delete this property to force a requery.
- * Example use:
- * <pre><code>
-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;
- }
- }
-});
- * </code></pre>
- * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
- * configure the combo with <tt>lastQuery=''</tt>. Example use:
- * <pre><code>
-var combo = new Ext.form.field.ComboBox({
- ...
- queryMode: 'local',
- triggerAction: 'all',
- lastQuery: ''
-});
- * </code></pre>
- * @property lastQuery
- * @type String
+</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 id='Ext-form-field-ComboBox-cfg-transform'> /**
-</span> * @cfg {Mixed} transform
- * The id, DOM node or {@link Ext.core.Element} of an existing HTML <tt>&lt;select&gt;</tt> 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> * @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
- * <p>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:</p>
- * <ul>
- * <li>{@link Ext.view.BoundList#cls} - defaults to empty</li>
- * <li>{@link Ext.view.BoundList#emptyText} - defaults to empty string</li>
- * <li>{@link Ext.view.BoundList#getInnerTpl} - defaults to the template defined in BoundList</li>
- * <li>{@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList</li>
- * <li>{@link Ext.view.BoundList#loadingText} - defaults to <tt>'Loading...'</tt></li>
- * <li>{@link Ext.view.BoundList#minWidth} - defaults to <tt>70</tt></li>
- * <li>{@link Ext.view.BoundList#maxWidth} - defaults to <tt>undefined</tt></li>
- * <li>{@link Ext.view.BoundList#maxHeight} - defaults to <tt>300</tt></li>
- * <li>{@link Ext.view.BoundList#resizable} - defaults to <tt>false</tt></li>
- * <li>{@link Ext.view.BoundList#shadow} - defaults to <tt>'sides'</tt></li>
- * <li>{@link Ext.view.BoundList#width} - defaults to <tt>undefined</tt> (automatically set to the width
- * of the ComboBox field if {@link #matchFieldWidth} is true)</li>
- * </ul>
+ * 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} - defaults to empty
+ * - {@link Ext.view.BoundList#emptyText} - defaults to empty string
+ * - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
+ * - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
+ * - {@link Ext.view.BoundList#minWidth} - defaults to `70`
+ * - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
+ * - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
+ * - {@link Ext.view.BoundList#resizable} - defaults to `false`
+ * - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
+ * - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
+ * field if {@link #matchFieldWidth} is true)
*/
//private
transform = me.transform,
transformSelect, isLocalMode;
+ Ext.applyIf(me.renderSelectors, {
+ hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
+ });
+
//<debug>
- if (!store && !transform) {
- Ext.Error.raise('Either a valid store, or a HTML select to transform, must be configured on the combo.');
- }
if (me.typeAhead && me.multiSelect) {
Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
}
//</debug>
this.addEvents(
- // TODO need beforeselect?
-
<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:<ul>
- * <li><code>combo</code> : Ext.form.field.ComboBox <div class="sub-desc">This combo box</div></li>
- * <li><code>query</code> : String <div class="sub-desc">The query string</div></li>
- * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
- * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
- * </ul>
+ * 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',
- /*
- * @event select
+<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'
+ '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 (!store && transform) {
+ if (transform) {
transformSelect = Ext.getDom(transform);
if (transformSelect) {
store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
}
}
- me.bindStore(store, true);
+ me.bindStore(store || 'ext-empty-store', true);
store = me.store;
if (store.autoCreated) {
me.queryMode = 'local';
if (!me.displayTpl) {
me.displayTpl = Ext.create('Ext.XTemplate',
'<tpl for=".">' +
- '{[typeof values === "string" ? values : values.' + me.displayField + ']}' +
+ '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
'<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
'</tpl>'
);
}
},
+<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() {
- var me = this;
- me.doQueryTask.cancel();
- if (me.forceSelection) {
- me.assertValue();
- } else {
- me.collapse();
- }
+ this.doQueryTask.cancel();
+ this.assertValue();
},
// private
value = me.getRawValue(),
rec;
- 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);
+ 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 {
- me.setValue(me.lastSelection);
+ // 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();
// would create it when we may be preping to destroy it
if (oldStore && !initial) {
if (oldStore !== store && oldStore.autoDestroy) {
- oldStore.destroy();
+ oldStore.destroyStore();
} else {
oldStore.un({
scope: me,
var me = this,
value = me.value;
- me.syncSelection();
- if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
- me.doAutoSelect();
+ // 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.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 {
+ me.setValue('');
+ }
+ }
}
},
* Execute the query with the raw contents within the textfield.
*/
doRawQuery: function() {
- this.doQuery(this.getRawValue());
+ 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.
+</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 <tt>true</tt> to force the query to execute even if there are currently fewer
- * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option. It
- * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
- * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery} handler.
+ * @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) {
+ doQuery: function(queryString, forceAll, rawQuery) {
queryString = queryString || '';
// store in object and pass by reference in 'beforequery'
// make sure they aren't querying the same thing
if (!me.queryCaching || me.lastQuery !== queryString) {
me.lastQuery = queryString;
- store.clearFilter(!forceAll);
+
if (isLocalMode) {
- if (!forceAll) {
+ // forceAll means no filtering - show whole dataset.
+ if (forceAll) {
+ store.clearFilter();
+ } else {
+ // Clear filter, but supress event so that the BoundList is not immediately updated.
+ store.clearFilter(true);
store.filter(me.displayField, queryString);
}
} else {
- store.load({
- params: me.getParams(queryString)
- });
+ // 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)
+ });
+ }
}
}
return true;
},
+ 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 p = {},
- pageSize = this.pageSize,
+ var params = {},
param = this.queryParam;
-
+
if (param) {
- p[param] = queryString;
+ params[param] = queryString;
}
-
- if (pageSize) {
- p.start = 0;
- p.limit = pageSize;
- }
- return p;
+ return params;
},
<span id='Ext-form-field-ComboBox-method-doAutoSelect'> /**
if (me.triggerAction === 'all') {
me.doQuery(me.allQuery, true);
} else {
- me.doQuery(me.getRawValue());
+ me.doQuery(me.getRawValue(), false, true);
}
}
me.inputEl.focus();
me.doQueryTask.delay(me.queryDelay);
}
}
-
+
if (me.enableKeyEvents) {
me.callParent(arguments);
}
me.callParent();
/*
- * Setup keyboard handling. If enableKeyEvents is true, we already have
+ * 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();
+ },
createPicker: function() {
var me = this,
picker,
menuCls = Ext.baseCSSPrefix + 'menu',
opts = Ext.apply({
+ pickerField: me,
selModel: {
mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
},
store: me.store,
displayField: me.displayField,
focusOnToFront: false,
- pageSize: me.pageSize
+ pageSize: me.pageSize,
+ tpl: me.tpl
}, me.listConfig, me.defaultListConfig);
picker = me.picker = Ext.create('Ext.view.BoundList', opts);
+ if (me.pageSize) {
+ picker.pagingToolbar.on('beforechange', me.onPageChange, me);
+ }
me.mon(picker, {
itemclick: me.onItemClick,
});
me.mon(picker.getSelectionModel(), {
- selectionChange: me.onListSelectionChange,
+ 'beforeselect': me.onBeforeSelect,
+ 'beforedeselect': me.onBeforeDeselect,
+ 'selectionchange': me.onListSelectionChange,
scope: me
});
return picker;
},
+ alignPicker: function(){
+ var me = this,
+ picker = me.picker,
+ heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
+ heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
+ space = Math.max(heightAbove, heightBelow);
+
+ me.callParent();
+ if (picker.getHeight() > space) {
+ picker.setHeight(space - 5); // have some leeway so we aren't flush against
+ me.doAlign();
+ }
+ },
+
onListRefresh: function() {
this.alignPicker();
this.syncSelection();
},
-
+
onItemClick: function(picker, record){
/*
* If we're doing single selection, the selection change events won't fire when
lastSelection = me.lastSelection,
valueField = me.valueField,
selected;
-
+
if (!me.multiSelect && lastSelection) {
selected = lastSelection[0];
- if (record.get(valueField) === selected.get(valueField)) {
+ if (selected && (record.get(valueField) === selected.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;
+ 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 (!me.multiSelect) {
+ if (!isMulti) {
Ext.defer(me.collapse, 1, me);
}
- me.setValue(selectedRecords, false);
- if (selectedRecords.length > 0) {
+ /*
+ * 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-select'> /**
</span> * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
- * @param r
+ * @param {Object} r
*/
select: function(r) {
this.setValue(r, true);
},
<span id='Ext-form-field-ComboBox-method-findRecord'> /**
-</span> * Find the record by searching for a specific field/value combination
- * Returns an Ext.data.Record or false
- * @private
+</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
+ * 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|Array} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
+ * @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
*/
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;
}
// record was not found, this could happen because
// store is not loaded or they set a value not in the store
else {
- // if valueNotFoundText is defined, display it, otherwise display nothing for this value
- if (Ext.isDefined(valueNotFoundText)) {
+ // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
+ if (!me.forceSelection) {
+ displayTplData.push(value[i]);
+ processedValue.push(value[i]);
+ }
+ // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
+ else if (Ext.isDefined(valueNotFoundText)) {
displayTplData.push(valueNotFoundText);
}
- processedValue.push(value[i]);
}
}
// 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;
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, i;
+ if (!me.hiddenDataEl) {
+ return;
+ }
+ values = Ext.Array.from(values);
+ var 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:me.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 Generate the string value to be displayed in the text field for the currently stored value
+</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);