/* * Ext JS Library 2.3.0 * Copyright(c) 2006-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ /** * @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}. * @constructor * Create a new ComboBox. * @param {Object} config Configuration options */ 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 a {@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 Ext.Editor, defaults to false). */ /** * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to: * {tag: "input", type: "text", size: "24", autocomplete: "off"}) */ /** * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to undefined). This can be * any {@link Ext.data.Store} subclass, a 1-dimensional array (e.g., ['Foo','Bar']) or a 2-dimensional array (e.g., * [['f','Foo'],['b','Bar']]). Arrays will be converted to a {@link Ext.data.SimpleStore} internally. * 1-dimensional arrays will automatically be expanded (each array item will be the combo value and text) and * for multi-dimensional arrays, the value in index 0 of each item will be assumed to be the combo value, while * the value at index 1 is assumed to be the combo text. */ /** * @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) */ /** * @cfg {String} displayField The underlying data field name to bind to this ComboBox (defaults to undefined if * mode = 'remote' or 'text' if transforming a select) */ /** * @cfg {String} valueField The underlying data value name to bind to this ComboBox (defaults to undefined if * mode = 'remote' or 'value' if transforming a select) Note: use of a valueField requires the user to make a selection * in order for a value to be mapped. */ /** * @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. Note that the hidden field's id will also default to this name if {@link #hiddenId} * is not specified. The combo's id and the hidden field's ids should be different, since no two DOM nodes should * share the same id, so if the combo and hidden names are the same, you should specify a unique 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 hiddenName). The hiddenId and combo {@link #id} should be different, since no two DOM * nodes should share the same id. */ /** * @cfg {String} listClass CSS class to apply to 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} 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. Use 'all' to run the * query specified by the allQuery config option (defaults to 'query') */ triggerAction: 'query', /** * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate * (defaults to 4 if remote or 0 if local, does not apply if 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 mode = 'remote' or 10 if mode = 'local') */ queryDelay: 500, /** * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the * filter queries will execute with page start and limit parameters. Only applies when 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 editable = true (defaults to false) */ selectOnFocus:false, /** * @cfg {String} queryParam Name of the query 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 mode = 'remote' (defaults to 'Loading...') */ loadingText: 'Loading...', /** * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false) */ resizable: false, /** * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8) */ handleHeight : 8, /** * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a * traditional select (defaults to true) */ editable: true, /** * @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 Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server) */ mode: 'remote', /** * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if * 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 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 * defaut 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:
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:combo : Ext.form.ComboBox query : String forceAll : Boolean cancel : Boolean * If you wish to preserve the default visual look of list items, add the CSS * class name
x-combo-list-itemto the template's container element. *
* The template must contain one or more substitution parameters using field * names from the Combo's {@link #store Store}. An example of a custom template * would be adding an
ext:qtipattribute which might display other fields * from the Store. *
* The dropdown list is displayed in a DataView. See {@link Ext.DataView} for details.
*/
this.tpl = ''x-combo-list-item'
to dropdown list items.
* A simple CSS selector (e.g. div.some-class or span:first-child) that will be
* used to determine what nodes the DataView which handles the dropdown display will
* be working with.
*/
}
/**
* 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'
});
this.view.on('click', this.onViewClick, this);
this.bindStore(this.store, true);
if(this.resizable){
this.resizer = new Ext.Resizable(this.list, {
pinned:true, handles:'se'
});
this.resizer.on('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 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('loadexception', this.collapse, this);
if(!store){
this.store = null;
if(this.view){
this.view.setStore(null);
}
}
}
if(store){
this.store = Ext.StoreMgr.lookup(store);
this.store.on('beforeload', this.onBeforeLoad, this);
this.store.on('load', this.onLoad, this);
this.store.on('loadexception', this.collapse, this);
if(this.view){
this.view.setStore(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.el.on("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.innerList,
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 && this.listWidth === undefined){
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;
}
},
/**
* Allow or prevent the user from directly editing the field text. If false is passed,
* the user will only be able to select from the items defined in the dropdown list. This method
* is the runtime equivalent of setting the 'editable' config option at config time.
* @param {Boolean} value True to allow the user to directly edit the field text
*/
setEditable : function(value){
if(value == this.editable){
return;
}
this.editable = value;
if(!value){
this.el.dom.setAttribute('readOnly', true);
this.el.on('mousedown', this.onTriggerClick, this);
this.el.addClass('x-combo-noedit');
}else{
this.el.dom.removeAttribute('readOnly');
this.el.un('mousedown', this.onTriggerClick, this);
this.el.removeClass('x-combo-noedit');
}
},
// private
onBeforeLoad : function(){
if(!this.hasFocus){
return;
}
this.innerList.update(this.loadingText ?
'