X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6:/pkgs/pkg-forms-debug.js diff --git a/pkgs/pkg-forms-debug.js b/pkgs/pkg-forms-debug.js index e3776075..ee84c537 100644 --- a/pkgs/pkg-forms-debug.js +++ b/pkgs/pkg-forms-debug.js @@ -1,8 +1,8 @@ /*! - * Ext JS Library 3.0.0 - * Copyright(c) 2006-2009 Ext JS, LLC - * licensing@extjs.com - * http://www.extjs.com/license + * Ext JS Library 3.3.1 + * Copyright(c) 2006-2010 Sencha Inc. + * licensing@sencha.com + * http://www.sencha.com/license */ /** * @class Ext.form.Field @@ -14,9 +14,15 @@ * @xtype field */ Ext.form.Field = Ext.extend(Ext.BoxComponent, { + /** + *

The label Element associated with this Field. Only available after this Field has been rendered by a + * {@link form Ext.layout.FormLayout} layout manager.

+ * @type Ext.Element + * @property label + */ /** * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults - * to "text"). The types "file" and "password" must be used to render those field types currently -- there are + * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are * no separate Ext components for those. Note that if you use inputType:'file', {@link #emptyText} * is not supported and should be avoided. */ @@ -28,32 +34,37 @@ Ext.form.Field = Ext.extend(Ext.BoxComponent, { * @cfg {Mixed} value A value to initialize this field with (defaults to undefined). */ /** - * @cfg {String} name The field's HTML name attribute (defaults to ""). + * @cfg {String} name The field's HTML name attribute (defaults to ''). * Note: this property must be set if this field is to be automatically included with * {@link Ext.form.BasicForm#submit form submit()}. */ /** - * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ""). + * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ''). */ /** - * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid") + * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to 'x-form-invalid') */ - invalidClass : "x-form-invalid", + invalidClass : 'x-form-invalid', /** * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided - * (defaults to "The value in this field is invalid") + * (defaults to 'The value in this field is invalid') + */ + invalidText : 'The value in this field is invalid', + /** + * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to 'x-form-focus') */ - invalidText : "The value in this field is invalid", + focusClass : 'x-form-focus', /** - * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus") + * @cfg {Boolean} preventMark + * true to disable {@link #markInvalid marking the field invalid}. + * Defaults to false. */ - focusClass : "x-form-focus", /** * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable - automatic validation (defaults to "keyup"). + automatic validation (defaults to 'keyup'). */ - validationEvent : "keyup", + validationEvent : 'keyup', /** * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true). */ @@ -67,25 +78,24 @@ Ext.form.Field = Ext.extend(Ext.BoxComponent, { * @cfg {String/Object} autoCreate

A {@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:

- *
{tag: "input", type: "text", size: "20", autocomplete: "off"}
+ *
{tag: 'input', type: 'text', size: '20', autocomplete: 'off'}
*/ - defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"}, + defaultAutoCreate : {tag: 'input', type: 'text', size: '20', autocomplete: 'off'}, /** - * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field") + * @cfg {String} fieldClass The default CSS class for the field (defaults to 'x-form-field') */ - fieldClass : "x-form-field", + fieldClass : 'x-form-field', /** - * @cfg {String} msgTarget The location where error text should display. Should be one of the following values - * (defaults to 'qtip'): - *
-Value         Description
------------   ----------------------------------------------------------------------
-qtip          Display a quick tip when the user hovers over the field
-title         Display a default browser title attribute popup
-under         Add a block div beneath the field containing the error text
-side          Add an error icon to the right of the field with a popup on hover
-[element id]  Add the error text directly to the innerHTML of the specified element
-
+ * @cfg {String} msgTarget

The location where the message text set through {@link #markInvalid} should display. + * Must be one of the following values:

+ *
*/ msgTarget : 'qtip', /** @@ -109,10 +119,18 @@ side Add an error icon to the right of the field with a popup on hover * disabled Fields will not be {@link Ext.form.BasicForm#submit submitted}.

*/ disabled : false, + /** + * @cfg {Boolean} submitValue False to clear the name attribute on the field so that it is not submitted during a form post. + * Defaults to true. + */ + submitValue: true, // private isFormField : true, + // private + msgDisplay: '', + // private hasFocus : false, @@ -193,9 +211,9 @@ var form = new Ext.form.FormPanel({ /** * Returns the {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} * attribute of the field if available. - * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} + * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} */ - getName: function(){ + getName : function(){ return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || ''; }, @@ -213,7 +231,9 @@ var form = new Ext.form.FormPanel({ this.autoEl = cfg; } Ext.form.Field.superclass.onRender.call(this, ct, position); - + if(this.submitValue === false){ + this.el.dom.removeAttribute('name'); + } var type = this.el.dom.type; if(type){ if(type == 'password'){ @@ -222,7 +242,7 @@ var form = new Ext.form.FormPanel({ this.el.addClass('x-form-'+type); } if(this.readOnly){ - this.el.dom.readOnly = true; + this.setReadOnly(true); } if(this.tabIndex !== undefined){ this.el.dom.setAttribute('tabIndex', this.tabIndex); @@ -233,7 +253,7 @@ var form = new Ext.form.FormPanel({ // private getItemCt : function(){ - return this.el.up('.x-form-item', 4); + return this.itemCt; }, // private @@ -270,6 +290,17 @@ var form = new Ext.form.FormPanel({ return String(this.getValue()) !== String(this.originalValue); }, + /** + * Sets the read only state of this field. + * @param {Boolean} readOnly Whether the field should be read only. + */ + setReadOnly : function(readOnly){ + if(this.rendered){ + this.el.dom.readOnly = readOnly; + } + this.readOnly = readOnly; + }, + // private afterRender : function(){ Ext.form.Field.superclass.afterRender.call(this); @@ -280,7 +311,7 @@ var form = new Ext.form.FormPanel({ // private fireKey : function(e){ if(e.isSpecialKey()){ - this.fireEvent("specialkey", this, e); + this.fireEvent('specialkey', this, e); } }, @@ -295,23 +326,34 @@ var form = new Ext.form.FormPanel({ // private initEvents : function(){ - this.mon(this.el, Ext.EventManager.useKeydown ? "keydown" : "keypress", this.fireKey, this); + this.mon(this.el, Ext.EventManager.getKeyEvent(), this.fireKey, this); this.mon(this.el, 'focus', this.onFocus, this); - // fix weird FF/Win editor issue when changing OS window focus - var o = this.inEditor && Ext.isWindows && Ext.isGecko ? {buffer:10} : null; - this.mon(this.el, 'blur', this.onBlur, this, o); + // standardise buffer across all browsers + OS-es for consistent event order. + // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus) + this.mon(this.el, 'blur', this.onBlur, this, this.inEditor ? {buffer:10} : null); }, + // private + preFocus: Ext.emptyFn, + // private onFocus : function(){ + this.preFocus(); if(this.focusClass){ this.el.addClass(this.focusClass); } if(!this.hasFocus){ this.hasFocus = true; + /** + *

The value that the Field had at the time it was last focused. This is the value that is passed + * to the {@link #change} event which is fired if the value has been changed when the Field is blurred.

+ *

This will be undefined until the Field has been visited. Compare {@link #originalValue}.

+ * @type mixed + * @property startValue + */ this.startValue = this.getValue(); - this.fireEvent("focus", this); + this.fireEvent('focus', this); } }, @@ -325,18 +367,24 @@ var form = new Ext.form.FormPanel({ this.el.removeClass(this.focusClass); } this.hasFocus = false; - if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){ + if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent == 'blur')){ this.validate(); } var v = this.getValue(); if(String(v) !== String(this.startValue)){ this.fireEvent('change', this, v, this.startValue); } - this.fireEvent("blur", this); + this.fireEvent('blur', this); + this.postBlur(); }, + // private + postBlur : Ext.emptyFn, + /** - * Returns whether or not the field value is currently valid + * Returns whether or not the field value is currently valid by + * {@link #validateValue validating} the {@link #processValue processed value} + * of the field. Note: {@link #disabled} fields are ignored. * @param {Boolean} preventMark True to disable marking the field invalid * @return {Boolean} True if the value is valid, else false */ @@ -363,62 +411,128 @@ var form = new Ext.form.FormPanel({ return false; }, - // protected - should be overridden by subclasses if necessary to prepare raw values for validation + /** + * This method should only be overridden if necessary to prepare raw values + * for validation (see {@link #validate} and {@link #isValid}). This method + * is expected to return the processed value for the field which will + * be used for validation (see validateValue method). + * @param {Mixed} value + */ processValue : function(value){ return value; }, - // private - // Subclasses should provide the validation implementation by overriding this - validateValue : function(value){ - return true; + /** + * Uses getErrors to build an array of validation errors. If any errors are found, markInvalid is called + * with the first and false is returned, otherwise true is returned. Previously, subclasses were invited + * to provide an implementation of this to process validations - from 3.2 onwards getErrors should be + * overridden instead. + * @param {Mixed} The current value of the field + * @return {Boolean} True if all validations passed, false if one or more failed + */ + validateValue : function(value) { + //currently, we only show 1 error at a time for a field, so just use the first one + var error = this.getErrors(value)[0]; + + if (error == undefined) { + return true; + } else { + this.markInvalid(error); + return false; + } + }, + + /** + * Runs this field's validators and returns an array of error messages for any validation failures. + * This is called internally during validation and would not usually need to be used manually. + * Each subclass should override or augment the return value to provide their own errors + * @return {Array} All error messages for this field + */ + getErrors: function() { + return []; }, /** - * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and - * applying {@link #invalidClass} to the field's element. + * Gets the active error message for this field. + * @return {String} Returns the active error message on the field, if there is no error, an empty string is returned. + */ + getActiveError : function(){ + return this.activeError || ''; + }, + + /** + *

Display an error message associated with this field, using {@link #msgTarget} to determine how to + * display the message and applying {@link #invalidClass} to the field's UI element.

+ *

Note: this method does not cause the Field's {@link #validate} method to return false + * if the value does pass validation. So simply marking a Field as invalid will not prevent + * submission of forms submitted with the {@link Ext.form.Action.Submit#clientValidation} option set.

+ * {@link #isValid invalid}. * @param {String} msg (optional) The validation message (defaults to {@link #invalidText}) */ markInvalid : function(msg){ - if(!this.rendered || this.preventMark){ // not rendered - return; - } - msg = msg || this.invalidText; - - var mt = this.getMessageHandler(); - if(mt){ - mt.mark(this, msg); - }else if(this.msgTarget){ - this.el.addClass(this.invalidClass); - var t = Ext.getDom(this.msgTarget); - if(t){ - t.innerHTML = msg; - t.style.display = this.msgDisplay; + //don't set the error icon if we're not rendered or marking is prevented + if (this.rendered && !this.preventMark) { + msg = msg || this.invalidText; + + var mt = this.getMessageHandler(); + if(mt){ + mt.mark(this, msg); + }else if(this.msgTarget){ + this.el.addClass(this.invalidClass); + var t = Ext.getDom(this.msgTarget); + if(t){ + t.innerHTML = msg; + t.style.display = this.msgDisplay; + } } } - this.fireEvent('invalid', this, msg); + + this.setActiveError(msg); }, - + /** * Clear any invalid styles/messages for this field */ clearInvalid : function(){ - if(!this.rendered || this.preventMark){ // not rendered - return; - } - this.el.removeClass(this.invalidClass); - var mt = this.getMessageHandler(); - if(mt){ - mt.clear(this); - }else if(this.msgTarget){ + //don't remove the error icon if we're not rendered or marking is prevented + if (this.rendered && !this.preventMark) { this.el.removeClass(this.invalidClass); - var t = Ext.getDom(this.msgTarget); - if(t){ - t.innerHTML = ''; - t.style.display = 'none'; + var mt = this.getMessageHandler(); + if(mt){ + mt.clear(this); + }else if(this.msgTarget){ + this.el.removeClass(this.invalidClass); + var t = Ext.getDom(this.msgTarget); + if(t){ + t.innerHTML = ''; + t.style.display = 'none'; + } } } - this.fireEvent('valid', this); + + this.unsetActiveError(); + }, + + /** + * Sets the current activeError to the given string. Fires the 'invalid' event. + * This does not set up the error icon, only sets the message and fires the event. To show the error icon, + * use markInvalid instead, which calls this method internally + * @param {String} msg The error message + * @param {Boolean} suppressEvent True to suppress the 'invalid' event from being fired + */ + setActiveError: function(msg, suppressEvent) { + this.activeError = msg; + if (suppressEvent !== true) this.fireEvent('invalid', this, msg); + }, + + /** + * Clears the activeError and fires the 'valid' event. This is called internally by clearInvalid and would not + * usually need to be called manually + * @param {Boolean} suppressEvent True to suppress the 'invalid' event from being fired + */ + unsetActiveError: function(suppressEvent) { + delete this.activeError; + if (suppressEvent !== true) this.fireEvent('valid', this); }, // private @@ -432,7 +546,12 @@ var form = new Ext.form.FormPanel({ this.el.findParent('.x-form-field-wrap', 5, true); // else direct field wrap }, - // private + // Alignment for 'under' target + alignErrorEl : function(){ + this.errorEl.setWidth(this.getErrorCt().getWidth(true) - 20); + }, + + // Alignment for 'side' target alignErrorIcon : function(){ this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]); }, @@ -470,7 +589,7 @@ var form = new Ext.form.FormPanel({ * @return {Mixed} value The field value that is set */ setRawValue : function(v){ - return (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)); + return this.rendered ? (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)) : ''; }, /** @@ -490,26 +609,6 @@ var form = new Ext.form.FormPanel({ // private, does not work for all fields append : function(v){ this.setValue([this.getValue(), v].join('')); - }, - - // private - adjustSize : function(w, h){ - var s = Ext.form.Field.superclass.adjustSize.call(this, w, h); - s.width = this.adjustWidth(this.el.dom.tagName, s.width); - if(this.offsetCt){ - var ct = this.getItemCt(); - s.width -= ct.getFrameWidth('lr'); - s.height -= ct.getFrameWidth('tb'); - } - return s; - }, - - // private - adjustWidth : function(tag, w){ - if(typeof w == 'number' && (Ext.isIE && (Ext.isIE6 || !Ext.isStrict)) && /input|textarea/i.test(tag) && !this.inEditor){ - return w - 3; - } - return w; } /** @@ -559,8 +658,12 @@ Ext.form.MessageTargets = { return; } field.errorEl = elp.createChild({cls:'x-form-invalid-msg'}); - field.errorEl.setWidth(elp.getWidth(true)-20); + field.on('resize', field.alignErrorEl, field); + field.on('destroy', function(){ + Ext.destroy(this.errorEl); + }, field); } + field.alignErrorEl(); field.errorEl.update(msg); Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field); }, @@ -578,24 +681,31 @@ Ext.form.MessageTargets = { field.el.addClass(field.invalidClass); if(!field.errorIcon){ var elp = field.getErrorCt(); - if(!elp){ // field has no container el + // field has no container el + if(!elp){ field.el.dom.title = msg; return; } field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'}); + if (field.ownerCt) { + field.ownerCt.on('afterlayout', field.alignErrorIcon, field); + field.ownerCt.on('expand', field.alignErrorIcon, field); + } + field.on('resize', field.alignErrorIcon, field); + field.on('destroy', function(){ + Ext.destroy(this.errorIcon); + }, field); } field.alignErrorIcon(); field.errorIcon.dom.qtip = msg; field.errorIcon.dom.qclass = 'x-form-invalid-tip'; field.errorIcon.show(); - field.on('resize', field.alignErrorIcon, field); }, clear: function(field){ field.el.removeClass(field.invalidClass); if(field.errorIcon){ field.errorIcon.dom.qtip = ''; field.errorIcon.hide(); - field.un('resize', field.alignErrorIcon, field); }else{ field.el.dom.title = ''; } @@ -645,44 +755,10 @@ Ext.reg('field', Ext.form.Field); * or as the base class for more sophisticated input controls (like {@link Ext.form.TextArea} * and {@link Ext.form.ComboBox}).

*

Validation

- *

Field validation is processed in a particular order. If validation fails at any particular - * step the validation routine halts.

+ *

The validation procedure is described in the documentation for {@link #validateValue}.

+ *

Alter Validation Behavior

+ *

Validation behavior for each field can be configured:

*
- * @constructor - * Creates a new TextField + * + * @constructor Creates a new TextField * @param {Object} config Configuration options + * * @xtype textfield */ Ext.form.TextField = Ext.extend(Ext.form.Field, { @@ -785,11 +860,22 @@ var myField = new Ext.form.NumberField({ */ blankText : 'This field is required', /** - * @cfg {Function} validator A custom validation function to be called during field validation + * @cfg {Function} validator + *

A custom validation function to be called during field validation ({@link #validateValue}) * (defaults to null). If specified, this function will be called first, allowing the - * developer to override the default validation process. This function will be passed the current - * field value and expected to return boolean true if the value is valid or a string - * error message if invalid. + * developer to override the default validation process.

+ *

This function will be passed the following Parameters:

+ *
+ *

This function is to Return:

+ *
*/ validator : null, /** @@ -868,22 +954,13 @@ var myField = new Ext.form.NumberField({ this.validationTask = new Ext.util.DelayedTask(this.validate, this); this.mon(this.el, 'keyup', this.filterValidation, this); } - else if(this.validationEvent !== false){ + else if(this.validationEvent !== false && this.validationEvent != 'blur'){ this.mon(this.el, this.validationEvent, this.validate, this, {buffer: this.validationDelay}); } - if(this.selectOnFocus || this.emptyText){ - this.on('focus', this.preFocus, this); - - this.mon(this.el, 'mousedown', function(){ - if(!this.hasFocus){ - this.el.on('mouseup', function(e){ - e.preventDefault(); - }, this, {single:true}); - } - }, this); + if(this.selectOnFocus || this.emptyText){ + this.mon(this.el, 'mousedown', this.onMouseDown, this); if(this.emptyText){ - this.on('blur', this.postBlur, this); this.applyEmptyText(); } } @@ -895,9 +972,18 @@ var myField = new Ext.form.NumberField({ this.mon(this.el, 'click', this.autoSize, this); } if(this.enableKeyEvents){ - this.mon(this.el, 'keyup', this.onKeyUp, this); - this.mon(this.el, 'keydown', this.onKeyDown, this); - this.mon(this.el, 'keypress', this.onKeyPress, this); + this.mon(this.el, { + scope: this, + keyup: this.onKeyUp, + keydown: this.onKeyDown, + keypress: this.onKeyPress + }); + } + }, + + onMouseDown: function(e){ + if(!this.hasFocus){ + this.mon(this.el, 'mouseup', Ext.emptyFn, this, { single: true, preventDefault: true }); } }, @@ -936,10 +1022,15 @@ var myField = new Ext.form.NumberField({ // private onKeyUpBuffered : function(e){ - if(!e.isNavKeyPress()){ + if(this.doAutoSize(e)){ this.autoSize(); } }, + + // private + doAutoSize : function(e){ + return !e.isNavKeyPress(); + }, // private onKeyUp : function(e){ @@ -975,17 +1066,17 @@ var myField = new Ext.form.NumberField({ // private preFocus : function(){ - var el = this.el; + var el = this.el, + isEmpty; if(this.emptyText){ if(el.dom.value == this.emptyText){ this.setRawValue(''); + isEmpty = true; } el.removeClass(this.emptyClass); } - if(this.selectOnFocus){ - (function(){ - el.dom.select(); - }).defer(this.inEditor && Ext.isIE ? 50 : 0); + if(this.selectOnFocus || isEmpty){ + el.dom.select(); } }, @@ -996,12 +1087,18 @@ var myField = new Ext.form.NumberField({ // private filterKeys : function(e){ - // special keys don't generate charCodes, so leave them alone - if(e.ctrlKey || e.isSpecialKey()){ + if(e.ctrlKey){ return; } - - if(!this.maskRe.test(String.fromCharCode(e.getCharCode()))){ + var k = e.getKey(); + if(Ext.isGecko && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){ + return; + } + var cc = String.fromCharCode(e.getCharCode()); + if(!Ext.isGecko && e.isSpecialKey() && !cc){ + return; + } + if(!this.maskRe.test(cc)){ e.stopEvent(); } }, @@ -1017,48 +1114,118 @@ var myField = new Ext.form.NumberField({ }, /** - * Validates a value according to the field's validation rules and marks the field as invalid - * if the validation fails - * @param {Mixed} value The value to validate - * @return {Boolean} True if the value is valid, else false - */ - validateValue : function(value){ - if(Ext.isFunction(this.validator)){ + *

Validates a value according to the field's validation rules and returns an array of errors + * for any failing validations. Validation rules are processed in the following order:

+ *
*

See also {@link #mode}.

*/ @@ -2281,8 +2646,9 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ /** * @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). + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field1' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). *

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 @@ -2290,8 +2656,9 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ /** * @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}). + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field2' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). *

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}.

*/ @@ -2299,16 +2666,11 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { * @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. + * to give the hidden field a unique id. 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 @@ -2342,8 +2704,10 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ 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?') + * @cfg {String/Array} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details + * on supported anchor positions and offsets. To specify x/y offsets as well, this value + * may be specified as an Array of {@link Ext.Element#alignTo} method arguments.

+ *
[ 'tl-bl?', [6,0] ]
(defaults to 'tl-bl?') */ listAlign : 'tl-bl?', /** @@ -2374,6 +2738,12 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { * {@link Ext.form.TriggerField#editable editable} = false). */ minChars : 4, + /** + * @cfg {Boolean} autoSelect true to select the first result gathered by the data store (defaults + * to true). A false value would require a manual selection from the dropdown list to set the components value + * unless the value of ({@link #typeAheadDelay}) were true. + */ + autoSelect : true, /** * @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 @@ -2482,6 +2852,19 @@ var combo = new Ext.form.ComboBox({ */ lazyInit : true, + /** + * @cfg {Boolean} clearFilterOnReset true to clear any filters on the store (when in local mode) when reset is called + * (defaults to true) + */ + clearFilterOnReset : true, + + /** + * @cfg {Boolean} submitValue False to clear the name attribute on the field so that it is not submitted during a form post. + * If a hiddenName is specified, setting this to true will cause both the hidden field and the element to be submitted. + * Defaults to undefined. + */ + submitValue: undefined, + /** * The value of the match string used to filter the store. Delete this property to force a requery. * Example use: @@ -2529,6 +2912,7 @@ var combo = new Ext.form.ComboBox({ * @param {Ext.form.ComboBox} combo This combo box */ 'collapse', + /** * @event beforeselect * Fires before a list item is selected. Return false to cancel the selection. @@ -2575,7 +2959,7 @@ var combo = new Ext.form.ComboBox({ d.push([value, o.text]); } this.store = new Ext.data.ArrayStore({ - 'id': 0, + idIndex: 0, fields: ['value', 'text'], data : d, autoDestroy: true @@ -2588,10 +2972,8 @@ var combo = new Ext.form.ComboBox({ this.target = true; this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate); this.render(this.el.parentNode, s); - Ext.removeNode(s); // remove it - }else{ - Ext.removeNode(s); // remove it } + Ext.removeNode(s); } //auto-configure store from local array data else if(this.store){ @@ -2618,13 +3000,14 @@ var combo = new Ext.form.ComboBox({ // private onRender : function(ct, position){ + if(this.hiddenName && !Ext.isDefined(this.submitValue)){ + this.submitValue = false; + } Ext.form.ComboBox.superclass.onRender.call(this, ct, position); if(this.hiddenName){ this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, - id: (this.hiddenId||this.hiddenName)}, 'before', true); + id: (this.hiddenId || Ext.id())}, 'before', true); - // prevent input submission - this.el.dom.removeAttribute('name'); } if(Ext.isGecko){ this.el.dom.setAttribute('autocomplete', 'off'); @@ -2642,21 +3025,42 @@ var combo = new Ext.form.ComboBox({ Ext.form.ComboBox.superclass.initValue.call(this); if(this.hiddenField){ this.hiddenField.value = - Ext.isDefined(this.hiddenValue) ? this.hiddenValue : - Ext.isDefined(this.value) ? this.value : ''; + Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, ''); + } + }, + + getParentZIndex : function(){ + var zindex; + if (this.ownerCt){ + this.findParentBy(function(ct){ + zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10); + return !!zindex; + }); + } + return zindex; + }, + + getZIndex : function(listParent){ + listParent = listParent || Ext.getDom(this.getListParent() || Ext.getBody()); + var zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10); + if(!zindex){ + zindex = this.getParentZIndex(); } + return (zindex || 12000) + 5; }, // private initList : function(){ if(!this.list){ - var cls = 'x-combo-list'; + var cls = 'x-combo-list', + listParent = Ext.getDom(this.getListParent() || Ext.getBody()); this.list = new Ext.Layer({ - parentEl: this.getListParent(), + parentEl: listParent, shadow: this.shadow, cls: [cls, this.listClass].join(' '), - constrain:false + constrain:false, + zindex: this.getZIndex(listParent) }); var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth); @@ -2727,10 +3131,15 @@ var combo = new Ext.form.ComboBox({ singleSelect: true, selectedClass: this.selectedClass, itemSelector: this.itemSelector || '.' + cls + '-item', - emptyText: this.listEmptyText + emptyText: this.listEmptyText, + deferEmptyText: false }); - this.mon(this.view, 'click', this.onViewClick, this); + this.mon(this.view, { + containerclick : this.onViewClick, + click : this.onViewClick, + scope :this + }); this.bindStore(this.store, true); @@ -2803,17 +3212,21 @@ var menu = new Ext.menu.Menu({ // 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(); + }else{ + this.store.un('beforeload', this.onBeforeLoad, this); + this.store.un('load', this.onLoad, this); + this.store.un('exception', this.collapse, this); } if(!store){ this.store = null; if(this.view){ this.view.bindStore(null); } + if(this.pageTb){ + this.pageTb.bindStore(null); + } } } if(store){ @@ -2838,10 +3251,34 @@ var menu = new Ext.menu.Menu({ } }, + reset : function(){ + if(this.clearFilterOnReset && this.mode == 'local'){ + this.store.clearFilter(); + } + Ext.form.ComboBox.superclass.reset.call(this); + }, + // private initEvents : function(){ Ext.form.ComboBox.superclass.initEvents.call(this); + /** + * @property keyNav + * @type Ext.KeyNav + *

A {@link Ext.KeyNav KeyNav} object which handles navigation keys for this ComboBox. This performs actions + * based on keystrokes typed when the input field is focused.

+ *

After the ComboBox has been rendered, you may override existing navigation key functionality, + * or add your own based upon key names as specified in the {@link Ext.KeyNav KeyNav} class.

+ *

The function is executed in the scope (this reference of the ComboBox. Example:


+myCombo.keyNav.esc = function(e) {  // Override ESC handling function
+    this.collapse();                // Standard behaviour of Ext's ComboBox.
+    this.setValue(this.startValue); // We reset to starting value on ESC
+};
+myCombo.keyNav.tab = function() {   // Override TAB handling function
+    this.onViewClick(false);        // Select the currently highlighted row
+};
+
+ */ this.keyNav = new Ext.KeyNav(this.el, { "up" : function(e){ this.inKeyMode = true; @@ -2859,8 +3296,6 @@ var menu = new Ext.menu.Menu({ "enter" : function(e){ this.onViewClick(); - this.delayedCheck = true; - this.unsetDelayCheck.defer(10, this); }, "esc" : function(e){ @@ -2868,20 +3303,31 @@ var menu = new Ext.menu.Menu({ }, "tab" : function(e){ - this.onViewClick(false); + if (this.forceSelection === true) { + this.collapse(); + } else { + this.onViewClick(false); + } return true; }, scope : this, - doRelay : function(foo, bar, hname){ + doRelay : function(e, h, hname){ if(hname == 'down' || this.scope.isExpanded()){ - return Ext.KeyNav.prototype.doRelay.apply(this, arguments); + // this MUST be called before ComboBox#fireKey() + var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments); + if(!Ext.isIE && Ext.EventManager.useKeydown){ + // call Combo#fireKey() for browsers which use keydown event (except IE) + this.scope.fireKey(e); + } + return relay; } return true; }, - forceKeyDown : true + forceKeyDown : true, + defaultEventAction: 'stopEvent' }); this.queryDelay = Math.max(this.queryDelay || 10, this.mode == 'local' ? 10 : 250); @@ -2889,11 +3335,12 @@ var menu = new Ext.menu.Menu({ if(this.typeAhead){ this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this); } - if(this.editable !== false && !this.enableKeyEvents){ + if(!this.enableKeyEvents){ this.mon(this.el, 'keyup', this.onKeyUp, this); } }, + // private onDestroy : function(){ if (this.dqTask){ @@ -2907,34 +3354,29 @@ var menu = new Ext.menu.Menu({ this.pageTb, this.list ); + Ext.destroyMembers(this, 'hiddenField'); 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); + if (!this.isExpanded()) { + Ext.form.ComboBox.superclass.fireKey.call(this, e); } }, // private onResize : function(w, h){ Ext.form.ComboBox.superclass.onResize.apply(this, arguments); - if(this.list && !Ext.isDefined(this.listWidth)){ + if(!isNaN(w) && this.isVisible() && this.list){ + this.doResize(w); + }else{ + this.bufferSize = w; + } + }, + + doResize: function(w){ + if(!Ext.isDefined(this.listWidth)){ var lw = Math.max(w, this.minListWidth); this.list.setWidth(lw); this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); @@ -2973,26 +3415,29 @@ var menu = new Ext.menu.Menu({ if(!this.hasFocus){ return; } - if(this.store.getCount() > 0){ + if(this.store.getCount() > 0 || this.listEmptyText){ this.expand(); this.restrictHeight(); if(this.lastQuery == this.allQuery){ if(this.editable){ this.el.dom.select(); } - if(!this.selectByValue(this.value, true)){ + + if(this.autoSelect !== false && !this.selectByValue(this.value, true)){ this.select(0, true); } }else{ - this.selectNext(); + if(this.autoSelect !== false){ + this.selectNext(); + } if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){ this.taTask.delay(this.typeAheadDelay); } } }else{ - this.onEmptyResults(); + this.collapse(); } - //this.el.focus(); + }, // private @@ -3009,6 +3454,38 @@ var menu = new Ext.menu.Menu({ } }, + // private + assertValue : function(){ + var val = this.getRawValue(), + rec; + + if(this.valueField && Ext.isDefined(this.value)){ + rec = this.findRecord(this.valueField, this.value); + } + if(!rec || rec.get(this.displayField) != val){ + rec = this.findRecord(this.displayField, val); + } + if(!rec && this.forceSelection){ + if(val.length > 0 && val != this.emptyText){ + this.el.dom.value = Ext.value(this.lastSelectionText, ''); + this.applyEmptyText(); + }else{ + this.clearValue(); + } + }else{ + if(rec && this.valueField){ + // onSelect may have already set the value and by doing so + // set the display field properly. Let's not wipe out the + // valueField here by just sending the displayField. + if (this.value == val){ + return; + } + val = rec.get(this.valueField || this.displayField); + } + this.setValue(val); + } + }, + // private onSelect : function(record, index){ if(this.fireEvent('beforeselect', this, record, index) !== false){ @@ -3069,7 +3546,7 @@ var menu = new Ext.menu.Menu({ } this.lastSelectionText = text; if(this.hiddenField){ - this.hiddenField.value = v; + this.hiddenField.value = Ext.value(v, ''); } Ext.form.ComboBox.superclass.setValue.call(this, text); this.value = v; @@ -3109,39 +3586,39 @@ var menu = new Ext.menu.Menu({ // private onViewClick : function(doFocus){ - var index = this.view.getSelectedIndexes()[0]; - var r = this.store.getAt(index); + var index = this.view.getSelectedIndexes()[0], + s = this.store, + r = s.getAt(index); if(r){ this.onSelect(r, index); + }else { + this.collapse(); } if(doFocus !== false){ this.el.focus(); } }, + // private restrictHeight : function(){ this.innerList.dom.style.height = ''; - var inner = this.innerList.dom; - var pad = this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight; - var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight); - var ha = this.getPosition()[1]-Ext.getBody().getScroll().top; - var hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height; - var space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5; + var inner = this.innerList.dom, + pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight, + h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight), + ha = this.getPosition()[1]-Ext.getBody().getScroll().top, + hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height, + space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5; + h = Math.min(h, space, this.maxHeight); this.innerList.setHeight(h); this.list.beginUpdate(); this.list.setHeight(h+pad); - this.list.alignTo(this.wrap, this.listAlign); + this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign)); this.list.endUpdate(); }, - // private - onEmptyResults : function(){ - this.collapse(); - }, - /** * Returns true if the dropdown list is expanded, else false. */ @@ -3184,6 +3661,7 @@ var menu = new Ext.menu.Menu({ this.innerList.scrollChildIntoView(el, false); } } + }, // private @@ -3213,7 +3691,8 @@ var menu = new Ext.menu.Menu({ // private onKeyUp : function(e){ var k = e.getKey(); - if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){ + if(this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())){ + this.lastKey = k; this.dqTask.delay(this.queryDelay); } @@ -3232,21 +3711,14 @@ var menu = new Ext.menu.Menu({ // private beforeBlur : function(){ - var val = this.getRawValue(); - if(this.forceSelection){ - if(val.length > 0 && val != this.emptyText){ - this.el.dom.value = Ext.isDefined(this.lastSelectionText) ? this.lastSelectionText : ''; - this.applyEmptyText(); - }else{ - this.clearValue(); - } - }else{ - var rec = this.findRecord(this.displayField, val); - if(rec){ - val = rec.get(this.valueField || this.displayField); - } - this.setValue(val); - } + this.assertValue(); + }, + + // private + postBlur : function(){ + Ext.form.ComboBox.superclass.postBlur.call(this); + this.collapse(); + this.inKeyMode = false; }, /** @@ -3297,13 +3769,13 @@ var menu = new Ext.menu.Menu({ // private getParams : function(q){ - var p = {}; - //p[this.queryParam] = q; + var params = {}, + paramNames = this.store.paramNames; if(this.pageSize){ - p.start = 0; - p.limit = this.pageSize; + params[paramNames.start] = 0; + params[paramNames.limit] = this.pageSize; } - return p; + return params; }, /** @@ -3321,7 +3793,7 @@ var menu = new Ext.menu.Menu({ // private collapseIf : function(e){ - if(!e.within(this.wrap) && !e.within(this.list)){ + if(!this.isDestroyed && !e.within(this.wrap) && !e.within(this.list)){ this.collapse(); } }, @@ -3333,12 +3805,30 @@ var menu = new Ext.menu.Menu({ if(this.isExpanded() || !this.hasFocus){ return; } - this.list.alignTo(this.wrap, this.listAlign); + + if(this.title || this.pageSize){ + this.assetHeight = 0; + if(this.title){ + this.assetHeight += this.header.getHeight(); + } + if(this.pageSize){ + this.assetHeight += this.footer.getHeight(); + } + } + + if(this.bufferSize){ + this.doResize(this.bufferSize); + delete this.bufferSize; + } + this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign)); + + // zindex can change, re-check it and set it if necessary + this.list.setZIndex(this.getZIndex()); this.list.show(); if(Ext.isGecko2){ this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac } - Ext.getDoc().on({ + this.mon(Ext.getDoc(), { scope: this, mousewheel: this.collapseIf, mousedown: this.collapseIf @@ -3353,7 +3843,7 @@ var menu = new Ext.menu.Menu({ // private // Implements the default empty TriggerField.onTriggerClick function onTriggerClick : function(){ - if(this.disabled){ + if(this.readOnly || this.disabled){ return; } if(this.isExpanded()){ @@ -3385,7 +3875,8 @@ var menu = new Ext.menu.Menu({ */ }); -Ext.reg('combo', Ext.form.ComboBox);/** +Ext.reg('combo', Ext.form.ComboBox); +/** * @class Ext.form.Checkbox * @extends Ext.form.Field * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. @@ -3407,19 +3898,20 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, { * @cfg {Boolean} checked true if the checkbox should render initially checked (defaults to false) */ checked : false, + /** + * @cfg {String} boxLabel The text that appears beside the checkbox + */ + boxLabel: ' ', /** * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to * {tag: 'input', type: 'checkbox', autocomplete: 'off'}) */ defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'}, - /** - * @cfg {String} boxLabel The text that appears beside the checkbox - */ /** * @cfg {String} inputValue The value that should go into the generated input element's value attribute */ /** - * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of + * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of * handling the check event). The handler is passed the following parameters: *