X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/pkgs/pkg-forms-debug.js diff --git a/pkgs/pkg-forms-debug.js b/pkgs/pkg-forms-debug.js new file mode 100644 index 00000000..e3776075 --- /dev/null +++ b/pkgs/pkg-forms-debug.js @@ -0,0 +1,7661 @@ +/*! + * Ext JS Library 3.0.0 + * Copyright(c) 2006-2009 Ext JS, LLC + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.form.Field + * @extends Ext.BoxComponent + * Base class for form fields that provides default event handling, sizing, value handling and other functionality. + * @constructor + * Creates a new Field + * @param {Object} config Configuration options + * @xtype field + */ +Ext.form.Field = Ext.extend(Ext.BoxComponent, { + /** + * @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 + * no separate Ext components for those. Note that if you use inputType:'file', {@link #emptyText} + * is not supported and should be avoided. + */ + /** + * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered, + * not those which are built via applyTo (defaults to undefined). + */ + /** + * @cfg {Mixed} value A value to initialize this field with (defaults to undefined). + */ + /** + * @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} invalidClass The CSS class to use when marking a field invalid (defaults to "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") + */ + 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") + */ + 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"). + */ + validationEvent : "keyup", + /** + * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true). + */ + validateOnBlur : true, + /** + * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation + * is initiated (defaults to 250) + */ + validationDelay : 250, + /** + * @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"}
+ */ + defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"}, + /** + * @cfg {String} fieldClass The default CSS class for the field (defaults to "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
+
+ */ + msgTarget : 'qtip', + /** + * @cfg {String} msgFx Experimental The effect used when displaying a validation message under the field + * (defaults to 'normal'). + */ + msgFx : 'normal', + /** + * @cfg {Boolean} readOnly true to mark the field as readOnly in HTML + * (defaults to false). + *

Note: this only sets the element's readOnly DOM attribute. + * Setting readOnly=true, for example, will not disable triggering a + * ComboBox or DateField; it gives you the option of forcing the user to choose + * via the trigger without typing in the text box. To hide the trigger use + * {@link Ext.form.TriggerField#hideTrigger hideTrigger}.

+ */ + readOnly : false, + /** + * @cfg {Boolean} disabled True to disable the field (defaults to false). + *

Be aware that conformant with the HTML specification, + * disabled Fields will not be {@link Ext.form.BasicForm#submit submitted}.

+ */ + disabled : false, + + // private + isFormField : true, + + // private + hasFocus : false, + + // private + initComponent : function(){ + Ext.form.Field.superclass.initComponent.call(this); + this.addEvents( + /** + * @event focus + * Fires when this field receives input focus. + * @param {Ext.form.Field} this + */ + 'focus', + /** + * @event blur + * Fires when this field loses input focus. + * @param {Ext.form.Field} this + */ + 'blur', + /** + * @event specialkey + * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. + * To handle other keys see {@link Ext.Panel#keys} or {@link Ext.KeyMap}. + * You can check {@link Ext.EventObject#getKey} to determine which key was pressed. + * For example:

+var form = new Ext.form.FormPanel({
+    ...
+    items: [{
+            fieldLabel: 'Field 1',
+            name: 'field1',
+            allowBlank: false
+        },{
+            fieldLabel: 'Field 2',
+            name: 'field2',
+            listeners: {
+                specialkey: function(field, e){
+                    // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
+                    // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
+                    if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
+                        var form = field.ownerCt.getForm();
+                        form.submit();
+                    }
+                }
+            }
+        }
+    ],
+    ...
+});
+             * 
+ * @param {Ext.form.Field} this + * @param {Ext.EventObject} e The event object + */ + 'specialkey', + /** + * @event change + * Fires just before the field blurs if the field value has changed. + * @param {Ext.form.Field} this + * @param {Mixed} newValue The new value + * @param {Mixed} oldValue The original value + */ + 'change', + /** + * @event invalid + * Fires after the field has been marked as invalid. + * @param {Ext.form.Field} this + * @param {String} msg The validation message + */ + 'invalid', + /** + * @event valid + * Fires after the field has been validated with no errors. + * @param {Ext.form.Field} this + */ + 'valid' + ); + }, + + /** + * 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} + */ + getName: function(){ + return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || ''; + }, + + // private + onRender : function(ct, position){ + if(!this.el){ + var cfg = this.getAutoCreate(); + + if(!cfg.name){ + cfg.name = this.name || this.id; + } + if(this.inputType){ + cfg.type = this.inputType; + } + this.autoEl = cfg; + } + Ext.form.Field.superclass.onRender.call(this, ct, position); + + var type = this.el.dom.type; + if(type){ + if(type == 'password'){ + type = 'text'; + } + this.el.addClass('x-form-'+type); + } + if(this.readOnly){ + this.el.dom.readOnly = true; + } + if(this.tabIndex !== undefined){ + this.el.dom.setAttribute('tabIndex', this.tabIndex); + } + + this.el.addClass([this.fieldClass, this.cls]); + }, + + // private + getItemCt : function(){ + return this.el.up('.x-form-item', 4); + }, + + // private + initValue : function(){ + if(this.value !== undefined){ + this.setValue(this.value); + }else if(!Ext.isEmpty(this.el.dom.value) && this.el.dom.value != this.emptyText){ + this.setValue(this.el.dom.value); + } + /** + * The original value of the field as configured in the {@link #value} configuration, or + * as loaded by the last form load operation if the form's {@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad} + * setting is true. + * @type mixed + * @property originalValue + */ + this.originalValue = this.getValue(); + }, + + /** + *

Returns true if the value of this Field has been changed from its original value. + * Will return false if the field is disabled or has not been rendered yet.

+ *

Note that if the owning {@link Ext.form.BasicForm form} was configured with + * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad} + * then the original value is updated when the values are loaded by + * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#setValues setValues}.

+ * @return {Boolean} True if this field has been changed from its original value (and + * is not disabled), false otherwise. + */ + isDirty : function() { + if(this.disabled || !this.rendered) { + return false; + } + return String(this.getValue()) !== String(this.originalValue); + }, + + // private + afterRender : function(){ + Ext.form.Field.superclass.afterRender.call(this); + this.initEvents(); + this.initValue(); + }, + + // private + fireKey : function(e){ + if(e.isSpecialKey()){ + this.fireEvent("specialkey", this, e); + } + }, + + /** + * Resets the current field value to the originally loaded value and clears any validation messages. + * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad} + */ + reset : function(){ + this.setValue(this.originalValue); + this.clearInvalid(); + }, + + // private + initEvents : function(){ + this.mon(this.el, Ext.EventManager.useKeydown ? "keydown" : "keypress", 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); + }, + + // private + onFocus : function(){ + if(this.focusClass){ + this.el.addClass(this.focusClass); + } + if(!this.hasFocus){ + this.hasFocus = true; + this.startValue = this.getValue(); + this.fireEvent("focus", this); + } + }, + + // private + beforeBlur : Ext.emptyFn, + + // private + onBlur : function(){ + this.beforeBlur(); + if(this.focusClass){ + this.el.removeClass(this.focusClass); + } + this.hasFocus = false; + 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); + }, + + /** + * Returns whether or not the field value is currently valid + * @param {Boolean} preventMark True to disable marking the field invalid + * @return {Boolean} True if the value is valid, else false + */ + isValid : function(preventMark){ + if(this.disabled){ + return true; + } + var restore = this.preventMark; + this.preventMark = preventMark === true; + var v = this.validateValue(this.processValue(this.getRawValue())); + this.preventMark = restore; + return v; + }, + + /** + * Validates the field value + * @return {Boolean} True if the value is valid, else false + */ + validate : function(){ + if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){ + this.clearInvalid(); + return true; + } + return false; + }, + + // protected - should be overridden by subclasses if necessary to prepare raw values for validation + processValue : function(value){ + return value; + }, + + // private + // Subclasses should provide the validation implementation by overriding this + validateValue : function(value){ + return true; + }, + + /** + * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and + * applying {@link #invalidClass} to the field's element. + * @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; + } + } + this.fireEvent('invalid', this, 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){ + this.el.removeClass(this.invalidClass); + var t = Ext.getDom(this.msgTarget); + if(t){ + t.innerHTML = ''; + t.style.display = 'none'; + } + } + this.fireEvent('valid', this); + }, + + // private + getMessageHandler : function(){ + return Ext.form.MessageTargets[this.msgTarget]; + }, + + // private + getErrorCt : function(){ + return this.el.findParent('.x-form-element', 5, true) || // use form element wrap if available + this.el.findParent('.x-form-field-wrap', 5, true); // else direct field wrap + }, + + // private + alignErrorIcon : function(){ + this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]); + }, + + /** + * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}. + * @return {Mixed} value The field value + */ + getRawValue : function(){ + var v = this.rendered ? this.el.getValue() : Ext.value(this.value, ''); + if(v === this.emptyText){ + v = ''; + } + return v; + }, + + /** + * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}. + * @return {Mixed} value The field value + */ + getValue : function(){ + if(!this.rendered) { + return this.value; + } + var v = this.el.getValue(); + if(v === this.emptyText || v === undefined){ + v = ''; + } + return v; + }, + + /** + * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}. + * @param {Mixed} value The value to set + * @return {Mixed} value The field value that is set + */ + setRawValue : function(v){ + return (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)); + }, + + /** + * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}. + * @param {Mixed} value The value to set + * @return {Ext.form.Field} this + */ + setValue : function(v){ + this.value = v; + if(this.rendered){ + this.el.dom.value = (Ext.isEmpty(v) ? '' : v); + this.validate(); + } + return this; + }, + + // 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; + } + + /** + * @cfg {Boolean} autoWidth @hide + */ + /** + * @cfg {Boolean} autoHeight @hide + */ + + /** + * @cfg {String} autoEl @hide + */ +}); + + +Ext.form.MessageTargets = { + 'qtip' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + field.el.dom.qtip = msg; + field.el.dom.qclass = 'x-form-invalid-tip'; + if(Ext.QuickTips){ // fix for floating editors interacting with DND + Ext.QuickTips.enable(); + } + }, + clear: function(field){ + field.el.removeClass(field.invalidClass); + field.el.dom.qtip = ''; + } + }, + 'title' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + field.el.dom.title = msg; + }, + clear: function(field){ + field.el.dom.title = ''; + } + }, + 'under' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + if(!field.errorEl){ + var elp = field.getErrorCt(); + if(!elp){ // field has no container el + field.el.dom.title = msg; + return; + } + field.errorEl = elp.createChild({cls:'x-form-invalid-msg'}); + field.errorEl.setWidth(elp.getWidth(true)-20); + } + field.errorEl.update(msg); + Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field); + }, + clear: function(field){ + field.el.removeClass(field.invalidClass); + if(field.errorEl){ + Ext.form.Field.msgFx[field.msgFx].hide(field.errorEl, field); + }else{ + field.el.dom.title = ''; + } + } + }, + 'side' : { + mark: function(field, msg){ + field.el.addClass(field.invalidClass); + if(!field.errorIcon){ + var elp = field.getErrorCt(); + if(!elp){ // field has no container el + field.el.dom.title = msg; + return; + } + field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'}); + } + 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 = ''; + } + } + } +}; + +// anything other than normal should be considered experimental +Ext.form.Field.msgFx = { + normal : { + show: function(msgEl, f){ + msgEl.setDisplayed('block'); + }, + + hide : function(msgEl, f){ + msgEl.setDisplayed(false).update(''); + } + }, + + slide : { + show: function(msgEl, f){ + msgEl.slideIn('t', {stopFx:true}); + }, + + hide : function(msgEl, f){ + msgEl.slideOut('t', {stopFx:true,useDisplay:true}); + } + }, + + slideRight : { + show: function(msgEl, f){ + msgEl.fixDisplay(); + msgEl.alignTo(f.el, 'tl-tr'); + msgEl.slideIn('l', {stopFx:true}); + }, + + hide : function(msgEl, f){ + msgEl.slideOut('l', {stopFx:true,useDisplay:true}); + } + } +}; +Ext.reg('field', Ext.form.Field); +/** + * @class Ext.form.TextField + * @extends Ext.form.Field + *

Basic text field. Can be used as a direct replacement for traditional text inputs, + * 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.

+ *
+ * @constructor + * Creates a new TextField + * @param {Object} config Configuration options + * @xtype textfield + */ +Ext.form.TextField = Ext.extend(Ext.form.Field, { + /** + * @cfg {String} vtypeText A custom error message to display in place of the default message provided + * for the {@link #vtype} currently set for this field (defaults to ''). Note: + * only applies if {@link #vtype} is set, else ignored. + */ + /** + * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value + * before validation (defaults to null). + */ + /** + * @cfg {Boolean} grow true if this field should automatically grow and shrink to its content + * (defaults to false) + */ + grow : false, + /** + * @cfg {Number} growMin The minimum width to allow when {@link #grow} = true (defaults + * to 30) + */ + growMin : 30, + /** + * @cfg {Number} growMax The maximum width to allow when {@link #grow} = true (defaults + * to 800) + */ + growMax : 800, + /** + * @cfg {String} vtype A validation type name as defined in {@link Ext.form.VTypes} (defaults to null) + */ + vtype : null, + /** + * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do + * not match (defaults to null) + */ + maskRe : null, + /** + * @cfg {Boolean} disableKeyFilter Specify true to disable input keystroke filtering (defaults + * to false) + */ + disableKeyFilter : false, + /** + * @cfg {Boolean} allowBlank Specify false to validate that the value's length is > 0 (defaults to + * true) + */ + allowBlank : true, + /** + * @cfg {Number} minLength Minimum input field length required (defaults to 0) + */ + minLength : 0, + /** + * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). + * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting + * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be + * entered into the field use {@link Ext.form.Field#autoCreate autoCreate} to add + * any attributes you want to a field, for example:

+var myField = new Ext.form.NumberField({
+    id: 'mobile',
+    anchor:'90%',
+    fieldLabel: 'Mobile',
+    maxLength: 16, // for validation
+    autoCreate: {tag: 'input', type: 'text', size: '20', autocomplete: 'off', maxlength: '10'}
+});
+
+ */ + maxLength : Number.MAX_VALUE, + /** + * @cfg {String} minLengthText Error text to display if the {@link #minLength minimum length} + * validation fails (defaults to 'The minimum length for this field is {minLength}') + */ + minLengthText : 'The minimum length for this field is {0}', + /** + * @cfg {String} maxLengthText Error text to display if the {@link #maxLength maximum length} + * validation fails (defaults to 'The maximum length for this field is {maxLength}') + */ + maxLengthText : 'The maximum length for this field is {0}', + /** + * @cfg {Boolean} selectOnFocus true to automatically select any existing field text when the field + * receives input focus (defaults to false) + */ + selectOnFocus : false, + /** + * @cfg {String} blankText The error text to display if the {@link #allowBlank} validation + * fails (defaults to 'This field is required') + */ + blankText : 'This field is required', + /** + * @cfg {Function} validator A custom validation function to be called during field validation + * (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. + */ + validator : null, + /** + * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation + * (defaults to null). If the test fails, the field will be marked invalid using + * {@link #regexText}. + */ + regex : null, + /** + * @cfg {String} regexText The error text to display if {@link #regex} is used and the + * test fails during validation (defaults to '') + */ + regexText : '', + /** + * @cfg {String} emptyText The default text to place into an empty field (defaults to null). + * Note: that this value will be submitted to the server if this field is enabled and configured + * with a {@link #name}. + */ + emptyText : null, + /** + * @cfg {String} emptyClass The CSS class to apply to an empty field to style the {@link #emptyText} + * (defaults to 'x-form-empty-field'). This class is automatically added and removed as needed + * depending on the current field value. + */ + emptyClass : 'x-form-empty-field', + + /** + * @cfg {Boolean} enableKeyEvents true to enable the proxying of key events for the HTML input + * field (defaults to false) + */ + + initComponent : function(){ + Ext.form.TextField.superclass.initComponent.call(this); + this.addEvents( + /** + * @event autosize + * Fires when the {@link #autoSize} function is triggered. The field may or + * may not have actually changed size according to the default logic, but this event provides + * a hook for the developer to apply additional logic at runtime to resize the field if needed. + * @param {Ext.form.Field} this This text field + * @param {Number} width The new field width + */ + 'autosize', + + /** + * @event keydown + * Keydown input field event. This event only fires if {@link #enableKeyEvents} + * is set to true. + * @param {Ext.form.TextField} this This text field + * @param {Ext.EventObject} e + */ + 'keydown', + /** + * @event keyup + * Keyup input field event. This event only fires if {@link #enableKeyEvents} + * is set to true. + * @param {Ext.form.TextField} this This text field + * @param {Ext.EventObject} e + */ + 'keyup', + /** + * @event keypress + * Keypress input field event. This event only fires if {@link #enableKeyEvents} + * is set to true. + * @param {Ext.form.TextField} this This text field + * @param {Ext.EventObject} e + */ + 'keypress' + ); + }, + + // private + initEvents : function(){ + Ext.form.TextField.superclass.initEvents.call(this); + if(this.validationEvent == 'keyup'){ + this.validationTask = new Ext.util.DelayedTask(this.validate, this); + this.mon(this.el, 'keyup', this.filterValidation, this); + } + else if(this.validationEvent !== false){ + 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.emptyText){ + this.on('blur', this.postBlur, this); + this.applyEmptyText(); + } + } + if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Ext.form.VTypes[this.vtype+'Mask']))){ + this.mon(this.el, 'keypress', this.filterKeys, this); + } + if(this.grow){ + this.mon(this.el, 'keyup', this.onKeyUpBuffered, this, {buffer: 50}); + 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); + } + }, + + processValue : function(value){ + if(this.stripCharsRe){ + var newValue = value.replace(this.stripCharsRe, ''); + if(newValue !== value){ + this.setRawValue(newValue); + return newValue; + } + } + return value; + }, + + filterValidation : function(e){ + if(!e.isNavKeyPress()){ + this.validationTask.delay(this.validationDelay); + } + }, + + //private + onDisable: function(){ + Ext.form.TextField.superclass.onDisable.call(this); + if(Ext.isIE){ + this.el.dom.unselectable = 'on'; + } + }, + + //private + onEnable: function(){ + Ext.form.TextField.superclass.onEnable.call(this); + if(Ext.isIE){ + this.el.dom.unselectable = ''; + } + }, + + // private + onKeyUpBuffered : function(e){ + if(!e.isNavKeyPress()){ + this.autoSize(); + } + }, + + // private + onKeyUp : function(e){ + this.fireEvent('keyup', this, e); + }, + + // private + onKeyDown : function(e){ + this.fireEvent('keydown', this, e); + }, + + // private + onKeyPress : function(e){ + this.fireEvent('keypress', this, e); + }, + + /** + * Resets the current field value to the originally-loaded value and clears any validation messages. + * Also adds {@link #emptyText} and {@link #emptyClass} if the + * original value was blank. + */ + reset : function(){ + Ext.form.TextField.superclass.reset.call(this); + this.applyEmptyText(); + }, + + applyEmptyText : function(){ + if(this.rendered && this.emptyText && this.getRawValue().length < 1 && !this.hasFocus){ + this.setRawValue(this.emptyText); + this.el.addClass(this.emptyClass); + } + }, + + // private + preFocus : function(){ + var el = this.el; + if(this.emptyText){ + if(el.dom.value == this.emptyText){ + this.setRawValue(''); + } + el.removeClass(this.emptyClass); + } + if(this.selectOnFocus){ + (function(){ + el.dom.select(); + }).defer(this.inEditor && Ext.isIE ? 50 : 0); + } + }, + + // private + postBlur : function(){ + this.applyEmptyText(); + }, + + // private + filterKeys : function(e){ + // special keys don't generate charCodes, so leave them alone + if(e.ctrlKey || e.isSpecialKey()){ + return; + } + + if(!this.maskRe.test(String.fromCharCode(e.getCharCode()))){ + e.stopEvent(); + } + }, + + setValue : function(v){ + if(this.emptyText && this.el && !Ext.isEmpty(v)){ + this.el.removeClass(this.emptyClass); + } + Ext.form.TextField.superclass.setValue.apply(this, arguments); + this.applyEmptyText(); + this.autoSize(); + return this; + }, + + /** + * 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)){ + var msg = this.validator(value); + if(msg !== true){ + this.markInvalid(msg); + return false; + } + } + if(value.length < 1 || value === this.emptyText){ // if it's blank + if(this.allowBlank){ + this.clearInvalid(); + return true; + }else{ + this.markInvalid(this.blankText); + return false; + } + } + if(value.length < this.minLength){ + this.markInvalid(String.format(this.minLengthText, this.minLength)); + return false; + } + if(value.length > this.maxLength){ + this.markInvalid(String.format(this.maxLengthText, this.maxLength)); + return false; + } + if(this.vtype){ + var vt = Ext.form.VTypes; + if(!vt[this.vtype](value, this)){ + this.markInvalid(this.vtypeText || vt[this.vtype +'Text']); + return false; + } + } + if(this.regex && !this.regex.test(value)){ + this.markInvalid(this.regexText); + return false; + } + return true; + }, + + /** + * Selects text in this field + * @param {Number} start (optional) The index where the selection should start (defaults to 0) + * @param {Number} end (optional) The index where the selection should end (defaults to the text length) + */ + selectText : function(start, end){ + var v = this.getRawValue(); + var doFocus = false; + if(v.length > 0){ + start = start === undefined ? 0 : start; + end = end === undefined ? v.length : end; + var d = this.el.dom; + if(d.setSelectionRange){ + d.setSelectionRange(start, end); + }else if(d.createTextRange){ + var range = d.createTextRange(); + range.moveStart('character', start); + range.moveEnd('character', end-v.length); + range.select(); + } + doFocus = Ext.isGecko || Ext.isOpera; + }else{ + doFocus = true; + } + if(doFocus){ + this.focus(); + } + }, + + /** + * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. + * This only takes effect if {@link #grow} = true, and fires the {@link #autosize} event. + */ + autoSize : function(){ + if(!this.grow || !this.rendered){ + return; + } + if(!this.metrics){ + this.metrics = Ext.util.TextMetrics.createInstance(this.el); + } + var el = this.el; + var v = el.dom.value; + var d = document.createElement('div'); + d.appendChild(document.createTextNode(v)); + v = d.innerHTML; + d = null; + Ext.removeNode(d); + v += ' '; + var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin)); + this.el.setWidth(w); + this.fireEvent('autosize', this, w); + }, + + onDestroy: function(){ + if(this.validationTask){ + this.validationTask.cancel(); + this.validationTask = null; + } + Ext.form.TextField.superclass.onDestroy.call(this); + } +}); +Ext.reg('textfield', Ext.form.TextField); +/** + * @class Ext.form.TriggerField + * @extends Ext.form.TextField + * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default). + * The trigger has no default action, so you must assign a function to implement the trigger click handler by + * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox + * for which you can provide a custom implementation. For example: + *

+var trigger = new Ext.form.TriggerField();
+trigger.onTriggerClick = myTriggerFn;
+trigger.applyToMarkup('my-field');
+
+ * + * However, in general you will most likely want to use TriggerField as the base class for a reusable component. + * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this. + * + * @constructor + * Create a new TriggerField. + * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied + * to the base TextField) + * @xtype trigger + */ +Ext.form.TriggerField = Ext.extend(Ext.form.TextField, { + /** + * @cfg {String} triggerClass + * An additional CSS class used to style the trigger button. The trigger will always get the + * class 'x-form-trigger' by default and triggerClass will be appended if specified. + */ + /** + * @cfg {Mixed} triggerConfig + *

A {@link Ext.DomHelper DomHelper} config object specifying the structure of the + * trigger element for this Field. (Optional).

+ *

Specify this when you need a customized element to act as the trigger button for a TriggerField.

+ *

Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning + * and appearance of the trigger. Defaults to:

+ *
{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}
+ */ + /** + * @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: "16", autocomplete: "off"}
+ */ + defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"}, + /** + * @cfg {Boolean} hideTrigger true to hide the trigger element and display only the base + * text field (defaults to false) + */ + hideTrigger:false, + /** + * @cfg {Boolean} editable false to prevent the user from typing text directly into the field, + * the field will only respond to a click on the trigger to set the value. (defaults to true) + */ + editable: true, + /** + * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to + * x-trigger-wrap-focus. + */ + wrapFocusClass: 'x-trigger-wrap-focus', + /** + * @hide + * @method autoSize + */ + autoSize: Ext.emptyFn, + // private + monitorTab : true, + // private + deferHeight : true, + // private + mimicing : false, + + actionMode: 'wrap', + + // private + onResize : function(w, h){ + Ext.form.TriggerField.superclass.onResize.call(this, w, h); + if(typeof w == 'number'){ + this.el.setWidth(this.adjustWidth('input', w - this.trigger.getWidth())); + } + this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth()); + }, + + // private + adjustSize : Ext.BoxComponent.prototype.adjustSize, + + // private + getResizeEl : function(){ + return this.wrap; + }, + + // private + getPositionEl : function(){ + return this.wrap; + }, + + // private + alignErrorIcon : function(){ + if(this.wrap){ + this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); + } + }, + + // private + onRender : function(ct, position){ + Ext.form.TriggerField.superclass.onRender.call(this, ct, position); + + this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'}); + this.trigger = this.wrap.createChild(this.triggerConfig || + {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}); + if(this.hideTrigger){ + this.trigger.setDisplayed(false); + } + this.initTrigger(); + if(!this.width){ + this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth()); + } + if(!this.editable){ + this.editable = true; + this.setEditable(false); + } + }, + + afterRender : function(){ + Ext.form.TriggerField.superclass.afterRender.call(this); + }, + + // private + initTrigger : function(){ + this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true}); + this.trigger.addClassOnOver('x-form-trigger-over'); + this.trigger.addClassOnClick('x-form-trigger-click'); + }, + + // private + onDestroy : function(){ + Ext.destroy(this.trigger, this.wrap); + if (this.mimicing){ + Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this); + } + Ext.form.TriggerField.superclass.onDestroy.call(this); + }, + + // private + onFocus : function(){ + Ext.form.TriggerField.superclass.onFocus.call(this); + if(!this.mimicing){ + this.wrap.addClass(this.wrapFocusClass); + this.mimicing = true; + Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {delay: 10}); + if(this.monitorTab){ + this.el.on('keydown', this.checkTab, this); + } + } + }, + + // private + checkTab : function(e){ + if(e.getKey() == e.TAB){ + this.triggerBlur(); + } + }, + + // private + onBlur : function(){ + // do nothing + }, + + // private + mimicBlur : function(e){ + if(!this.wrap.contains(e.target) && this.validateBlur(e)){ + this.triggerBlur(); + } + }, + + // private + triggerBlur : function(){ + this.mimicing = false; + Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this); + if(this.monitorTab && this.el){ + this.el.un("keydown", this.checkTab, this); + } + Ext.form.TriggerField.superclass.onBlur.call(this); + if(this.wrap){ + this.wrap.removeClass(this.wrapFocusClass); + } + }, + + beforeBlur : Ext.emptyFn, + + /** + * Allow or prevent the user from directly editing the field text. If false is passed, + * the user will only be able to modify the field using the trigger. 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.addClass('x-trigger-noedit').on('click', this.onTriggerClick, this).dom.setAttribute('readOnly', true); + }else{ + this.el.removeClass('x-trigger-noedit').un('click', this.onTriggerClick, this).dom.removeAttribute('readOnly'); + } + }, + + // private + // This should be overriden by any subclass that needs to check whether or not the field can be blurred. + validateBlur : function(e){ + return true; + }, + + /** + * The function that should handle the trigger's click event. This method does nothing by default + * until overridden by an implementing function. See Ext.form.ComboBox and Ext.form.DateField for + * sample implementations. + * @method + * @param {EventObject} e + */ + onTriggerClick : Ext.emptyFn + + /** + * @cfg {Boolean} grow @hide + */ + /** + * @cfg {Number} growMin @hide + */ + /** + * @cfg {Number} growMax @hide + */ +}); + +/** + * @class Ext.form.TwinTriggerField + * @extends Ext.form.TriggerField + * TwinTriggerField is not a public class to be used directly. It is meant as an abstract base class + * to be extended by an implementing class. For an example of implementing this class, see the custom + * SearchField implementation here: + * http://extjs.com/deploy/ext/examples/form/custom.html + */ +Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, { + /** + * @cfg {Mixed} triggerConfig + *

A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements + * for this Field. (Optional).

+ *

Specify this when you need a customized element to contain the two trigger elements for this Field. + * Each trigger element must be marked by the CSS class x-form-trigger (also see + * {@link #trigger1Class} and {@link #trigger2Class}).

+ *

Note that when using this option, it is the developer's responsibility to ensure correct sizing, + * positioning and appearance of the triggers.

+ */ + /** + * @cfg {String} trigger1Class + * An additional CSS class used to style the trigger button. The trigger will always get the + * class 'x-form-trigger' by default and triggerClass will be appended if specified. + */ + /** + * @cfg {String} trigger2Class + * An additional CSS class used to style the trigger button. The trigger will always get the + * class 'x-form-trigger' by default and triggerClass will be appended if specified. + */ + + initComponent : function(){ + Ext.form.TwinTriggerField.superclass.initComponent.call(this); + + this.triggerConfig = { + tag:'span', cls:'x-form-twin-triggers', cn:[ + {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class}, + {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class} + ]}; + }, + + getTrigger : function(index){ + return this.triggers[index]; + }, + + initTrigger : function(){ + var ts = this.trigger.select('.x-form-trigger', true); + this.wrap.setStyle('overflow', 'hidden'); + var triggerField = this; + ts.each(function(t, all, index){ + t.hide = function(){ + var w = triggerField.wrap.getWidth(); + this.dom.style.display = 'none'; + triggerField.el.setWidth(w-triggerField.trigger.getWidth()); + }; + t.show = function(){ + var w = triggerField.wrap.getWidth(); + this.dom.style.display = ''; + triggerField.el.setWidth(w-triggerField.trigger.getWidth()); + }; + var triggerIndex = 'Trigger'+(index+1); + + if(this['hide'+triggerIndex]){ + t.dom.style.display = 'none'; + } + this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true}); + t.addClassOnOver('x-form-trigger-over'); + t.addClassOnClick('x-form-trigger-click'); + }, this); + this.triggers = ts.elements; + }, + + /** + * The function that should handle the trigger's click event. This method does nothing by default + * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick} + * for additional information. + * @method + * @param {EventObject} e + */ + onTrigger1Click : Ext.emptyFn, + /** + * The function that should handle the trigger's click event. This method does nothing by default + * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick} + * for additional information. + * @method + * @param {EventObject} e + */ + onTrigger2Click : Ext.emptyFn +}); +Ext.reg('trigger', Ext.form.TriggerField);/** + * @class Ext.form.TextArea + * @extends Ext.form.TextField + * Multiline text field. Can be used as a direct replacement for traditional textarea fields, plus adds + * support for auto-sizing. + * @constructor + * Creates a new TextArea + * @param {Object} config Configuration options + * @xtype textarea + */ +Ext.form.TextArea = Ext.extend(Ext.form.TextField, { + /** + * @cfg {Number} growMin The minimum height to allow when {@link Ext.form.TextField#grow grow}=true + * (defaults to 60) + */ + growMin : 60, + /** + * @cfg {Number} growMax The maximum height to allow when {@link Ext.form.TextField#grow grow}=true + * (defaults to 1000) + */ + growMax: 1000, + growAppend : ' \n ', + growPad : Ext.isWebKit ? -6 : 0, + + enterIsSpecial : false, + + /** + * @cfg {Boolean} preventScrollbars true to prevent scrollbars from appearing regardless of how much text is + * in the field (equivalent to setting overflow: hidden, defaults to false) + */ + preventScrollbars: false, + /** + * @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: "textarea", style: "width:100px;height:60px;", autocomplete: "off"}
+ */ + + // private + onRender : function(ct, position){ + if(!this.el){ + this.defaultAutoCreate = { + tag: "textarea", + style:"width:100px;height:60px;", + autocomplete: "off" + }; + } + Ext.form.TextArea.superclass.onRender.call(this, ct, position); + if(this.grow){ + this.textSizeEl = Ext.DomHelper.append(document.body, { + tag: "pre", cls: "x-form-grow-sizer" + }); + if(this.preventScrollbars){ + this.el.setStyle("overflow", "hidden"); + } + this.el.setHeight(this.growMin); + } + }, + + onDestroy : function(){ + Ext.destroy(this.textSizeEl); + Ext.form.TextArea.superclass.onDestroy.call(this); + }, + + fireKey : function(e){ + if(e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() != e.ENTER || e.hasModifier()))){ + this.fireEvent("specialkey", this, e); + } + }, + + // private + onKeyUp : function(e){ + if(!e.isNavKeyPress() || e.getKey() == e.ENTER){ + this.autoSize(); + } + Ext.form.TextArea.superclass.onKeyUp.call(this, e); + }, + + /** + * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. + * This only takes effect if grow = true, and fires the {@link #autosize} event if the height changes. + */ + autoSize: function(){ + if(!this.grow || !this.textSizeEl){ + return; + } + var el = this.el; + var v = el.dom.value; + var ts = this.textSizeEl; + ts.innerHTML = ''; + ts.appendChild(document.createTextNode(v)); + v = ts.innerHTML; + Ext.fly(ts).setWidth(this.el.getWidth()); + if(v.length < 1){ + v = "  "; + }else{ + v += this.growAppend; + if(Ext.isIE){ + v = v.replace(/\n/g, '
'); + } + } + ts.innerHTML = v; + var h = Math.min(this.growMax, Math.max(ts.offsetHeight, this.growMin) + this.growPad); + if(h != this.lastHeight){ + this.lastHeight = h; + this.el.setHeight(h); + this.fireEvent("autosize", this, h); + } + } +}); +Ext.reg('textarea', Ext.form.TextArea);/** + * @class Ext.form.NumberField + * @extends Ext.form.TextField + * Numeric text field that provides automatic keystroke filtering and numeric validation. + * @constructor + * Creates a new NumberField + * @param {Object} config Configuration options + * @xtype numberfield + */ +Ext.form.NumberField = Ext.extend(Ext.form.TextField, { + /** + * @cfg {RegExp} stripCharsRe @hide + */ + /** + * @cfg {RegExp} maskRe @hide + */ + /** + * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field") + */ + fieldClass: "x-form-field x-form-num-field", + /** + * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true) + */ + allowDecimals : true, + /** + * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.') + */ + decimalSeparator : ".", + /** + * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2) + */ + decimalPrecision : 2, + /** + * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true) + */ + allowNegative : true, + /** + * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY) + */ + minValue : Number.NEGATIVE_INFINITY, + /** + * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE) + */ + maxValue : Number.MAX_VALUE, + /** + * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}") + */ + minText : "The minimum value for this field is {0}", + /** + * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}") + */ + maxText : "The maximum value for this field is {0}", + /** + * @cfg {String} nanText Error text to display if the value is not a valid number. For example, this can happen + * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number") + */ + nanText : "{0} is not a valid number", + /** + * @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789'). + */ + baseChars : "0123456789", + + // private + initEvents : function(){ + var allowed = this.baseChars + ''; + if (this.allowDecimals) { + allowed += this.decimalSeparator; + } + if (this.allowNegative) { + allowed += '-'; + } + this.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']'); + Ext.form.NumberField.superclass.initEvents.call(this); + }, + + // private + validateValue : function(value){ + if(!Ext.form.NumberField.superclass.validateValue.call(this, value)){ + return false; + } + if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid + return true; + } + value = String(value).replace(this.decimalSeparator, "."); + if(isNaN(value)){ + this.markInvalid(String.format(this.nanText, value)); + return false; + } + var num = this.parseValue(value); + if(num < this.minValue){ + this.markInvalid(String.format(this.minText, this.minValue)); + return false; + } + if(num > this.maxValue){ + this.markInvalid(String.format(this.maxText, this.maxValue)); + return false; + } + return true; + }, + + getValue : function(){ + return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this))); + }, + + setValue : function(v){ + v = typeof v == 'number' ? v : parseFloat(String(v).replace(this.decimalSeparator, ".")); + v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator); + return Ext.form.NumberField.superclass.setValue.call(this, v); + }, + + // private + parseValue : function(value){ + value = parseFloat(String(value).replace(this.decimalSeparator, ".")); + return isNaN(value) ? '' : value; + }, + + // private + fixPrecision : function(value){ + var nan = isNaN(value); + if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){ + return nan ? '' : value; + } + return parseFloat(parseFloat(value).toFixed(this.decimalPrecision)); + }, + + beforeBlur : function(){ + var v = this.parseValue(this.getRawValue()); + if(!Ext.isEmpty(v)){ + this.setValue(this.fixPrecision(v)); + } + } +}); +Ext.reg('numberfield', Ext.form.NumberField);/** + * @class Ext.form.DateField + * @extends Ext.form.TriggerField + * Provides a date input field with a {@link Ext.DatePicker} dropdown and automatic date validation. + * @constructor + * Create a new DateField + * @param {Object} config + * @xtype datefield + */ +Ext.form.DateField = Ext.extend(Ext.form.TriggerField, { + /** + * @cfg {String} format + * The default date format string which can be overriden for localization support. The format must be + * valid according to {@link Date#parseDate} (defaults to 'm/d/Y'). + */ + format : "m/d/Y", + /** + * @cfg {String} altFormats + * Multiple date formats separated by "|" to try when parsing a user input value and it + * does not match the defined format (defaults to + * 'm/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d'). + */ + altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d", + /** + * @cfg {String} disabledDaysText + * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled') + */ + disabledDaysText : "Disabled", + /** + * @cfg {String} disabledDatesText + * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled') + */ + disabledDatesText : "Disabled", + /** + * @cfg {String} minText + * The error text to display when the date in the cell is before {@link #minValue} (defaults to + * 'The date in this field must be after {minValue}'). + */ + minText : "The date in this field must be equal to or after {0}", + /** + * @cfg {String} maxText + * The error text to display when the date in the cell is after {@link #maxValue} (defaults to + * 'The date in this field must be before {maxValue}'). + */ + maxText : "The date in this field must be equal to or before {0}", + /** + * @cfg {String} invalidText + * The error text to display when the date in the field is invalid (defaults to + * '{value} is not a valid date - it must be in the format {format}'). + */ + invalidText : "{0} is not a valid date - it must be in the format {1}", + /** + * @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-date-trigger' which displays a calendar icon). + */ + triggerClass : 'x-form-date-trigger', + /** + * @cfg {Boolean} showToday + * false to hide the footer area of the DatePicker containing the Today button and disable + * the keyboard handler for spacebar that selects the current date (defaults to true). + */ + showToday : true, + /** + * @cfg {Date/String} minValue + * The minimum allowed date. Can be either a Javascript date object or a string date in a + * valid format (defaults to null). + */ + /** + * @cfg {Date/String} maxValue + * The maximum allowed date. Can be either a Javascript date object or a string date in a + * valid format (defaults to null). + */ + /** + * @cfg {Array} disabledDays + * An array of days to disable, 0 based (defaults to null). Some examples:

+// disable Sunday and Saturday:
+disabledDays:  [0, 6]
+// disable weekdays:
+disabledDays: [1,2,3,4,5]
+     * 
+ */ + /** + * @cfg {Array} disabledDates + * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular + * expression so they are very powerful. Some examples:

+// disable these exact dates:
+disabledDates: ["03/08/2003", "09/16/2003"]
+// disable these days for every year:
+disabledDates: ["03/08", "09/16"]
+// only match the beginning (useful if you are using short years):
+disabledDates: ["^03/08"]
+// disable every day in March 2006:
+disabledDates: ["03/../2006"]
+// disable every day in every March:
+disabledDates: ["^03"]
+     * 
+ * Note that the format of the dates included in the array should exactly match the {@link #format} config. + * In order to support regular expressions, if you are using a {@link #format date format} that has "." in + * it, you will have to escape the dot when restricting dates. For example: ["03\\.08\\.03"]. + */ + /** + * @cfg {String/Object} autoCreate + * A {@link Ext.DomHelper DomHelper element specification object}, or true for the default element + * specification object:

+     * autoCreate: {tag: "input", type: "text", size: "10", autocomplete: "off"}
+     * 
+ */ + + // private + defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "off"}, + + initComponent : function(){ + Ext.form.DateField.superclass.initComponent.call(this); + + this.addEvents( + /** + * @event select + * Fires when a date is selected via the date picker. + * @param {Ext.form.DateField} this + * @param {Date} date The date that was selected + */ + 'select' + ); + + if(Ext.isString(this.minValue)){ + this.minValue = this.parseDate(this.minValue); + } + if(Ext.isString(this.maxValue)){ + this.maxValue = this.parseDate(this.maxValue); + } + this.disabledDatesRE = null; + this.initDisabledDays(); + }, + + // private + initDisabledDays : function(){ + if(this.disabledDates){ + var dd = this.disabledDates, + len = dd.length - 1, + re = "(?:"; + + Ext.each(dd, function(d, i){ + re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i]; + if(i != len){ + re += '|'; + } + }, this); + this.disabledDatesRE = new RegExp(re + ')'); + } + }, + + /** + * Replaces any existing disabled dates with new values and refreshes the DatePicker. + * @param {Array} disabledDates An array of date strings (see the {@link #disabledDates} config + * for details on supported values) used to disable a pattern of dates. + */ + setDisabledDates : function(dd){ + this.disabledDates = dd; + this.initDisabledDays(); + if(this.menu){ + this.menu.picker.setDisabledDates(this.disabledDatesRE); + } + }, + + /** + * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. + * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} + * config for details on supported values. + */ + setDisabledDays : function(dd){ + this.disabledDays = dd; + if(this.menu){ + this.menu.picker.setDisabledDays(dd); + } + }, + + /** + * Replaces any existing {@link #minValue} with the new value and refreshes the DatePicker. + * @param {Date} value The minimum date that can be selected + */ + setMinValue : function(dt){ + this.minValue = (Ext.isString(dt) ? this.parseDate(dt) : dt); + if(this.menu){ + this.menu.picker.setMinDate(this.minValue); + } + }, + + /** + * Replaces any existing {@link #maxValue} with the new value and refreshes the DatePicker. + * @param {Date} value The maximum date that can be selected + */ + setMaxValue : function(dt){ + this.maxValue = (Ext.isString(dt) ? this.parseDate(dt) : dt); + if(this.menu){ + this.menu.picker.setMaxDate(this.maxValue); + } + }, + + // private + validateValue : function(value){ + value = this.formatDate(value); + if(!Ext.form.DateField.superclass.validateValue.call(this, value)){ + return false; + } + if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid + return true; + } + var svalue = value; + value = this.parseDate(value); + if(!value){ + this.markInvalid(String.format(this.invalidText, svalue, this.format)); + return false; + } + var time = value.getTime(); + if(this.minValue && time < this.minValue.getTime()){ + this.markInvalid(String.format(this.minText, this.formatDate(this.minValue))); + return false; + } + if(this.maxValue && time > this.maxValue.getTime()){ + this.markInvalid(String.format(this.maxText, this.formatDate(this.maxValue))); + return false; + } + if(this.disabledDays){ + var day = value.getDay(); + for(var i = 0; i < this.disabledDays.length; i++) { + if(day === this.disabledDays[i]){ + this.markInvalid(this.disabledDaysText); + return false; + } + } + } + var fvalue = this.formatDate(value); + if(this.disabledDatesRE && this.disabledDatesRE.test(fvalue)){ + this.markInvalid(String.format(this.disabledDatesText, fvalue)); + return false; + } + return true; + }, + + // private + // Provides logic to override the default TriggerField.validateBlur which just returns true + validateBlur : function(){ + return !this.menu || !this.menu.isVisible(); + }, + + /** + * Returns the current date value of the date field. + * @return {Date} The date value + */ + getValue : function(){ + return this.parseDate(Ext.form.DateField.superclass.getValue.call(this)) || ""; + }, + + /** + * Sets the value of the date field. You can pass a date object or any string that can be + * parsed into a valid date, using {@link #format} as the date format, according + * to the same rules as {@link Date#parseDate} (the default format used is "m/d/Y"). + *
Usage: + *

+//All of these calls set the same date value (May 4, 2006)
+
+//Pass a date object:
+var dt = new Date('5/4/2006');
+dateField.setValue(dt);
+
+//Pass a date string (default format):
+dateField.setValue('05/04/2006');
+
+//Pass a date string (custom format):
+dateField.format = 'Y-m-d';
+dateField.setValue('2006-05-04');
+
+ * @param {String/Date} date The date or valid date string + * @return {Ext.form.Field} this + */ + setValue : function(date){ + return Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date))); + }, + + // private + parseDate : function(value){ + if(!value || Ext.isDate(value)){ + return value; + } + var v = Date.parseDate(value, this.format); + if(!v && this.altFormats){ + if(!this.altFormatsArray){ + this.altFormatsArray = this.altFormats.split("|"); + } + for(var i = 0, len = this.altFormatsArray.length; i < len && !v; i++){ + v = Date.parseDate(value, this.altFormatsArray[i]); + } + } + return v; + }, + + // private + onDestroy : function(){ + Ext.destroy(this.menu); + Ext.form.DateField.superclass.onDestroy.call(this); + }, + + // private + formatDate : function(date){ + return Ext.isDate(date) ? date.dateFormat(this.format) : date; + }, + + /** + * @method onTriggerClick + * @hide + */ + // private + // Implements the default empty TriggerField.onTriggerClick function to display the DatePicker + onTriggerClick : function(){ + if(this.disabled){ + return; + } + if(this.menu == null){ + this.menu = new Ext.menu.DateMenu({ + hideOnClick: false + }); + } + this.onFocus(); + Ext.apply(this.menu.picker, { + minDate : this.minValue, + maxDate : this.maxValue, + disabledDatesRE : this.disabledDatesRE, + disabledDatesText : this.disabledDatesText, + disabledDays : this.disabledDays, + disabledDaysText : this.disabledDaysText, + format : this.format, + showToday : this.showToday, + minText : String.format(this.minText, this.formatDate(this.minValue)), + maxText : String.format(this.maxText, this.formatDate(this.maxValue)) + }); + this.menu.picker.setValue(this.getValue() || new Date()); + this.menu.show(this.el, "tl-bl?"); + this.menuEvents('on'); + }, + + //private + menuEvents: function(method){ + this.menu[method]('select', this.onSelect, this); + this.menu[method]('hide', this.onMenuHide, this); + this.menu[method]('show', this.onFocus, this); + }, + + onSelect: function(m, d){ + this.setValue(d); + this.fireEvent('select', this, d); + this.menu.hide(); + }, + + onMenuHide: function(){ + this.focus(false, 60); + this.menuEvents('un'); + }, + + // private + beforeBlur : function(){ + var v = this.parseDate(this.getRawValue()); + if(v){ + this.setValue(v); + } + } + + /** + * @cfg {Boolean} grow @hide + */ + /** + * @cfg {Number} growMin @hide + */ + /** + * @cfg {Number} growMax @hide + */ + /** + * @hide + * @method autoSize + */ +}); +Ext.reg('datefield', Ext.form.DateField);/** + * @class Ext.form.DisplayField + * @extends Ext.form.Field + * A display-only text field which is not validated and not submitted. + * @constructor + * Creates a new DisplayField. + * @param {Object} config Configuration options + * @xtype displayfield + */ +Ext.form.DisplayField = Ext.extend(Ext.form.Field, { + validationEvent : false, + validateOnBlur : false, + defaultAutoCreate : {tag: "div"}, + /** + * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-display-field") + */ + fieldClass : "x-form-display-field", + /** + * @cfg {Boolean} htmlEncode false to skip HTML-encoding the text when rendering it (defaults to + * false). This might be useful if you want to include tags in the field's innerHTML rather than + * rendering them as string literals per the default logic. + */ + htmlEncode: false, + + // private + initEvents : Ext.emptyFn, + + isValid : function(){ + return true; + }, + + validate : function(){ + return true; + }, + + getRawValue : function(){ + var v = this.rendered ? this.el.dom.innerHTML : Ext.value(this.value, ''); + if(v === this.emptyText){ + v = ''; + } + if(this.htmlEncode){ + v = Ext.util.Format.htmlDecode(v); + } + return v; + }, + + getValue : function(){ + return this.getRawValue(); + }, + + getName: function() { + return this.name; + }, + + setRawValue : function(v){ + if(this.htmlEncode){ + v = Ext.util.Format.htmlEncode(v); + } + return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v); + }, + + setValue : function(v){ + this.setRawValue(v); + return this; + } + /** + * @cfg {String} inputType + * @hide + */ + /** + * @cfg {Boolean} disabled + * @hide + */ + /** + * @cfg {Boolean} readOnly + * @hide + */ + /** + * @cfg {Boolean} validateOnBlur + * @hide + */ + /** + * @cfg {Number} validationDelay + * @hide + */ + /** + * @cfg {String/Boolean} validationEvent + * @hide + */ +}); + +Ext.reg('displayfield', Ext.form.DisplayField); +/** + * @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} 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: "24", autocomplete: "off"}
+ */ + /** + * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to undefined). + * Acceptable values for this property are: + *
+ *

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. + *
+ *

See also {@link #queryParam}.

+ */ + 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: + *
+ */ + 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: + *

+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;
+        }
+    }
+});
+     * 
+ * 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: 'local',
+    triggerAction: 'all',
+    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: + */ + 'beforequery' + ); + if(this.transform){ + var s = Ext.getDom(this.transform); + if(!this.hiddenName){ + this.hiddenName = s.name; + } + if(!this.store){ + this.mode = 'local'; + var d = [], opts = s.options; + for(var i = 0, len = opts.length;i < len; i++){ + var o = opts[i], + value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text; + if(o.selected && Ext.isEmpty(this.value, true)) { + this.value = value; + } + d.push([value, o.text]); + } + this.store = new Ext.data.ArrayStore({ + 'id': 0, + fields: ['value', 'text'], + data : d, + autoDestroy: true + }); + this.valueField = 'value'; + this.displayField = 'text'; + } + s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference + if(!this.lazyRender){ + 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 + } + } + //auto-configure store from local array data + else if(this.store){ + this.store = Ext.StoreMgr.lookup(this.store); + if(this.store.autoCreated){ + this.displayField = this.valueField = 'field1'; + if(!this.store.expandData){ + this.displayField = 'field2'; + } + this.mode = 'local'; + } + } + + this.selectedIndex = -1; + if(this.mode == 'local'){ + if(!Ext.isDefined(this.initialConfig.queryDelay)){ + this.queryDelay = 10; + } + if(!Ext.isDefined(this.initialConfig.minChars)){ + this.minChars = 0; + } + } + }, + + // private + onRender : function(ct, position){ + 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); + + // prevent input submission + this.el.dom.removeAttribute('name'); + } + if(Ext.isGecko){ + this.el.dom.setAttribute('autocomplete', 'off'); + } + + if(!this.lazyInit){ + this.initList(); + }else{ + this.on('focus', this.initList, this, {single: true}); + } + }, + + // private + initValue : function(){ + 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 : ''; + } + }, + + // private + initList : function(){ + if(!this.list){ + var cls = 'x-combo-list'; + + this.list = new Ext.Layer({ + parentEl: this.getListParent(), + shadow: this.shadow, + cls: [cls, this.listClass].join(' '), + constrain:false + }); + + var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth); + this.list.setSize(lw, 0); + this.list.swallowEvent('mousewheel'); + this.assetHeight = 0; + if(this.syncFont !== false){ + this.list.setStyle('font-size', this.el.getStyle('font-size')); + } + if(this.title){ + this.header = this.list.createChild({cls:cls+'-hd', html: this.title}); + this.assetHeight += this.header.getHeight(); + } + + this.innerList = this.list.createChild({cls:cls+'-inner'}); + this.mon(this.innerList, 'mouseover', this.onViewOver, this); + this.mon(this.innerList, 'mousemove', this.onViewMove, this); + this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); + + if(this.pageSize){ + this.footer = this.list.createChild({cls:cls+'-ft'}); + this.pageTb = new Ext.PagingToolbar({ + store: this.store, + pageSize: this.pageSize, + renderTo:this.footer + }); + this.assetHeight += this.footer.getHeight(); + } + + if(!this.tpl){ + /** + * @cfg {String/Ext.XTemplate} tpl

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:qtip
attribute 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-item
to the template's container element.

+ *

Also see {@link #itemSelector} for additional details.

+ */ + this.tpl = '
{' + this.displayField + '}
'; + /** + * @cfg {String} itemSelector + *

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 ? + '
'+this.loadingText+'
' : ''); + this.restrictHeight(); + this.selectedIndex = -1; + }, + + // private + onLoad : function(){ + if(!this.hasFocus){ + return; + } + if(this.store.getCount() > 0){ + this.expand(); + this.restrictHeight(); + if(this.lastQuery == this.allQuery){ + if(this.editable){ + this.el.dom.select(); + } + if(!this.selectByValue(this.value, true)){ + this.select(0, true); + } + }else{ + this.selectNext(); + if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){ + this.taTask.delay(this.typeAheadDelay); + } + } + }else{ + this.onEmptyResults(); + } + //this.el.focus(); + }, + + // private + onTypeAhead : function(){ + if(this.store.getCount() > 0){ + var r = this.store.getAt(0); + var newValue = r.data[this.displayField]; + var len = newValue.length; + var selStart = this.getRawValue().length; + if(selStart != len){ + this.setRawValue(newValue); + this.selectText(selStart, newValue.length); + } + } + }, + + // private + onSelect : function(record, index){ + if(this.fireEvent('beforeselect', this, record, index) !== false){ + this.setValue(record.data[this.valueField || this.displayField]); + this.collapse(); + this.fireEvent('select', this, record, index); + } + }, + + // inherit docs + getName: function(){ + var hf = this.hiddenField; + return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this); + }, + + /** + * Returns the currently selected field value or empty string if no value is set. + * @return {String} value The selected value + */ + getValue : function(){ + if(this.valueField){ + return Ext.isDefined(this.value) ? this.value : ''; + }else{ + return Ext.form.ComboBox.superclass.getValue.call(this); + } + }, + + /** + * Clears any text/value currently set in the field + */ + clearValue : function(){ + if(this.hiddenField){ + this.hiddenField.value = ''; + } + this.setRawValue(''); + this.lastSelectionText = ''; + this.applyEmptyText(); + this.value = ''; + }, + + /** + * Sets the specified value into the field. If the value finds a match, the corresponding record text + * will be displayed in the field. If the value does not match the data value of an existing item, + * and the valueNotFoundText config option is defined, it will be displayed as the default field text. + * Otherwise the field will be blank (although the value will still be set). + * @param {String} value The value to match + * @return {Ext.form.Field} this + */ + setValue : function(v){ + var text = v; + if(this.valueField){ + var r = this.findRecord(this.valueField, v); + if(r){ + text = r.data[this.displayField]; + }else if(Ext.isDefined(this.valueNotFoundText)){ + text = this.valueNotFoundText; + } + } + this.lastSelectionText = text; + if(this.hiddenField){ + this.hiddenField.value = v; + } + Ext.form.ComboBox.superclass.setValue.call(this, text); + this.value = v; + return this; + }, + + // private + findRecord : function(prop, value){ + var record; + if(this.store.getCount() > 0){ + this.store.each(function(r){ + if(r.data[prop] == value){ + record = r; + return false; + } + }); + } + return record; + }, + + // private + onViewMove : function(e, t){ + this.inKeyMode = false; + }, + + // private + onViewOver : function(e, t){ + if(this.inKeyMode){ // prevent key nav and mouse over conflicts + return; + } + var item = this.view.findItemFromChild(t); + if(item){ + var index = this.view.indexOf(item); + this.select(index, false); + } + }, + + // private + onViewClick : function(doFocus){ + var index = this.view.getSelectedIndexes()[0]; + var r = this.store.getAt(index); + if(r){ + this.onSelect(r, index); + } + 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; + 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.endUpdate(); + }, + + // private + onEmptyResults : function(){ + this.collapse(); + }, + + /** + * Returns true if the dropdown list is expanded, else false. + */ + isExpanded : function(){ + return this.list && this.list.isVisible(); + }, + + /** + * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire. + * The store must be loaded and the list expanded for this function to work, otherwise use setValue. + * @param {String} value The data value of the item to select + * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the + * selected item if it is not currently in view (defaults to true) + * @return {Boolean} True if the value matched an item in the list, else false + */ + selectByValue : function(v, scrollIntoView){ + if(!Ext.isEmpty(v, true)){ + var r = this.findRecord(this.valueField || this.displayField, v); + if(r){ + this.select(this.store.indexOf(r), scrollIntoView); + return true; + } + } + return false; + }, + + /** + * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire. + * The store must be loaded and the list expanded for this function to work, otherwise use setValue. + * @param {Number} index The zero-based index of the list item to select + * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the + * selected item if it is not currently in view (defaults to true) + */ + select : function(index, scrollIntoView){ + this.selectedIndex = index; + this.view.select(index); + if(scrollIntoView !== false){ + var el = this.view.getNode(index); + if(el){ + this.innerList.scrollChildIntoView(el, false); + } + } + }, + + // private + selectNext : function(){ + var ct = this.store.getCount(); + if(ct > 0){ + if(this.selectedIndex == -1){ + this.select(0); + }else if(this.selectedIndex < ct-1){ + this.select(this.selectedIndex+1); + } + } + }, + + // private + selectPrev : function(){ + var ct = this.store.getCount(); + if(ct > 0){ + if(this.selectedIndex == -1){ + this.select(0); + }else if(this.selectedIndex !== 0){ + this.select(this.selectedIndex-1); + } + } + }, + + // private + onKeyUp : function(e){ + var k = e.getKey(); + if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){ + this.lastKey = k; + this.dqTask.delay(this.queryDelay); + } + Ext.form.ComboBox.superclass.onKeyUp.call(this, e); + }, + + // private + validateBlur : function(){ + return !this.list || !this.list.isVisible(); + }, + + // private + initQuery : function(){ + this.doQuery(this.getRawValue()); + }, + + // 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); + } + }, + + /** + * Execute 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} query The SQL query to execute + * @param {Boolean} forceAll 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 (defaults to false) + */ + doQuery : function(q, forceAll){ + q = Ext.isEmpty(q) ? '' : q; + var qe = { + query: q, + forceAll: forceAll, + combo: this, + cancel:false + }; + if(this.fireEvent('beforequery', qe)===false || qe.cancel){ + return false; + } + q = qe.query; + forceAll = qe.forceAll; + if(forceAll === true || (q.length >= this.minChars)){ + if(this.lastQuery !== q){ + this.lastQuery = q; + if(this.mode == 'local'){ + this.selectedIndex = -1; + if(forceAll){ + this.store.clearFilter(); + }else{ + this.store.filter(this.displayField, q); + } + this.onLoad(); + }else{ + this.store.baseParams[this.queryParam] = q; + this.store.load({ + params: this.getParams(q) + }); + this.expand(); + } + }else{ + this.selectedIndex = -1; + this.onLoad(); + } + } + }, + + // private + getParams : function(q){ + var p = {}; + //p[this.queryParam] = q; + if(this.pageSize){ + p.start = 0; + p.limit = this.pageSize; + } + return p; + }, + + /** + * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion. + */ + collapse : function(){ + if(!this.isExpanded()){ + return; + } + this.list.hide(); + Ext.getDoc().un('mousewheel', this.collapseIf, this); + Ext.getDoc().un('mousedown', this.collapseIf, this); + this.fireEvent('collapse', this); + }, + + // private + collapseIf : function(e){ + if(!e.within(this.wrap) && !e.within(this.list)){ + this.collapse(); + } + }, + + /** + * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion. + */ + expand : function(){ + if(this.isExpanded() || !this.hasFocus){ + return; + } + this.list.alignTo(this.wrap, this.listAlign); + this.list.show(); + if(Ext.isGecko2){ + this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac + } + Ext.getDoc().on({ + scope: this, + mousewheel: this.collapseIf, + mousedown: this.collapseIf + }); + this.fireEvent('expand', this); + }, + + /** + * @method onTriggerClick + * @hide + */ + // private + // Implements the default empty TriggerField.onTriggerClick function + onTriggerClick : function(){ + if(this.disabled){ + return; + } + if(this.isExpanded()){ + this.collapse(); + this.el.focus(); + }else { + this.onFocus({}); + if(this.triggerAction == 'all') { + this.doQuery(this.allQuery, true); + } else { + this.doQuery(this.getRawValue()); + } + this.el.focus(); + } + } + + /** + * @hide + * @method autoSize + */ + /** + * @cfg {Boolean} grow @hide + */ + /** + * @cfg {Number} growMin @hide + */ + /** + * @cfg {Number} growMax @hide + */ + +}); +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. + * @constructor + * Creates a new Checkbox + * @param {Object} config Configuration options + * @xtype checkbox + */ +Ext.form.Checkbox = Ext.extend(Ext.form.Field, { + /** + * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined) + */ + focusClass : undefined, + /** + * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to 'x-form-field') + */ + fieldClass : 'x-form-field', + /** + * @cfg {Boolean} checked true if the checkbox should render initially checked (defaults to false) + */ + checked : false, + /** + * @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 + * handling the check event). The handler is passed the following parameters: + *
+ */ + /** + * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function + * (defaults to this Checkbox). + */ + + // private + actionMode : 'wrap', + + // private + initComponent : function(){ + Ext.form.Checkbox.superclass.initComponent.call(this); + this.addEvents( + /** + * @event check + * Fires when the checkbox is checked or unchecked. + * @param {Ext.form.Checkbox} this This checkbox + * @param {Boolean} checked The new checked value + */ + 'check' + ); + }, + + // private + onResize : function(){ + Ext.form.Checkbox.superclass.onResize.apply(this, arguments); + if(!this.boxLabel && !this.fieldLabel){ + this.el.alignTo(this.wrap, 'c-c'); + } + }, + + // private + initEvents : function(){ + Ext.form.Checkbox.superclass.initEvents.call(this); + this.mon(this.el, 'click', this.onClick, this); + this.mon(this.el, 'change', this.onClick, this); + }, + + // private + getResizeEl : function(){ + return this.wrap; + }, + + // private + getPositionEl : function(){ + return this.wrap; + }, + + /** + * @hide + * Overridden and disabled. The editor element does not support standard valid/invalid marking. + * @method + */ + markInvalid : Ext.emptyFn, + /** + * @hide + * Overridden and disabled. The editor element does not support standard valid/invalid marking. + * @method + */ + clearInvalid : Ext.emptyFn, + + // private + onRender : function(ct, position){ + Ext.form.Checkbox.superclass.onRender.call(this, ct, position); + if(this.inputValue !== undefined){ + this.el.dom.value = this.inputValue; + } + this.wrap = this.el.wrap({cls: 'x-form-check-wrap'}); + if(this.boxLabel){ + this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel}); + } + if(this.checked){ + this.setValue(true); + }else{ + this.checked = this.el.dom.checked; + } + }, + + // private + onDestroy : function(){ + Ext.destroy(this.wrap); + Ext.form.Checkbox.superclass.onDestroy.call(this); + }, + + // private + initValue : function() { + this.originalValue = this.getValue(); + }, + + /** + * Returns the checked state of the checkbox. + * @return {Boolean} True if checked, else false + */ + getValue : function(){ + if(this.rendered){ + return this.el.dom.checked; + } + return false; + }, + + // private + onClick : function(){ + if(this.el.dom.checked != this.checked){ + this.setValue(this.el.dom.checked); + } + }, + + /** + * Sets the checked state of the checkbox, fires the 'check' event, and calls a + * {@link #handler} (if configured). + * @param {Boolean/String} checked The following values will check the checkbox: + * true, 'true', '1', or 'on'. Any other value will uncheck the checkbox. + * @return {Ext.form.Field} this + */ + setValue : function(v){ + var checked = this.checked ; + this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on'); + if(this.rendered){ + this.el.dom.checked = this.checked; + this.el.dom.defaultChecked = this.checked; + } + if(checked != this.checked){ + this.fireEvent('check', this, this.checked); + if(this.handler){ + this.handler.call(this.scope || this, this, this.checked); + } + } + return this; + } +}); +Ext.reg('checkbox', Ext.form.Checkbox); +/** + * @class Ext.form.CheckboxGroup + * @extends Ext.form.Field + *

A grouping container for {@link Ext.form.Checkbox} controls.

+ *

Sample usage:

+ *

+var myCheckboxGroup = new Ext.form.CheckboxGroup({
+    id:'myGroup',
+    xtype: 'checkboxgroup',
+    fieldLabel: 'Single Column',
+    itemCls: 'x-check-group-alt',
+    // Put all controls in a single column with width 100%
+    columns: 1,
+    items: [
+        {boxLabel: 'Item 1', name: 'cb-col-1'},
+        {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
+        {boxLabel: 'Item 3', name: 'cb-col-3'}
+    ]
+});
+ * 
+ * @constructor + * Creates a new CheckboxGroup + * @param {Object} config Configuration options + * @xtype checkboxgroup + */ +Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, { + /** + * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects + * to arrange in the group. + */ + /** + * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped + * checkbox/radio controls using automatic layout. This config can take several types of values: + * + */ + columns : 'auto', + /** + * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column + * top to bottom before starting on the next column. The number of controls in each column will be automatically + * calculated to keep columns as even as possible. The default value is false, so that controls will be added + * to columns one at a time, completely filling each row left to right before starting on the next row. + */ + vertical : false, + /** + * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true). + * If no items are selected at validation time, {@link @blankText} will be used as the error text. + */ + allowBlank : true, + /** + * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must + * select at least one item in this group") + */ + blankText : "You must select at least one item in this group", + + // private + defaultType : 'checkbox', + + // private + groupCls : 'x-form-check-group', + + // private + initComponent: function(){ + this.addEvents( + /** + * @event change + * Fires when the state of a child checkbox changes. + * @param {Ext.form.CheckboxGroup} this + * @param {Array} checked An array containing the checked boxes. + */ + 'change' + ); + Ext.form.CheckboxGroup.superclass.initComponent.call(this); + }, + + // private + onRender : function(ct, position){ + if(!this.el){ + var panelCfg = { + cls: this.groupCls, + layout: 'column', + border: false, + renderTo: ct + }; + var colCfg = { + defaultType: this.defaultType, + layout: 'form', + border: false, + defaults: { + hideLabel: true, + anchor: '100%' + } + }; + + if(this.items[0].items){ + + // The container has standard ColumnLayout configs, so pass them in directly + + Ext.apply(panelCfg, { + layoutConfig: {columns: this.items.length}, + defaults: this.defaults, + items: this.items + }); + for(var i=0, len=this.items.length; i0 && i%rows==0){ + ri++; + } + if(this.items[i].fieldLabel){ + this.items[i].hideLabel = false; + } + cols[ri].items.push(this.items[i]); + }; + }else{ + for(var i=0, len=this.items.length; i