/** * @class Ext.form.ComboBox * @extends Ext.form.TriggerField *A combobox control with support for autocomplete, remote-loading, paging and many other features.
*A ComboBox works in a similar manner to a traditional HTML <select> field. The difference is * that to submit the {@link #valueField}, you must specify a {@link #hiddenName} to create a hidden input * field to hold the value of the valueField. The {@link #displayField} is shown in the text field * which is named according to the {@link #name}.
*Events
*To do something when something in ComboBox is selected, configure the select event:
* *var cb = new 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.ComboBox(yourOptions); cb.on('select', yourFunction, yourScope); *
ComboBox in Grid
*If using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a {@link Ext.grid.Column#renderer renderer} * will be needed to show the displayField when the editor is not active. Set up the renderer manually, or implement * a reusable render, for example:
* *// create reusable renderer Ext.util.Format.comboRenderer = function(combo){ return function(value){ var record = combo.findRecord(combo.{@link #valueField}, value); return record ? record.get(combo.{@link #displayField}) : combo.{@link #valueNotFoundText}; } } // create the combo instance var combo = new Ext.form.ComboBox({ {@link #typeAhead}: true, {@link #triggerAction}: 'all', {@link #lazyRender}:true, {@link #mode}: 'local', {@link #store}: new Ext.data.ArrayStore({ id: 0, fields: [ 'myId', 'displayText' ], data: [[1, 'item1'], [2, 'item2']] }), {@link #valueField}: 'myId', {@link #displayField}: 'displayText' }); // snippet of column model used within grid var cm = new Ext.grid.ColumnModel([{ ... },{ header: "Some Header", dataIndex: 'whatever', width: 130, editor: combo, // specify reference to combo instance renderer: Ext.util.Format.comboRenderer(combo) // pass combo instance to reusable renderer }, ... ]); *
Filtering
*A ComboBox {@link #doQuery uses filtering itself}, for information about filtering the ComboBox * store manually see {@link #lastQuery}.
* @constructor * Create a new ComboBox. * @param {Object} config Configuration options * @xtype combo */ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { /** * @cfg {Mixed} transform The id, DOM node or element of an existing HTML SELECT to convert to a ComboBox. * Note that if you specify this and the combo is going to be in an {@link Ext.form.BasicForm} or * {@link Ext.form.FormPanel}, you must also set {@link #lazyRender} = true. */ /** * @cfg {Boolean} lazyRender true to prevent the ComboBox from rendering until requested * (should always be used when rendering into an {@link Ext.Editor} (e.g. {@link Ext.grid.EditorGridPanel Grids}), * defaults to false). */ /** * @cfg {String/Object} autoCreateA {@link Ext.DomHelper DomHelper} element spec, or true for a default * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:
**/ /** * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to undefined). * Acceptable values for this property are: *{tag: "input", type: "text", size: "24", autocomplete: "off"}
**
- any {@link Ext.data.Store Store} subclass
*- an Array : Arrays will be converted to a {@link Ext.data.ArrayStore} internally. *
*
- 1-dimensional array : (e.g., ['Foo','Bar'])
** A 1-dimensional array will automatically be expanded (each array item will be the combo * {@link #valueField value} and {@link #displayField text})- 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 value}, while the value at index 1 is assumed to be the combo {@link #displayField text}. *See also {@link #mode}.
*/ /** * @cfg {String} title If supplied, a header element is created containing this text and added into the top of * the dropdown list (defaults to undefined, with no header element) */ // private defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"}, /** * @cfg {Number} listWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown * list (defaults to the width of the ComboBox field). See also {@link #minListWidth} */ /** * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'text' if * {@link #transform transforming a select} a select). *See also {@link #valueField}.
*Note: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not * active.
*/ /** * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'value' if * {@link #transform transforming a select}). *Note: use of a valueField requires the user to make a selection in order for a value to be * mapped. See also {@link #hiddenName}, {@link #hiddenValue}, and {@link #displayField}.
*/ /** * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the * field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically * post during a form submission. See also {@link #valueField}. *Note: the hidden field's id will also default to this name if {@link #hiddenId} is not specified. * The ComboBox {@link Ext.Component#id id} and the {@link #hiddenId} should be different, since * no two DOM nodes should share the same id. So, if the ComboBox {@link Ext.form.Field#name name} and * hiddenName are the same, you should specify a unique {@link #hiddenId}.
*/ /** * @cfg {String} hiddenId If {@link #hiddenName} is specified, hiddenId can also be provided * to give the hidden field a unique id (defaults to the {@link #hiddenName}). The hiddenId * and combo {@link Ext.Component#id id} should be different, since no two DOM * nodes should share the same id. */ /** * @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is * specified to contain the selected {@link #valueField}, from the Store. Defaults to the configured * {@link Ext.form.Field#value value}. */ /** * @cfg {String} listClass The CSS class to add to the predefined 'x-combo-list' class * applied the dropdown list element (defaults to ''). */ listClass : '', /** * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list * (defaults to 'x-combo-selected') */ selectedClass : 'x-combo-selected', /** * @cfg {String} listEmptyText The empty text to display in the data view if no items are found. * (defaults to '') */ listEmptyText: '', /** * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always * get the class 'x-form-trigger' and triggerClass will be appended if specified * (defaults to 'x-form-arrow-trigger' which displays a downward arrow icon). */ triggerClass : 'x-form-arrow-trigger', /** * @cfg {Boolean/String} shadow true or "sides" for the default effect, "frame" for * 4-way shadow, and "drop" for bottom-right */ shadow : 'sides', /** * @cfg {String} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details * on supported anchor positions (defaults to 'tl-bl?') */ listAlign : 'tl-bl?', /** * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown * (defaults to 300) */ maxHeight : 300, /** * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its * distance to the viewport edges (defaults to 90) */ minHeight : 90, /** * @cfg {String} triggerAction The action to execute when the trigger is clicked. ***
- 'query' : Default *
*{@link #doQuery run the query} using the {@link Ext.form.Field#getRawValue raw value}.
- 'all' : *
*{@link #doQuery run the query} specified by the {@link #allQuery} config option
See also
*/ triggerAction : 'query', /** * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and * {@link #typeAhead} activate (defaults to 4 if {@link #mode} = 'remote' or 0 if * {@link #mode} = 'local', does not apply if * {@link Ext.form.TriggerField#editable editable} = false). */ minChars : 4, /** * @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 (defaults * to false) */ typeAhead : false, /** * @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 #mode} = 'remote' * or 10 if {@link #mode} = 'local') */ queryDelay : 500, /** * @cfg {Number} pageSize If greater than 0, a {@link Ext.PagingToolbar} is displayed in the * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and * {@link Ext.PagingToolbar#pageSize limit} parameters. Only applies when {@link #mode} = 'remote' * (defaults to 0). */ pageSize : 0, /** * @cfg {Boolean} selectOnFocus true to select any existing text in the field immediately on focus. * Only applies when {@link Ext.form.TriggerField#editable editable} = true (defaults to * false). */ selectOnFocus : false, /** * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store) * as it will be passed on the querystring (defaults to 'query') */ queryParam : 'query', /** * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies * when {@link #mode} = 'remote' (defaults to 'Loading...') */ loadingText : 'Loading...', /** * @cfg {Boolean} resizable true to add a resize handle to the bottom of the dropdown list * (creates an {@link Ext.Resizable} with 'se' {@link Ext.Resizable#pinned pinned} handles). * Defaults to false. */ resizable : false, /** * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if * {@link #resizable} = true (defaults to 8) */ handleHeight : 8, /** * @cfg {String} allQuery The text query to send to the server to return all records for the list * with no filtering (defaults to '') */ allQuery: '', /** * @cfg {String} mode Acceptable values are: *{@link #queryParam}
.*/ mode: 'remote', /** * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will * be ignored if {@link #listWidth} has a higher value) */ minListWidth : 70, /** * @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 (defaults to false) */ forceSelection : false, /** * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed * if {@link #typeAhead} = true (defaults to 250) */ typeAheadDelay : 250, /** * @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. */ /** * @cfg {Boolean} lazyInit true to not initialize the list for this combo until the field is focused * (defaults to true) */ lazyInit : true, /** * The value of the match string used to filter the store. Delete this property to force a requery. * Example use: **
- 'remote' : Default *
*Automatically loads the {@link #store} the first 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 'local' and manually load the store. To force a requery of the store * every time the trigger is clicked see {@link #lastQuery}.
- 'local' : *
*ComboBox loads local data
*var combo = new Ext.form.ComboBox({ renderTo: document.body, mode: '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' }); *
* 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.ComboBox({ ... mode: '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; } } }); *
* @property lastQuery * @type String */ // private initComponent : function(){ Ext.form.ComboBox.superclass.initComponent.call(this); this.addEvents( /** * @event expand * Fires when the dropdown list is expanded * @param {Ext.form.ComboBox} combo This combo box */ 'expand', /** * @event collapse * Fires when the dropdown list is collapsed * @param {Ext.form.ComboBox} combo This combo box */ 'collapse', /** * @event beforeselect * Fires before a list item is selected. Return false to cancel the selection. * @param {Ext.form.ComboBox} combo This combo box * @param {Ext.data.Record} record The data record returned from the underlying store * @param {Number} index The index of the selected item in the dropdown list */ 'beforeselect', /** * @event select * Fires when a list item is selected * @param {Ext.form.ComboBox} combo This combo box * @param {Ext.data.Record} record The data record returned from the underlying store * @param {Number} index The index of the selected item in the dropdown list */ 'select', /** * @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:var combo = new Ext.form.ComboBox({ ... mode: 'local', triggerAction: 'all', lastQuery: '' }); *
combo
: Ext.form.ComboBox query
: String forceAll
: Boolean cancel
: Boolean The template string, or {@link Ext.XTemplate} instance to * use to display each item in the dropdown list. The dropdown list is displayed in a * DataView. See {@link #view}.
*The default template string is:
'<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
*
* Override the default value to create custom UI layouts for items in the list. * For example:
'<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
*
* The template must contain one or more substitution parameters using field * names from the Combo's {@link #store Store}. In the example above an *
ext:qtipattribute is added to display other fields from the Store. *
To preserve the default visual look of list items, add the CSS class name *
x-combo-list-itemto the template's container element. *
Also see {@link #itemSelector} for additional details.
*/ this.tpl = 'A simple CSS selector (e.g. div.some-class or span:first-child) that will be * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown * display will be working with.
*Note: this setting is required if a custom XTemplate has been * specified in {@link #tpl} which assigns a class other than
'x-combo-list-item'* to dropdown list items */ } /** * The {@link Ext.DataView DataView} used to display the ComboBox's options. * @type Ext.DataView */ this.view = new Ext.DataView({ applyTo: this.innerList, tpl: this.tpl, singleSelect: true, selectedClass: this.selectedClass, itemSelector: this.itemSelector || '.' + cls + '-item', emptyText: this.listEmptyText }); this.mon(this.view, 'click', this.onViewClick, this); this.bindStore(this.store, true); if(this.resizable){ this.resizer = new Ext.Resizable(this.list, { pinned:true, handles:'se' }); this.mon(this.resizer, 'resize', function(r, w, h){ this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight; this.listWidth = w; this.innerList.setWidth(w - this.list.getFrameWidth('lr')); this.restrictHeight(); }, this); this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px'); } } }, /** *
Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.
* A custom implementation may be provided as a configuration option if the floating list needs to be rendered * to a different Element. An example might be rendering the list inside a Menu so that clicking * the list does not hide the Menu:
var store = new Ext.data.ArrayStore({
autoDestroy: true,
fields: ['initials', 'fullname'],
data : [
['FF', 'Fred Flintstone'],
['BR', 'Barney Rubble']
]
});
var combo = new Ext.form.ComboBox({
store: store,
displayField: 'fullname',
emptyText: 'Select a name...',
forceSelection: true,
getListParent: function() {
return this.el.up('.x-menu');
},
iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
mode: 'local',
selectOnFocus: true,
triggerAction: 'all',
typeAhead: true,
width: 135
});
var menu = new Ext.menu.Menu({
id: 'mainMenu',
items: [
combo // A Field in a Menu
]
});
*/
getListParent : function() {
return document.body;
},
/**
* Returns the store associated with this combo.
* @return {Ext.data.Store} The store
*/
getStore : function(){
return this.store;
},
// private
bindStore : function(store, initial){
if(this.store && !initial){
this.store.un('beforeload', this.onBeforeLoad, this);
this.store.un('load', this.onLoad, this);
this.store.un('exception', this.collapse, this);
if(this.store !== store && this.store.autoDestroy){
this.store.destroy();
}
if(!store){
this.store = null;
if(this.view){
this.view.bindStore(null);
}
}
}
if(store){
if(!initial) {
this.lastQuery = null;
if(this.pageTb) {
this.pageTb.bindStore(store);
}
}
this.store = Ext.StoreMgr.lookup(store);
this.store.on({
scope: this,
beforeload: this.onBeforeLoad,
load: this.onLoad,
exception: this.collapse
});
if(this.view){
this.view.bindStore(store);
}
}
},
// private
initEvents : function(){
Ext.form.ComboBox.superclass.initEvents.call(this);
this.keyNav = new Ext.KeyNav(this.el, {
"up" : function(e){
this.inKeyMode = true;
this.selectPrev();
},
"down" : function(e){
if(!this.isExpanded()){
this.onTriggerClick();
}else{
this.inKeyMode = true;
this.selectNext();
}
},
"enter" : function(e){
this.onViewClick();
this.delayedCheck = true;
this.unsetDelayCheck.defer(10, this);
},
"esc" : function(e){
this.collapse();
},
"tab" : function(e){
this.onViewClick(false);
return true;
},
scope : this,
doRelay : function(foo, bar, hname){
if(hname == 'down' || this.scope.isExpanded()){
return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
}
return true;
},
forceKeyDown : true
});
this.queryDelay = Math.max(this.queryDelay || 10,
this.mode == 'local' ? 10 : 250);
this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
if(this.typeAhead){
this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
}
if(this.editable !== false && !this.enableKeyEvents){
this.mon(this.el, 'keyup', this.onKeyUp, this);
}
},
// private
onDestroy : function(){
if (this.dqTask){
this.dqTask.cancel();
this.dqTask = null;
}
this.bindStore(null);
Ext.destroy(
this.resizer,
this.view,
this.pageTb,
this.list
);
Ext.form.ComboBox.superclass.onDestroy.call(this);
},
// private
unsetDelayCheck : function(){
delete this.delayedCheck;
},
// private
fireKey : function(e){
var fn = function(ev){
if (ev.isNavKeyPress() && !this.isExpanded() && !this.delayedCheck) {
this.fireEvent("specialkey", this, ev);
}
};
//For some reason I can't track down, the events fire in a different order in webkit.
//Need a slight delay here
if(this.inEditor && Ext.isWebKit && e.getKey() == e.TAB){
fn.defer(10, this, [new Ext.EventObjectImpl(e)]);
}else{
fn.call(this, e);
}
},
// private
onResize : function(w, h){
Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
if(this.list && !Ext.isDefined(this.listWidth)){
var lw = Math.max(w, this.minListWidth);
this.list.setWidth(lw);
this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
}
},
// private
onEnable : function(){
Ext.form.ComboBox.superclass.onEnable.apply(this, arguments);
if(this.hiddenField){
this.hiddenField.disabled = false;
}
},
// private
onDisable : function(){
Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
if(this.hiddenField){
this.hiddenField.disabled = true;
}
},
// private
onBeforeLoad : function(){
if(!this.hasFocus){
return;
}
this.innerList.update(this.loadingText ?
'