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

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

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

A {@link Ext.DomHelper DomHelper} element spec, or true for a default * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

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

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

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

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

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

+ *

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

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

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

+ *

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

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

*

Validation

- *

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

+ *

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

+ *

Alter Validation Behavior

+ *

Validation behavior for each field can be configured:

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

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

+ *

This function will be passed the following Parameters:

+ *
+ *

This function is to Return:

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

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

+ *
*

See also {@link #mode}.

*/ @@ -2281,8 +2583,9 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ /** * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this - * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'text' if - * {@link #transform transforming a select} a select). + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field1' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). *

See also {@link #valueField}.

*

Note: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not @@ -2290,8 +2593,9 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ /** * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this - * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'value' if - * {@link #transform transforming a select}). + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field2' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). *

Note: use of a valueField requires the user to make a selection in order for a value to be * mapped. See also {@link #hiddenName}, {@link #hiddenValue}, and {@link #displayField}.

*/ @@ -2342,8 +2646,10 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ shadow : 'sides', /** - * @cfg {String} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details - * on supported anchor positions (defaults to 'tl-bl?') + * @cfg {String/Array} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details + * on supported anchor positions and offsets. To specify x/y offsets as well, this value + * may be specified as an Array of {@link Ext.Element#alignTo} method arguments.

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

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

+ *

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

+ *

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


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

Note: If subclassing FormPanel, any configuration options for the BasicForm must be applied to * the initialConfig property of the FormPanel. Applying {@link Ext.form.BasicForm BasicForm} * configuration settings to this will not affect the BasicForm's configuration.

- * + * *

Form Validation

*

For information on form validation see the following:

*
    @@ -4945,20 +5934,20 @@ Ext.BasicForm = Ext.form.BasicForm;/** *
  • {@link Ext.form.BasicForm#doAction BasicForm.doAction clientValidation notes}
  • *
  • {@link Ext.form.FormPanel#monitorValid monitorValid}
  • *
- * + * *

Form Submission

*

By default, Ext Forms are submitted through Ajax, using {@link Ext.form.Action}. To enable normal browser * submission of the {@link Ext.form.BasicForm BasicForm} contained in this FormPanel, see the * {@link Ext.form.BasicForm#standardSubmit standardSubmit} option.

- * + * * @constructor * @param {Object} config Configuration options * @xtype form */ Ext.FormPanel = Ext.extend(Ext.Panel, { - /** - * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id). - */ + /** + * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id). + */ /** * @cfg {Boolean} hideLabels *

true to hide field labels by default (sets display:none). Defaults to @@ -5020,7 +6009,7 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { monitorPoll : 200, /** - * @cfg {String} layout Defaults to 'form'. Normally this configuration property should not be altered. + * @cfg {String} layout Defaults to 'form'. Normally this configuration property should not be altered. * For additional details see {@link Ext.layout.FormLayout} and {@link Ext.Container#layout Ext.Container.layout}. */ layout : 'form', @@ -5040,7 +6029,7 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { this.bodyCfg.enctype = 'multipart/form-data'; } this.initItems(); - + this.addEvents( /** * @event clientvalidation @@ -5067,19 +6056,8 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { var fn = function(c){ if(formPanel.isField(c)){ f.add(c); - }if(c.isFieldWrap){ - Ext.applyIf(c, { - labelAlign: c.ownerCt.labelAlign, - labelWidth: c.ownerCt.labelWidth, - itemCls: c.ownerCt.itemCls - }); - f.add(c.field); - }else if(c.doLayout && c != formPanel){ - Ext.applyIf(c, { - labelAlign: c.ownerCt.labelAlign, - labelWidth: c.ownerCt.labelWidth, - itemCls: c.ownerCt.itemCls - }); + }else if(c.findBy && c != formPanel){ + formPanel.applySettings(c); //each check required for check/radio groups. if(c.items && c.items.each){ c.items.each(fn, this); @@ -5089,6 +6067,16 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { this.items.each(fn, this); }, + // private + applySettings: function(c){ + var ct = c.ownerCt; + Ext.applyIf(c, { + labelAlign: ct.labelAlign, + labelWidth: ct.labelWidth, + itemCls: ct.itemCls + }); + }, + // private getLayoutTarget : function(){ return this.form.el; @@ -5108,21 +6096,15 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { Ext.FormPanel.superclass.onRender.call(this, ct, position); this.form.initEl(this.body); }, - + // private beforeDestroy : function(){ this.stopMonitoring(); + this.form.destroy(true); Ext.FormPanel.superclass.beforeDestroy.call(this); - /* - * Clear the items here to prevent them being destroyed again. - * Don't move this behaviour to BasicForm because it can be used - * on it's own. - */ - this.form.items.clear(); - Ext.destroy(this.form); }, - // Determine if a Component is usable as a form Field. + // Determine if a Component is usable as a form Field. isField : function(c) { return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid; }, @@ -5130,1730 +6112,2055 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { // private initEvents : function(){ Ext.FormPanel.superclass.initEvents.call(this); - this.on('remove', this.onRemove, this); - this.on('add', this.onAdd, this); + // Listeners are required here to catch bubbling events from children. + this.on({ + scope: this, + add: this.onAddEvent, + remove: this.onRemoveEvent + }); if(this.monitorValid){ // initialize after render this.startMonitoring(); } }, - + + // private + onAdd: function(c){ + Ext.FormPanel.superclass.onAdd.call(this, c); + this.processAdd(c); + }, + + // private + onAddEvent: function(ct, c){ + if(ct !== this){ + this.processAdd(c); + } + }, + // private - onAdd : function(ct, c) { - // If a single form Field, add it - if (this.isField(c)) { + processAdd : function(c){ + // If a single form Field, add it + if(this.isField(c)){ this.form.add(c); - // If a Container, add any Fields it might contain - } else if (c.findBy) { - Ext.applyIf(c, { - labelAlign: c.ownerCt.labelAlign, - labelWidth: c.ownerCt.labelWidth, - itemCls: c.ownerCt.itemCls - }); + // If a Container, add any Fields it might contain + }else if(c.findBy){ + this.applySettings(c); this.form.add.apply(this.form, c.findBy(this.isField)); } }, - + + // private + onRemove: function(c){ + Ext.FormPanel.superclass.onRemove.call(this, c); + this.processRemove(c); + }, + + onRemoveEvent: function(ct, c){ + if(ct !== this){ + this.processRemove(c); + } + }, + + // private + processRemove: function(c){ + if(!this.destroying){ + // If a single form Field, remove it + if(this.isField(c)){ + this.form.remove(c); + // If a Container, its already destroyed by the time it gets here. Remove any references to destroyed fields. + }else if (c.findBy){ + Ext.each(c.findBy(this.isField), this.form.remove, this.form); + if (c.isDestroyed) { + this.form.cleanDestroyed(); + } + } + } + }, + + /** + * Starts monitoring of the valid state of this form. Usually this is done by passing the config + * option "monitorValid" + */ + startMonitoring : function(){ + if(!this.validTask){ + this.validTask = new Ext.util.TaskRunner(); + this.validTask.start({ + run : this.bindHandler, + interval : this.monitorPoll || 200, + scope: this + }); + } + }, + + /** + * Stops monitoring of the valid state of this form + */ + stopMonitoring : function(){ + if(this.validTask){ + this.validTask.stopAll(); + this.validTask = null; + } + }, + + /** + * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call. + * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details) + */ + load : function(){ + this.form.load.apply(this.form, arguments); + }, + + // private + onDisable : function(){ + Ext.FormPanel.superclass.onDisable.call(this); + if(this.form){ + this.form.items.each(function(){ + this.disable(); + }); + } + }, + + // private + onEnable : function(){ + Ext.FormPanel.superclass.onEnable.call(this); + if(this.form){ + this.form.items.each(function(){ + this.enable(); + }); + } + }, + + // private + bindHandler : function(){ + var valid = true; + this.form.items.each(function(f){ + if(!f.isValid(true)){ + valid = false; + return false; + } + }); + if(this.fbar){ + var fitems = this.fbar.items.items; + for(var i = 0, len = fitems.length; i < len; i++){ + var btn = fitems[i]; + if(btn.formBind === true && btn.disabled === valid){ + btn.setDisabled(!valid); + } + } + } + this.fireEvent('clientvalidation', this, valid); + } +}); +Ext.reg('form', Ext.FormPanel); + +Ext.form.FormPanel = Ext.FormPanel; +/** + * @class Ext.form.FieldSet + * @extends Ext.Panel + * Standard container used for grouping items within a {@link Ext.form.FormPanel form}. + *


+var form = new Ext.FormPanel({
+    title: 'Simple Form with FieldSets',
+    labelWidth: 75, // label settings here cascade unless overridden
+    url: 'save-form.php',
+    frame:true,
+    bodyStyle:'padding:5px 5px 0',
+    width: 700,
+    renderTo: document.body,
+    layout:'column', // arrange items in columns
+    defaults: {      // defaults applied to items
+        layout: 'form',
+        border: false,
+        bodyStyle: 'padding:4px'
+    },
+    items: [{
+        // Fieldset in Column 1
+        xtype:'fieldset',
+        columnWidth: 0.5,
+        title: 'Fieldset 1',
+        collapsible: true,
+        autoHeight:true,
+        defaults: {
+            anchor: '-20' // leave room for error icon
+        },
+        defaultType: 'textfield',
+        items :[{
+                fieldLabel: 'Field 1'
+            }, {
+                fieldLabel: 'Field 2'
+            }, {
+                fieldLabel: 'Field 3'
+            }
+        ]
+    },{
+        // Fieldset in Column 2 - Panel inside
+        xtype:'fieldset',
+        title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
+        autoHeight:true,
+        columnWidth: 0.5,
+        checkboxToggle: true,
+        collapsed: true, // fieldset initially collapsed
+        layout:'anchor',
+        items :[{
+            xtype: 'panel',
+            anchor: '100%',
+            title: 'Panel inside a fieldset',
+            frame: true,
+            height: 100
+        }]
+    }]
+});
+ * 
+ * @constructor + * @param {Object} config Configuration options + * @xtype fieldset + */ +Ext.form.FieldSet = Ext.extend(Ext.Panel, { + /** + * @cfg {Mixed} checkboxToggle true to render a checkbox into the fieldset frame just + * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults + * to false). + *

A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox. + * If true is specified, the default DomHelper config object used to create the element + * is:


+     * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
+     * 
+ */ + /** + * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true + * (defaults to '[checkbox id]-checkbox'). + */ + /** + * @cfg {Boolean} collapsible + * true to make the fieldset collapsible and have the expand/collapse toggle button automatically + * rendered into the legend element, false to keep the fieldset statically sized with no collapse + * button (defaults to false). Another option is to configure {@link #checkboxToggle}. + */ + /** + * @cfg {Number} labelWidth The width of labels. This property cascades to child containers. + */ + /** + * @cfg {String} itemCls A css class to apply to the x-form-item of fields (see + * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details). + * This property cascades to child containers. + */ + /** + * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to 'x-fieldset'). + */ + baseCls : 'x-fieldset', + /** + * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to 'form'). + */ + layout : 'form', + /** + * @cfg {Boolean} animCollapse + * true to animate the transition when the panel is collapsed, false to skip the + * animation (defaults to false). + */ + animCollapse : false, + + // private + onRender : function(ct, position){ + if(!this.el){ + this.el = document.createElement('fieldset'); + this.el.id = this.id; + if (this.title || this.header || this.checkboxToggle) { + this.el.appendChild(document.createElement('legend')).className = this.baseCls + '-header'; + } + } + + Ext.form.FieldSet.superclass.onRender.call(this, ct, position); + + if(this.checkboxToggle){ + var o = typeof this.checkboxToggle == 'object' ? + this.checkboxToggle : + {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}; + this.checkbox = this.header.insertFirst(o); + this.checkbox.dom.checked = !this.collapsed; + this.mon(this.checkbox, 'click', this.onCheckClick, this); + } + }, + + // private + onCollapse : function(doAnim, animArg){ + if(this.checkbox){ + this.checkbox.dom.checked = false; + } + Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg); + + }, + + // private + onExpand : function(doAnim, animArg){ + if(this.checkbox){ + this.checkbox.dom.checked = true; + } + Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg); + }, + + /** + * This function is called by the fieldset's checkbox when it is toggled (only applies when + * checkboxToggle = true). This method should never be called externally, but can be + * overridden to provide custom behavior when the checkbox is toggled if needed. + */ + onCheckClick : function(){ + this[this.checkbox.dom.checked ? 'expand' : 'collapse'](); + } + + /** + * @cfg {String/Number} activeItem + * @hide + */ + /** + * @cfg {Mixed} applyTo + * @hide + */ + /** + * @cfg {Boolean} bodyBorder + * @hide + */ + /** + * @cfg {Boolean} border + * @hide + */ + /** + * @cfg {Boolean/Number} bufferResize + * @hide + */ + /** + * @cfg {Boolean} collapseFirst + * @hide + */ + /** + * @cfg {String} defaultType + * @hide + */ + /** + * @cfg {String} disabledClass + * @hide + */ + /** + * @cfg {String} elements + * @hide + */ + /** + * @cfg {Boolean} floating + * @hide + */ + /** + * @cfg {Boolean} footer + * @hide + */ + /** + * @cfg {Boolean} frame + * @hide + */ + /** + * @cfg {Boolean} header + * @hide + */ + /** + * @cfg {Boolean} headerAsText + * @hide + */ + /** + * @cfg {Boolean} hideCollapseTool + * @hide + */ + /** + * @cfg {String} iconCls + * @hide + */ + /** + * @cfg {Boolean/String} shadow + * @hide + */ + /** + * @cfg {Number} shadowOffset + * @hide + */ + /** + * @cfg {Boolean} shim + * @hide + */ + /** + * @cfg {Object/Array} tbar + * @hide + */ + /** + * @cfg {Array} tools + * @hide + */ + /** + * @cfg {Ext.Template/Ext.XTemplate} toolTemplate + * @hide + */ + /** + * @cfg {String} xtype + * @hide + */ + /** + * @property header + * @hide + */ + /** + * @property footer + * @hide + */ + /** + * @method focus + * @hide + */ + /** + * @method getBottomToolbar + * @hide + */ + /** + * @method getTopToolbar + * @hide + */ + /** + * @method setIconClass + * @hide + */ + /** + * @event activate + * @hide + */ + /** + * @event beforeclose + * @hide + */ + /** + * @event bodyresize + * @hide + */ + /** + * @event close + * @hide + */ + /** + * @event deactivate + * @hide + */ +}); +Ext.reg('fieldset', Ext.form.FieldSet);/** + * @class Ext.form.HtmlEditor + * @extends Ext.form.Field + * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be + * automatically hidden when needed. These are noted in the config options where appropriate. + *

The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not + * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}. + *

Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT + * supported by this editor. + *

An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within + * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs. + *

Example usage: + *

+// Simple example rendered with default options:
+Ext.QuickTips.init();  // enable tooltips
+new Ext.form.HtmlEditor({
+    renderTo: Ext.getBody(),
+    width: 800,
+    height: 300
+});
+
+// Passed via xtype into a container and with custom options:
+Ext.QuickTips.init();  // enable tooltips
+new Ext.Panel({
+    title: 'HTML Editor',
+    renderTo: Ext.getBody(),
+    width: 600,
+    height: 300,
+    frame: true,
+    layout: 'fit',
+    items: {
+        xtype: 'htmleditor',
+        enableColors: false,
+        enableAlignments: false
+    }
+});
+
+ * @constructor + * Create a new HtmlEditor + * @param {Object} config + * @xtype htmleditor + */ + +Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, { + /** + * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true) + */ + enableFormat : true, + /** + * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true) + */ + enableFontSize : true, + /** + * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true) + */ + enableColors : true, + /** + * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true) + */ + enableAlignments : true, + /** + * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true) + */ + enableLists : true, + /** + * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true) + */ + enableSourceEdit : true, + /** + * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true) + */ + enableLinks : true, + /** + * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true) + */ + enableFont : true, + /** + * @cfg {String} createLinkText The default text for the create link prompt + */ + createLinkText : 'Please enter the URL for the link:', + /** + * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /) + */ + defaultLinkValue : 'http:/'+'/', + /** + * @cfg {Array} fontFamilies An array of available font families + */ + fontFamilies : [ + 'Arial', + 'Courier New', + 'Tahoma', + 'Times New Roman', + 'Verdana' + ], + defaultFont: 'tahoma', + /** + * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to   (Non-breaking space) in Opera and IE6, ​ (Zero-width space) in all other browsers). + */ + defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '​', + + // private properties + actionMode: 'wrap', + validationEvent : false, + deferHeight: true, + initialized : false, + activated : false, + sourceEditMode : false, + onFocus : Ext.emptyFn, + iframePad:3, + hideMode:'offsets', + defaultAutoCreate : { + tag: "textarea", + style:"width:500px;height:300px;", + autocomplete: "off" + }, + + // private + initComponent : function(){ + this.addEvents( + /** + * @event initialize + * Fires when the editor is fully initialized (including the iframe) + * @param {HtmlEditor} this + */ + 'initialize', + /** + * @event activate + * Fires when the editor is first receives the focus. Any insertion must wait + * until after this event. + * @param {HtmlEditor} this + */ + 'activate', + /** + * @event beforesync + * Fires before the textarea is updated with content from the editor iframe. Return false + * to cancel the sync. + * @param {HtmlEditor} this + * @param {String} html + */ + 'beforesync', + /** + * @event beforepush + * Fires before the iframe editor is updated with content from the textarea. Return false + * to cancel the push. + * @param {HtmlEditor} this + * @param {String} html + */ + 'beforepush', + /** + * @event sync + * Fires when the textarea is updated with content from the editor iframe. + * @param {HtmlEditor} this + * @param {String} html + */ + 'sync', + /** + * @event push + * Fires when the iframe editor is updated with content from the textarea. + * @param {HtmlEditor} this + * @param {String} html + */ + 'push', + /** + * @event editmodechange + * Fires when the editor switches edit modes + * @param {HtmlEditor} this + * @param {Boolean} sourceEdit True if source edit, false if standard editing. + */ + 'editmodechange' + ); + }, + + // private + createFontOptions : function(){ + var buf = [], fs = this.fontFamilies, ff, lc; + for(var i = 0, len = fs.length; i< len; i++){ + ff = fs[i]; + lc = ff.toLowerCase(); + buf.push( + '' + ); + } + return buf.join(''); + }, + + /* + * Protected method that will not generally be called directly. It + * is called when the editor creates its toolbar. Override this method if you need to + * add custom toolbar buttons. + * @param {HtmlEditor} editor + */ + createToolbar : function(editor){ + var items = []; + var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled(); + + + function btn(id, toggle, handler){ + return { + itemId : id, + cls : 'x-btn-icon', + iconCls: 'x-edit-'+id, + enableToggle:toggle !== false, + scope: editor, + handler:handler||editor.relayBtnCmd, + clickEvent:'mousedown', + tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined, + overflowText: editor.buttonTips[id].title || undefined, + tabIndex:-1 + }; + } + + + if(this.enableFont && !Ext.isSafari2){ + var fontSelectItem = new Ext.Toolbar.Item({ + autoEl: { + tag:'select', + cls:'x-font-select', + html: this.createFontOptions() + } + }); + + items.push( + fontSelectItem, + '-' + ); + } + + if(this.enableFormat){ + items.push( + btn('bold'), + btn('italic'), + btn('underline') + ); + } + + if(this.enableFontSize){ + items.push( + '-', + btn('increasefontsize', false, this.adjustFont), + btn('decreasefontsize', false, this.adjustFont) + ); + } + + if(this.enableColors){ + items.push( + '-', { + itemId:'forecolor', + cls:'x-btn-icon', + iconCls: 'x-edit-forecolor', + clickEvent:'mousedown', + tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined, + tabIndex:-1, + menu : new Ext.menu.ColorMenu({ + allowReselect: true, + focus: Ext.emptyFn, + value:'000000', + plain:true, + listeners: { + scope: this, + select: function(cp, color){ + this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color); + this.deferFocus(); + } + }, + clickEvent:'mousedown' + }) + }, { + itemId:'backcolor', + cls:'x-btn-icon', + iconCls: 'x-edit-backcolor', + clickEvent:'mousedown', + tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined, + tabIndex:-1, + menu : new Ext.menu.ColorMenu({ + focus: Ext.emptyFn, + value:'FFFFFF', + plain:true, + allowReselect: true, + listeners: { + scope: this, + select: function(cp, color){ + if(Ext.isGecko){ + this.execCmd('useCSS', false); + this.execCmd('hilitecolor', color); + this.execCmd('useCSS', true); + this.deferFocus(); + }else{ + this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color); + this.deferFocus(); + } + } + }, + clickEvent:'mousedown' + }) + } + ); + } + + if(this.enableAlignments){ + items.push( + '-', + btn('justifyleft'), + btn('justifycenter'), + btn('justifyright') + ); + } + + if(!Ext.isSafari2){ + if(this.enableLinks){ + items.push( + '-', + btn('createlink', false, this.createLink) + ); + } + + if(this.enableLists){ + items.push( + '-', + btn('insertorderedlist'), + btn('insertunorderedlist') + ); + } + if(this.enableSourceEdit){ + items.push( + '-', + btn('sourceedit', true, function(btn){ + this.toggleSourceEdit(!this.sourceEditMode); + }) + ); + } + } + + // build the toolbar + var tb = new Ext.Toolbar({ + renderTo: this.wrap.dom.firstChild, + items: items + }); + + if (fontSelectItem) { + this.fontSelect = fontSelectItem.el; + + this.mon(this.fontSelect, 'change', function(){ + var font = this.fontSelect.dom.value; + this.relayCmd('fontname', font); + this.deferFocus(); + }, this); + } + + // stop form submits + this.mon(tb.el, 'click', function(e){ + e.preventDefault(); + }); + + this.tb = tb; + this.tb.doLayout(); + }, + + onDisable: function(){ + this.wrap.mask(); + Ext.form.HtmlEditor.superclass.onDisable.call(this); + }, + + onEnable: function(){ + this.wrap.unmask(); + Ext.form.HtmlEditor.superclass.onEnable.call(this); + }, + + setReadOnly: function(readOnly){ + + Ext.form.HtmlEditor.superclass.setReadOnly.call(this, readOnly); + if(this.initialized){ + if(Ext.isIE){ + this.getEditorBody().contentEditable = !readOnly; + }else{ + this.setDesignMode(!readOnly); + } + var bd = this.getEditorBody(); + if(bd){ + bd.style.cursor = this.readOnly ? 'default' : 'text'; + } + this.disableItems(readOnly); + } + }, + + /** + * Protected method that will not generally be called directly. It + * is called when the editor initializes the iframe with HTML contents. Override this method if you + * want to change the initialization markup of the iframe (e.g. to add stylesheets). + * + * Note: IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility + */ + getDocMarkup : function(){ + var h = Ext.fly(this.iframe).getHeight() - this.iframePad * 2; + return String.format('', this.iframePad, h); + }, + + // private + getEditorBody : function(){ + var doc = this.getDoc(); + return doc.body || doc.documentElement; + }, + + // private + getDoc : function(){ + return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document); + }, + + // private + getWin : function(){ + return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name]; + }, + + // private + onRender : function(ct, position){ + Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position); + this.el.dom.style.border = '0 none'; + this.el.dom.setAttribute('tabIndex', -1); + this.el.addClass('x-hidden'); + if(Ext.isIE){ // fix IE 1px bogus margin + this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;'); + } + this.wrap = this.el.wrap({ + cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'} + }); + + this.createToolbar(this); + + this.disableItems(true); + + this.tb.doLayout(); + + this.createIFrame(); + + if(!this.width){ + var sz = this.el.getSize(); + this.setSize(sz.width, this.height || sz.height); + } + this.resizeEl = this.positionEl = this.wrap; + }, + + createIFrame: function(){ + var iframe = document.createElement('iframe'); + iframe.name = Ext.id(); + iframe.frameBorder = '0'; + iframe.style.overflow = 'auto'; + + this.wrap.dom.appendChild(iframe); + this.iframe = iframe; + + this.monitorTask = Ext.TaskMgr.start({ + run: this.checkDesignMode, + scope: this, + interval:100 + }); + }, + + initFrame : function(){ + Ext.TaskMgr.stop(this.monitorTask); + var doc = this.getDoc(); + this.win = this.getWin(); + + doc.open(); + doc.write(this.getDocMarkup()); + doc.close(); + + var task = { // must defer to wait for browser to be ready + run : function(){ + var doc = this.getDoc(); + if(doc.body || doc.readyState == 'complete'){ + Ext.TaskMgr.stop(task); + this.setDesignMode(true); + this.initEditor.defer(10, this); + } + }, + interval : 10, + duration:10000, + scope: this + }; + Ext.TaskMgr.start(task); + }, + + + checkDesignMode : function(){ + if(this.wrap && this.wrap.dom.offsetWidth){ + var doc = this.getDoc(); + if(!doc){ + return; + } + if(!doc.editorInitialized || this.getDesignMode() != 'on'){ + this.initFrame(); + } + } + }, + + /* private + * set current design mode. To enable, mode can be true or 'on', off otherwise + */ + setDesignMode : function(mode){ + var doc ; + if(doc = this.getDoc()){ + if(this.readOnly){ + mode = false; + } + doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off'; + } + + }, + + // private + getDesignMode : function(){ + var doc = this.getDoc(); + if(!doc){ return ''; } + return String(doc.designMode).toLowerCase(); + + }, + + disableItems: function(disabled){ + if(this.fontSelect){ + this.fontSelect.dom.disabled = disabled; + } + this.tb.items.each(function(item){ + if(item.getItemId() != 'sourceedit'){ + item.setDisabled(disabled); + } + }); + }, + + // private + onResize : function(w, h){ + Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments); + if(this.el && this.iframe){ + if(Ext.isNumber(w)){ + var aw = w - this.wrap.getFrameWidth('lr'); + this.el.setWidth(aw); + this.tb.setWidth(aw); + this.iframe.style.width = Math.max(aw, 0) + 'px'; + } + if(Ext.isNumber(h)){ + var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight(); + this.el.setHeight(ah); + this.iframe.style.height = Math.max(ah, 0) + 'px'; + var bd = this.getEditorBody(); + if(bd){ + bd.style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px'; + } + } + } + }, + + /** + * Toggles the editor between standard and source edit mode. + * @param {Boolean} sourceEdit (optional) True for source edit, false for standard + */ + toggleSourceEdit : function(sourceEditMode){ + var iframeHeight, + elHeight, + ls; + + if (sourceEditMode === undefined) { + sourceEditMode = !this.sourceEditMode; + } + this.sourceEditMode = sourceEditMode === true; + var btn = this.tb.getComponent('sourceedit'); + + if (btn.pressed !== this.sourceEditMode) { + btn.toggle(this.sourceEditMode); + if (!btn.xtbHidden) { + return; + } + } + if (this.sourceEditMode) { + // grab the height of the containing panel before we hide the iframe + ls = this.getSize(); + + iframeHeight = Ext.get(this.iframe).getHeight(); + + this.disableItems(true); + this.syncValue(); + this.iframe.className = 'x-hidden'; + this.el.removeClass('x-hidden'); + this.el.dom.removeAttribute('tabIndex'); + this.el.focus(); + this.el.dom.style.height = iframeHeight + 'px'; + } + else { + elHeight = parseInt(this.el.dom.style.height, 10); + if (this.initialized) { + this.disableItems(this.readOnly); + } + this.pushValue(); + this.iframe.className = ''; + this.el.addClass('x-hidden'); + this.el.dom.setAttribute('tabIndex', -1); + this.deferFocus(); + + this.setSize(ls); + this.iframe.style.height = elHeight + 'px'; + } + this.fireEvent('editmodechange', this, this.sourceEditMode); + }, + + // private used internally + createLink : function() { + var url = prompt(this.createLinkText, this.defaultLinkValue); + if(url && url != 'http:/'+'/'){ + this.relayCmd('createlink', url); + } + }, + + // private + initEvents : function(){ + this.originalValue = this.getValue(); + }, + + /** + * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide + * @method + */ + markInvalid : Ext.emptyFn, + + /** + * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide + * @method + */ + clearInvalid : Ext.emptyFn, + + // docs inherit from Field + setValue : function(v){ + Ext.form.HtmlEditor.superclass.setValue.call(this, v); + this.pushValue(); + return this; + }, + + /** + * Protected method that will not generally be called directly. If you need/want + * custom HTML cleanup, this is the method you should override. + * @param {String} html The HTML to be cleaned + * @return {String} The cleaned HTML + */ + cleanHtml: function(html) { + html = String(html); + if(Ext.isWebKit){ // strip safari nonsense + html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, ''); + } + + /* + * Neat little hack. Strips out all the non-digit characters from the default + * value and compares it to the character code of the first character in the string + * because it can cause encoding issues when posted to the server. + */ + if(html.charCodeAt(0) == this.defaultValue.replace(/\D/g, '')){ + html = html.substring(1); + } + return html; + }, + + /** + * Protected method that will not generally be called directly. Syncs the contents + * of the editor iframe with the textarea. + */ + syncValue : function(){ + if(this.initialized){ + var bd = this.getEditorBody(); + var html = bd.innerHTML; + if(Ext.isWebKit){ + var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element! + var m = bs.match(/text-align:(.*?);/i); + if(m && m[1]){ + html = '
' + html + '
'; + } + } + html = this.cleanHtml(html); + if(this.fireEvent('beforesync', this, html) !== false){ + this.el.dom.value = html; + this.fireEvent('sync', this, html); + } + } + }, + + //docs inherit from Field + getValue : function() { + this[this.sourceEditMode ? 'pushValue' : 'syncValue'](); + return Ext.form.HtmlEditor.superclass.getValue.call(this); + }, + + /** + * Protected method that will not generally be called directly. Pushes the value of the textarea + * into the iframe editor. + */ + pushValue : function(){ + if(this.initialized){ + var v = this.el.dom.value; + if(!this.activated && v.length < 1){ + v = this.defaultValue; + } + if(this.fireEvent('beforepush', this, v) !== false){ + this.getEditorBody().innerHTML = v; + if(Ext.isGecko){ + // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8 + this.setDesignMode(false); //toggle off first + this.setDesignMode(true); + } + this.fireEvent('push', this, v); + } + + } + }, + + // private + deferFocus : function(){ + this.focus.defer(10, this); + }, + + // docs inherit from Field + focus : function(){ + if(this.win && !this.sourceEditMode){ + this.win.focus(); + }else{ + this.el.focus(); + } + }, + + // private + initEditor : function(){ + //Destroying the component during/before initEditor can cause issues. + try{ + var dbody = this.getEditorBody(), + ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'), + doc, + fn; + + ss['background-attachment'] = 'fixed'; // w3c + dbody.bgProperties = 'fixed'; // ie + + Ext.DomHelper.applyStyles(dbody, ss); + + doc = this.getDoc(); + + if(doc){ + try{ + Ext.EventManager.removeAll(doc); + }catch(e){} + } + + /* + * We need to use createDelegate here, because when using buffer, the delayed task is added + * as a property to the function. When the listener is removed, the task is deleted from the function. + * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors + * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function. + */ + fn = this.onEditorEvent.createDelegate(this); + Ext.EventManager.on(doc, { + mousedown: fn, + dblclick: fn, + click: fn, + keyup: fn, + buffer:100 + }); + + if(Ext.isGecko){ + Ext.EventManager.on(doc, 'keypress', this.applyCommand, this); + } + if(Ext.isIE || Ext.isWebKit || Ext.isOpera){ + Ext.EventManager.on(doc, 'keydown', this.fixKeys, this); + } + doc.editorInitialized = true; + this.initialized = true; + this.pushValue(); + this.setReadOnly(this.readOnly); + this.fireEvent('initialize', this); + }catch(e){} + }, + + // private + onDestroy : function(){ + if(this.monitorTask){ + Ext.TaskMgr.stop(this.monitorTask); + } + if(this.rendered){ + Ext.destroy(this.tb); + var doc = this.getDoc(); + if(doc){ + try{ + Ext.EventManager.removeAll(doc); + for (var prop in doc){ + delete doc[prop]; + } + }catch(e){} + } + if(this.wrap){ + this.wrap.dom.innerHTML = ''; + this.wrap.remove(); + } + } + + if(this.el){ + this.el.removeAllListeners(); + this.el.remove(); + } + this.purgeListeners(); + }, + + // private + onFirstFocus : function(){ + this.activated = true; + this.disableItems(this.readOnly); + if(Ext.isGecko){ // prevent silly gecko errors + this.win.focus(); + var s = this.win.getSelection(); + if(!s.focusNode || s.focusNode.nodeType != 3){ + var r = s.getRangeAt(0); + r.selectNodeContents(this.getEditorBody()); + r.collapse(true); + this.deferFocus(); + } + try{ + this.execCmd('useCSS', true); + this.execCmd('styleWithCSS', false); + }catch(e){} + } + this.fireEvent('activate', this); + }, + + // private + adjustFont: function(btn){ + var adjust = btn.getItemId() == 'increasefontsize' ? 1 : -1, + doc = this.getDoc(), + v = parseInt(doc.queryCommandValue('FontSize') || 2, 10); + if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){ + // Safari 3 values + // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px + if(v <= 10){ + v = 1 + adjust; + }else if(v <= 13){ + v = 2 + adjust; + }else if(v <= 16){ + v = 3 + adjust; + }else if(v <= 18){ + v = 4 + adjust; + }else if(v <= 24){ + v = 5 + adjust; + }else { + v = 6 + adjust; + } + v = v.constrain(1, 6); + }else{ + if(Ext.isSafari){ // safari + adjust *= 2; + } + v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0); + } + this.execCmd('FontSize', v); + }, + + // private + onEditorEvent : function(e){ + this.updateToolbar(); + }, + + + /** + * Protected method that will not generally be called directly. It triggers + * a toolbar update by reading the markup state of the current selection in the editor. + */ + updateToolbar: function(){ + + if(this.readOnly){ + return; + } + + if(!this.activated){ + this.onFirstFocus(); + return; + } + + var btns = this.tb.items.map, + doc = this.getDoc(); + + if(this.enableFont && !Ext.isSafari2){ + var name = (doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase(); + if(name != this.fontSelect.dom.value){ + this.fontSelect.dom.value = name; + } + } + if(this.enableFormat){ + btns.bold.toggle(doc.queryCommandState('bold')); + btns.italic.toggle(doc.queryCommandState('italic')); + btns.underline.toggle(doc.queryCommandState('underline')); + } + if(this.enableAlignments){ + btns.justifyleft.toggle(doc.queryCommandState('justifyleft')); + btns.justifycenter.toggle(doc.queryCommandState('justifycenter')); + btns.justifyright.toggle(doc.queryCommandState('justifyright')); + } + if(!Ext.isSafari2 && this.enableLists){ + btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist')); + btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist')); + } + + Ext.menu.MenuMgr.hideAll(); + + this.syncValue(); + }, + + // private + relayBtnCmd : function(btn){ + this.relayCmd(btn.getItemId()); + }, + + /** + * Executes a Midas editor command on the editor document and performs necessary focus and + * toolbar updates. This should only be called after the editor is initialized. + * @param {String} cmd The Midas command + * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null) + */ + relayCmd : function(cmd, value){ + (function(){ + this.focus(); + this.execCmd(cmd, value); + this.updateToolbar(); + }).defer(10, this); + }, + + /** + * Executes a Midas editor command directly on the editor document. + * For visual commands, you should use {@link #relayCmd} instead. + * This should only be called after the editor is initialized. + * @param {String} cmd The Midas command + * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null) + */ + execCmd : function(cmd, value){ + var doc = this.getDoc(); + doc.execCommand(cmd, false, value === undefined ? null : value); + this.syncValue(); + }, + + // private + applyCommand : function(e){ + if(e.ctrlKey){ + var c = e.getCharCode(), cmd; + if(c > 0){ + c = String.fromCharCode(c); + switch(c){ + case 'b': + cmd = 'bold'; + break; + case 'i': + cmd = 'italic'; + break; + case 'u': + cmd = 'underline'; + break; + } + if(cmd){ + this.win.focus(); + this.execCmd(cmd); + this.deferFocus(); + e.preventDefault(); + } + } + } + }, + + /** + * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated + * to insert text. + * @param {String} text + */ + insertAtCursor : function(text){ + if(!this.activated){ + return; + } + if(Ext.isIE){ + this.win.focus(); + var doc = this.getDoc(), + r = doc.selection.createRange(); + if(r){ + r.pasteHTML(text); + this.syncValue(); + this.deferFocus(); + } + }else{ + this.win.focus(); + this.execCmd('InsertHTML', text); + this.deferFocus(); + } + }, + + // private + fixKeys : function(){ // load time branching for fastest keydown performance + if(Ext.isIE){ + return function(e){ + var k = e.getKey(), + doc = this.getDoc(), + r; + if(k == e.TAB){ + e.stopEvent(); + r = doc.selection.createRange(); + if(r){ + r.collapse(true); + r.pasteHTML('    '); + this.deferFocus(); + } + }else if(k == e.ENTER){ + r = doc.selection.createRange(); + if(r){ + var target = r.parentElement(); + if(!target || target.tagName.toLowerCase() != 'li'){ + e.stopEvent(); + r.pasteHTML('
'); + r.collapse(false); + r.select(); + } + } + } + }; + }else if(Ext.isOpera){ + return function(e){ + var k = e.getKey(); + if(k == e.TAB){ + e.stopEvent(); + this.win.focus(); + this.execCmd('InsertHTML','    '); + this.deferFocus(); + } + }; + }else if(Ext.isWebKit){ + return function(e){ + var k = e.getKey(); + if(k == e.TAB){ + e.stopEvent(); + this.execCmd('InsertText','\t'); + this.deferFocus(); + }else if(k == e.ENTER){ + e.stopEvent(); + this.execCmd('InsertHtml','

'); + this.deferFocus(); + } + }; + } + }(), + + /** + * Returns the editor's toolbar. This is only available after the editor has been rendered. + * @return {Ext.Toolbar} + */ + getToolbar : function(){ + return this.tb; + }, + + /** + * Object collection of toolbar tooltips for the buttons in the editor. The key + * is the command id associated with that button and the value is a valid QuickTips object. + * For example: +

+{
+    bold : {
+        title: 'Bold (Ctrl+B)',
+        text: 'Make the selected text bold.',
+        cls: 'x-html-editor-tip'
+    },
+    italic : {
+        title: 'Italic (Ctrl+I)',
+        text: 'Make the selected text italic.',
+        cls: 'x-html-editor-tip'
+    },
+    ...
+
+ * @type Object + */ + buttonTips : { + bold : { + title: 'Bold (Ctrl+B)', + text: 'Make the selected text bold.', + cls: 'x-html-editor-tip' + }, + italic : { + title: 'Italic (Ctrl+I)', + text: 'Make the selected text italic.', + cls: 'x-html-editor-tip' + }, + underline : { + title: 'Underline (Ctrl+U)', + text: 'Underline the selected text.', + cls: 'x-html-editor-tip' + }, + increasefontsize : { + title: 'Grow Text', + text: 'Increase the font size.', + cls: 'x-html-editor-tip' + }, + decreasefontsize : { + title: 'Shrink Text', + text: 'Decrease the font size.', + cls: 'x-html-editor-tip' + }, + backcolor : { + title: 'Text Highlight Color', + text: 'Change the background color of the selected text.', + cls: 'x-html-editor-tip' + }, + forecolor : { + title: 'Font Color', + text: 'Change the color of the selected text.', + cls: 'x-html-editor-tip' + }, + justifyleft : { + title: 'Align Text Left', + text: 'Align text to the left.', + cls: 'x-html-editor-tip' + }, + justifycenter : { + title: 'Center Text', + text: 'Center text in the editor.', + cls: 'x-html-editor-tip' + }, + justifyright : { + title: 'Align Text Right', + text: 'Align text to the right.', + cls: 'x-html-editor-tip' + }, + insertunorderedlist : { + title: 'Bullet List', + text: 'Start a bulleted list.', + cls: 'x-html-editor-tip' + }, + insertorderedlist : { + title: 'Numbered List', + text: 'Start a numbered list.', + cls: 'x-html-editor-tip' + }, + createlink : { + title: 'Hyperlink', + text: 'Make the selected text a hyperlink.', + cls: 'x-html-editor-tip' + }, + sourceedit : { + title: 'Source Edit', + text: 'Switch to source editing mode.', + cls: 'x-html-editor-tip' + } + } + + // hide stuff that is not compatible + /** + * @event blur + * @hide + */ + /** + * @event change + * @hide + */ + /** + * @event focus + * @hide + */ + /** + * @event specialkey + * @hide + */ + /** + * @cfg {String} fieldClass @hide + */ + /** + * @cfg {String} focusClass @hide + */ + /** + * @cfg {String} autoCreate @hide + */ + /** + * @cfg {String} inputType @hide + */ + /** + * @cfg {String} invalidClass @hide + */ + /** + * @cfg {String} invalidText @hide + */ + /** + * @cfg {String} msgFx @hide + */ + /** + * @cfg {String} validateOnBlur @hide + */ + /** + * @cfg {Boolean} allowDomMove @hide + */ + /** + * @cfg {String} applyTo @hide + */ + /** + * @cfg {String} autoHeight @hide + */ + /** + * @cfg {String} autoWidth @hide + */ + /** + * @cfg {String} cls @hide + */ + /** + * @cfg {String} disabled @hide + */ + /** + * @cfg {String} disabledClass @hide + */ + /** + * @cfg {String} msgTarget @hide + */ + /** + * @cfg {String} readOnly @hide + */ + /** + * @cfg {String} style @hide + */ + /** + * @cfg {String} validationDelay @hide + */ + /** + * @cfg {String} validationEvent @hide + */ + /** + * @cfg {String} tabIndex @hide + */ + /** + * @property disabled + * @hide + */ + /** + * @method applyToMarkup + * @hide + */ + /** + * @method disable + * @hide + */ + /** + * @method enable + * @hide + */ + /** + * @method validate + * @hide + */ + /** + * @event valid + * @hide + */ + /** + * @method setDisabled + * @hide + */ + /** + * @cfg keys + * @hide + */ +}); +Ext.reg('htmleditor', Ext.form.HtmlEditor); +/** + * @class Ext.form.TimeField + * @extends Ext.form.ComboBox + * Provides a time input field with a time dropdown and automatic time validation. Example usage: + *

+new Ext.form.TimeField({
+    minValue: '9:00 AM',
+    maxValue: '6:00 PM',
+    increment: 30
+});
+
+ * @constructor + * Create a new TimeField + * @param {Object} config + * @xtype timefield + */ +Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, { + /** + * @cfg {Date/String} minValue + * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string + * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined). + */ + minValue : undefined, + /** + * @cfg {Date/String} maxValue + * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string + * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined). + */ + maxValue : undefined, + /** + * @cfg {String} minText + * The error text to display when the date in the cell is before minValue (defaults to + * 'The time in this field must be equal to or after {0}'). + */ + minText : "The time in this field must be equal to or after {0}", + /** + * @cfg {String} maxText + * The error text to display when the time is after maxValue (defaults to + * 'The time in this field must be equal to or before {0}'). + */ + maxText : "The time in this field must be equal to or before {0}", + /** + * @cfg {String} invalidText + * The error text to display when the time in the field is invalid (defaults to + * '{value} is not a valid time'). + */ + invalidText : "{0} is not a valid time", + /** + * @cfg {String} format + * The default time format string which can be overriden for localization support. The format must be + * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time + * format try 'H:i' instead. + */ + format : "g:i A", + /** + * @cfg {String} altFormats + * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined + * format (defaults to 'g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A'). + */ + altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A", + /** + * @cfg {Number} increment + * The number of minutes between each time value in the list (defaults to 15). + */ + increment: 15, + + // private override + mode: 'local', + // private override + triggerAction: 'all', + // private override + typeAhead: false, + + // private - This is the date to use when generating time values in the absence of either minValue + // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an + // arbitrary "safe" date that can be any date aside from DST boundary dates. + initDate: '1/1/2008', + + initDateFormat: 'j/n/Y', + // private - onRemove : function(ct, c) { - // If a single form Field, remove it - if (this.isField(c)) { - Ext.destroy(c.container.up('.x-form-item')); - this.form.remove(c); - // If a Container, remove any Fields it might contain - } else if (c.findByType) { - Ext.each(c.findBy(this.isField), this.form.remove, this.form); + initComponent : function(){ + if(Ext.isDefined(this.minValue)){ + this.setMinValue(this.minValue, true); } - }, - - /** - * Starts monitoring of the valid state of this form. Usually this is done by passing the config - * option "monitorValid" - */ - startMonitoring : function(){ - if(!this.validTask){ - this.validTask = new Ext.util.TaskRunner(); - this.validTask.start({ - run : this.bindHandler, - interval : this.monitorPoll || 200, - scope: this - }); + if(Ext.isDefined(this.maxValue)){ + this.setMaxValue(this.maxValue, true); + } + if(!this.store){ + this.generateStore(true); } + Ext.form.TimeField.superclass.initComponent.call(this); }, /** - * Stops monitoring of the valid state of this form + * Replaces any existing {@link #minValue} with the new time and refreshes the store. + * @param {Date/String} value The minimum time that can be selected */ - stopMonitoring : function(){ - if(this.validTask){ - this.validTask.stopAll(); - this.validTask = null; - } + setMinValue: function(value, /* private */ initial){ + this.setLimit(value, true, initial); + return this; }, /** - * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call. - * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details) + * Replaces any existing {@link #maxValue} with the new time and refreshes the store. + * @param {Date/String} value The maximum time that can be selected */ - load : function(){ - this.form.load.apply(this.form, arguments); + setMaxValue: function(value, /* private */ initial){ + this.setLimit(value, false, initial); + return this; }, // private - onDisable : function(){ - Ext.FormPanel.superclass.onDisable.call(this); - if(this.form){ - this.form.items.each(function(){ - this.disable(); - }); + generateStore: function(initial){ + var min = this.minValue || new Date(this.initDate).clearTime(), + max = this.maxValue || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1), + times = []; + + while(min <= max){ + times.push(min.dateFormat(this.format)); + min = min.add('mi', this.increment); } + this.bindStore(times, initial); }, // private - onEnable : function(){ - Ext.FormPanel.superclass.onEnable.call(this); - if(this.form){ - this.form.items.each(function(){ - this.enable(); - }); + setLimit: function(value, isMin, initial){ + var d; + if(Ext.isString(value)){ + d = this.parseDate(value); + }else if(Ext.isDate(value)){ + d = value; + } + if(d){ + var val = new Date(this.initDate).clearTime(); + val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()); + this[isMin ? 'minValue' : 'maxValue'] = val; + if(!initial){ + this.generateStore(); + } } }, - // private - bindHandler : function(){ - var valid = true; - this.form.items.each(function(f){ - if(!f.isValid(true)){ - valid = false; - return false; + // inherited docs + getValue : function(){ + var v = Ext.form.TimeField.superclass.getValue.call(this); + return this.formatDate(this.parseDate(v)) || ''; + }, + + // inherited docs + setValue : function(value){ + return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value))); + }, + + // private overrides + validateValue : Ext.form.DateField.prototype.validateValue, + + formatDate : Ext.form.DateField.prototype.formatDate, + + parseDate: function(value) { + if (!value || Ext.isDate(value)) { + return value; + } + + var id = this.initDate + ' ', + idf = this.initDateFormat + ' ', + v = Date.parseDate(id + value, idf + this.format), // *** handle DST. note: this.format is a TIME-only format + af = this.altFormats; + + if (!v && af) { + if (!this.altFormatsArray) { + this.altFormatsArray = af.split("|"); } - }); - if(this.fbar){ - var fitems = this.fbar.items.items; - for(var i = 0, len = fitems.length; i < len; i++){ - var btn = fitems[i]; - if(btn.formBind === true && btn.disabled === valid){ - btn.setDisabled(!valid); - } + for (var i = 0, afa = this.altFormatsArray, len = afa.length; i < len && !v; i++) { + v = Date.parseDate(id + value, idf + afa[i]); } } - this.fireEvent('clientvalidation', this, valid); + + return v; } }); -Ext.reg('form', Ext.FormPanel); - -Ext.form.FormPanel = Ext.FormPanel; - -/** - * @class Ext.form.FieldSet - * @extends Ext.Panel - * Standard container used for grouping items within a {@link Ext.form.FormPanel form}. - *

-var form = new Ext.FormPanel({
-    title: 'Simple Form with FieldSets',
-    labelWidth: 75, // label settings here cascade unless overridden
-    url: 'save-form.php',
-    frame:true,
-    bodyStyle:'padding:5px 5px 0',
-    width: 700,
-    renderTo: document.body,
-    layout:'column', // arrange items in columns
-    defaults: {      // defaults applied to items
-        layout: 'form',
-        border: false,
-        bodyStyle: 'padding:4px'
-    },
-    items: [{
-        // Fieldset in Column 1
-        xtype:'fieldset',
-        columnWidth: 0.5,
-        title: 'Fieldset 1',
-        collapsible: true,
-        autoHeight:true,
-        defaults: {
-            anchor: '-20' // leave room for error icon
-        },
-        defaultType: 'textfield',
-        items :[{
-                fieldLabel: 'Field 1'
-            }, {
-                fieldLabel: 'Field 2'
-            }, {
-                fieldLabel: 'Field 3'
-            }
-        ]
-    },{
-        // Fieldset in Column 2 - Panel inside
-        xtype:'fieldset',
-        title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
-        autoHeight:true,
-        columnWidth: 0.5,
-        checkboxToggle: true,
-        collapsed: true, // fieldset initially collapsed
-        layout:'anchor',
-        items :[{
-            xtype: 'panel',
-            anchor: '100%',
-            title: 'Panel inside a fieldset',
-            frame: true,
-            height: 100
-        }]
-    }]
-});
- * 
- * @constructor - * @param {Object} config Configuration options - * @xtype fieldset - */ -Ext.form.FieldSet = Ext.extend(Ext.Panel, { - /** - * @cfg {Mixed} checkboxToggle true to render a checkbox into the fieldset frame just - * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults - * to false). - *

A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox. - * If true is specified, the default DomHelper config object used to create the element - * is:


-     * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
-     * 
- */ - /** - * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true - * (defaults to '[checkbox id]-checkbox'). - */ - /** - * @cfg {Boolean} collapsible - * true to make the fieldset collapsible and have the expand/collapse toggle button automatically - * rendered into the legend element, false to keep the fieldset statically sized with no collapse - * button (defaults to false). Another option is to configure {@link #checkboxToggle}. - */ - /** - * @cfg {Number} labelWidth The width of labels. This property cascades to child containers. - */ - /** - * @cfg {String} itemCls A css class to apply to the x-form-item of fields (see - * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details). - * This property cascades to child containers. - */ - /** - * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to 'x-fieldset'). - */ - baseCls : 'x-fieldset', - /** - * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to 'form'). - */ - layout : 'form', - /** - * @cfg {Boolean} animCollapse - * true to animate the transition when the panel is collapsed, false to skip the - * animation (defaults to false). - */ - animCollapse : false, - - // private - onRender : function(ct, position){ - if(!this.el){ - this.el = document.createElement('fieldset'); - this.el.id = this.id; - if (this.title || this.header || this.checkboxToggle) { - this.el.appendChild(document.createElement('legend')).className = 'x-fieldset-header'; - } - } - - Ext.form.FieldSet.superclass.onRender.call(this, ct, position); - - if(this.checkboxToggle){ - var o = typeof this.checkboxToggle == 'object' ? - this.checkboxToggle : - {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}; - this.checkbox = this.header.insertFirst(o); - this.checkbox.dom.checked = !this.collapsed; - this.mon(this.checkbox, 'click', this.onCheckClick, this); - } - }, - - // private - onCollapse : function(doAnim, animArg){ - if(this.checkbox){ - this.checkbox.dom.checked = false; - } - Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg); - - }, - - // private - onExpand : function(doAnim, animArg){ - if(this.checkbox){ - this.checkbox.dom.checked = true; - } - Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg); - }, - - /** - * This function is called by the fieldset's checkbox when it is toggled (only applies when - * checkboxToggle = true). This method should never be called externally, but can be - * overridden to provide custom behavior when the checkbox is toggled if needed. - */ - onCheckClick : function(){ - this[this.checkbox.dom.checked ? 'expand' : 'collapse'](); - } - - /** - * @cfg {String/Number} activeItem - * @hide - */ - /** - * @cfg {Mixed} applyTo - * @hide - */ - /** - * @cfg {Boolean} bodyBorder - * @hide - */ - /** - * @cfg {Boolean} border - * @hide - */ - /** - * @cfg {Boolean/Number} bufferResize - * @hide - */ - /** - * @cfg {Boolean} collapseFirst - * @hide - */ - /** - * @cfg {String} defaultType - * @hide - */ - /** - * @cfg {String} disabledClass - * @hide - */ - /** - * @cfg {String} elements - * @hide - */ - /** - * @cfg {Boolean} floating - * @hide - */ - /** - * @cfg {Boolean} footer - * @hide - */ - /** - * @cfg {Boolean} frame - * @hide - */ - /** - * @cfg {Boolean} header - * @hide - */ - /** - * @cfg {Boolean} headerAsText - * @hide - */ - /** - * @cfg {Boolean} hideCollapseTool - * @hide - */ - /** - * @cfg {String} iconCls - * @hide - */ - /** - * @cfg {Boolean/String} shadow - * @hide - */ - /** - * @cfg {Number} shadowOffset - * @hide - */ - /** - * @cfg {Boolean} shim - * @hide - */ - /** - * @cfg {Object/Array} tbar - * @hide - */ - /** - * @cfg {String} tabTip - * @hide - */ - /** - * @cfg {Boolean} titleCollapse - * @hide - */ - /** - * @cfg {Array} tools - * @hide - */ - /** - * @cfg {Ext.Template/Ext.XTemplate} toolTemplate - * @hide - */ - /** - * @cfg {String} xtype - * @hide - */ - /** - * @property header - * @hide - */ - /** - * @property footer - * @hide - */ - /** - * @method focus - * @hide - */ - /** - * @method getBottomToolbar - * @hide - */ - /** - * @method getTopToolbar - * @hide - */ - /** - * @method setIconClass - * @hide - */ - /** - * @event activate - * @hide - */ - /** - * @event beforeclose - * @hide - */ - /** - * @event bodyresize - * @hide - */ - /** - * @event close - * @hide - */ - /** - * @event deactivate - * @hide - */ -}); -Ext.reg('fieldset', Ext.form.FieldSet); -/** - * @class Ext.form.HtmlEditor - * @extends Ext.form.Field - * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be - * automatically hidden when needed. These are noted in the config options where appropriate. - *

The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not - * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}. - *

Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT - * supported by this editor. - *

An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within - * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs. - *

Example usage: - *

-// Simple example rendered with default options:
-Ext.QuickTips.init();  // enable tooltips
-new Ext.form.HtmlEditor({
-    renderTo: Ext.getBody(),
-    width: 800,
-    height: 300
-});
-
-// Passed via xtype into a container and with custom options:
-Ext.QuickTips.init();  // enable tooltips
-new Ext.Panel({
-    title: 'HTML Editor',
-    renderTo: Ext.getBody(),
-    width: 600,
-    height: 300,
-    frame: true,
-    layout: 'fit',
-    items: {
-        xtype: 'htmleditor',
-        enableColors: false,
-        enableAlignments: false
-    }
-});
-
- * @constructor - * Create a new HtmlEditor - * @param {Object} config - * @xtype htmleditor - */ - -Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, { - /** - * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true) - */ - enableFormat : true, - /** - * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true) - */ - enableFontSize : true, - /** - * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true) - */ - enableColors : true, - /** - * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true) - */ - enableAlignments : true, - /** - * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true) - */ - enableLists : true, - /** - * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true) - */ - enableSourceEdit : true, - /** - * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true) - */ - enableLinks : true, - /** - * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true) - */ - enableFont : true, - /** - * @cfg {String} createLinkText The default text for the create link prompt - */ - createLinkText : 'Please enter the URL for the link:', - /** - * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /) - */ - defaultLinkValue : 'http:/'+'/', - /** - * @cfg {Array} fontFamilies An array of available font families - */ - fontFamilies : [ - 'Arial', - 'Courier New', - 'Tahoma', - 'Times New Roman', - 'Verdana' - ], - defaultFont: 'tahoma', - /** - * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to ​ (Zero-width space),   (Non-breaking space) in Opera and IE6). - */ - defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '​', - - // private properties - actionMode: 'wrap', - validationEvent : false, - deferHeight: true, - initialized : false, - activated : false, - sourceEditMode : false, - onFocus : Ext.emptyFn, - iframePad:3, - hideMode:'offsets', - defaultAutoCreate : { - tag: "textarea", - style:"width:500px;height:300px;", - autocomplete: "off" - }, - - // private - initComponent : function(){ - this.addEvents( - /** - * @event initialize - * Fires when the editor is fully initialized (including the iframe) - * @param {HtmlEditor} this - */ - 'initialize', - /** - * @event activate - * Fires when the editor is first receives the focus. Any insertion must wait - * until after this event. - * @param {HtmlEditor} this - */ - 'activate', - /** - * @event beforesync - * Fires before the textarea is updated with content from the editor iframe. Return false - * to cancel the sync. - * @param {HtmlEditor} this - * @param {String} html - */ - 'beforesync', - /** - * @event beforepush - * Fires before the iframe editor is updated with content from the textarea. Return false - * to cancel the push. - * @param {HtmlEditor} this - * @param {String} html - */ - 'beforepush', - /** - * @event sync - * Fires when the textarea is updated with content from the editor iframe. - * @param {HtmlEditor} this - * @param {String} html - */ - 'sync', - /** - * @event push - * Fires when the iframe editor is updated with content from the textarea. - * @param {HtmlEditor} this - * @param {String} html - */ - 'push', - /** - * @event editmodechange - * Fires when the editor switches edit modes - * @param {HtmlEditor} this - * @param {Boolean} sourceEdit True if source edit, false if standard editing. - */ - 'editmodechange' - ) - }, - - // private - createFontOptions : function(){ - var buf = [], fs = this.fontFamilies, ff, lc; - for(var i = 0, len = fs.length; i< len; i++){ - ff = fs[i]; - lc = ff.toLowerCase(); - buf.push( - '' - ); - } - return buf.join(''); - }, - - /* - * Protected method that will not generally be called directly. It - * is called when the editor creates its toolbar. Override this method if you need to - * add custom toolbar buttons. - * @param {HtmlEditor} editor - */ - createToolbar : function(editor){ - - var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled(); - - function btn(id, toggle, handler){ - return { - itemId : id, - cls : 'x-btn-icon', - iconCls: 'x-edit-'+id, - enableToggle:toggle !== false, - scope: editor, - handler:handler||editor.relayBtnCmd, - clickEvent:'mousedown', - tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined, - overflowText: editor.buttonTips[id].title || undefined, - tabIndex:-1 - }; - } - - // build the toolbar - var tb = new Ext.Toolbar({ - renderTo:this.wrap.dom.firstChild - }); - - // stop form submits - this.mon(tb.el, 'click', function(e){ - e.preventDefault(); - }); - - if(this.enableFont && !Ext.isSafari2){ - this.fontSelect = tb.el.createChild({ - tag:'select', - cls:'x-font-select', - html: this.createFontOptions() - }); - this.mon(this.fontSelect, 'change', function(){ - var font = this.fontSelect.dom.value; - this.relayCmd('fontname', font); - this.deferFocus(); - }, this); - - tb.add( - this.fontSelect.dom, - '-' - ); - } - - if(this.enableFormat){ - tb.add( - btn('bold'), - btn('italic'), - btn('underline') - ); - } - - if(this.enableFontSize){ - tb.add( - '-', - btn('increasefontsize', false, this.adjustFont), - btn('decreasefontsize', false, this.adjustFont) - ); - } - - if(this.enableColors){ - tb.add( - '-', { - itemId:'forecolor', - cls:'x-btn-icon', - iconCls: 'x-edit-forecolor', - clickEvent:'mousedown', - tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined, - tabIndex:-1, - menu : new Ext.menu.ColorMenu({ - allowReselect: true, - focus: Ext.emptyFn, - value:'000000', - plain:true, - listeners: { - scope: this, - select: function(cp, color){ - this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color); - this.deferFocus(); - } - }, - clickEvent:'mousedown' - }) - }, { - itemId:'backcolor', - cls:'x-btn-icon', - iconCls: 'x-edit-backcolor', - clickEvent:'mousedown', - tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined, - tabIndex:-1, - menu : new Ext.menu.ColorMenu({ - focus: Ext.emptyFn, - value:'FFFFFF', - plain:true, - allowReselect: true, - listeners: { - scope: this, - select: function(cp, color){ - if(Ext.isGecko){ - this.execCmd('useCSS', false); - this.execCmd('hilitecolor', color); - this.execCmd('useCSS', true); - this.deferFocus(); - }else{ - this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color); - this.deferFocus(); - } - } - }, - clickEvent:'mousedown' - }) - } - ); - } - - if(this.enableAlignments){ - tb.add( - '-', - btn('justifyleft'), - btn('justifycenter'), - btn('justifyright') - ); - } - - if(!Ext.isSafari2){ - if(this.enableLinks){ - tb.add( - '-', - btn('createlink', false, this.createLink) - ); - } - - if(this.enableLists){ - tb.add( - '-', - btn('insertorderedlist'), - btn('insertunorderedlist') - ); - } - if(this.enableSourceEdit){ - tb.add( - '-', - btn('sourceedit', true, function(btn){ - this.toggleSourceEdit(!this.sourceEditMode); - }) - ); - } - } - - this.tb = tb; - }, - - /** - * Protected method that will not generally be called directly. It - * is called when the editor initializes the iframe with HTML contents. Override this method if you - * want to change the initialization markup of the iframe (e.g. to add stylesheets). - */ - getDocMarkup : function(){ - return ''; - }, - - // private - getEditorBody : function(){ - return this.doc.body || this.doc.documentElement; - }, - - // private - getDoc : function(){ - return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document); - }, - - // private - getWin : function(){ - return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name]; - }, - - // private - onRender : function(ct, position){ - Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position); - this.el.dom.style.border = '0 none'; - this.el.dom.setAttribute('tabIndex', -1); - this.el.addClass('x-hidden'); - if(Ext.isIE){ // fix IE 1px bogus margin - this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;') - } - this.wrap = this.el.wrap({ - cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'} - }); - - this.createToolbar(this); - - this.disableItems(true); - // is this needed? - // this.tb.doLayout(); - - this.createIFrame(); - - if(!this.width){ - var sz = this.el.getSize(); - this.setSize(sz.width, this.height || sz.height); - } - }, - - createIFrame: function(){ - var iframe = document.createElement('iframe'); - iframe.name = Ext.id(); - iframe.frameBorder = '0'; - iframe.src = Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;"; - this.wrap.dom.appendChild(iframe); - - this.iframe = iframe; - - this.monitorTask = Ext.TaskMgr.start({ - run: this.checkDesignMode, - scope: this, - interval:100 - }); - }, - - initFrame : function(){ - Ext.TaskMgr.stop(this.monitorTask); - this.doc = this.getDoc(); - this.win = this.getWin(); - - this.doc.open(); - this.doc.write(this.getDocMarkup()); - this.doc.close(); - - var task = { // must defer to wait for browser to be ready - run : function(){ - if(this.doc.body || this.doc.readyState == 'complete'){ - Ext.TaskMgr.stop(task); - this.doc.designMode="on"; - this.initEditor.defer(10, this); - } - }, - interval : 10, - duration:10000, - scope: this - }; - Ext.TaskMgr.start(task); - }, - - - checkDesignMode : function(){ - if(this.wrap && this.wrap.dom.offsetWidth){ - var doc = this.getDoc(); - if(!doc){ - return; - } - if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){ - this.initFrame(); - } - } - }, - - disableItems: function(disabled){ - if(this.fontSelect){ - this.fontSelect.dom.disabled = disabled; - } - this.tb.items.each(function(item){ - if(item.itemId != 'sourceedit'){ - item.setDisabled(disabled); - } - }); - }, - - // private - onResize : function(w, h){ - Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments); - if(this.el && this.iframe){ - if(typeof w == 'number'){ - var aw = w - this.wrap.getFrameWidth('lr'); - this.el.setWidth(this.adjustWidth('textarea', aw)); - this.tb.setWidth(aw); - this.iframe.style.width = Math.max(aw, 0) + 'px'; - } - if(typeof h == 'number'){ - var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight(); - this.el.setHeight(this.adjustWidth('textarea', ah)); - this.iframe.style.height = Math.max(ah, 0) + 'px'; - if(this.doc){ - this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px'; - } - } - } - }, - - /** - * Toggles the editor between standard and source edit mode. - * @param {Boolean} sourceEdit (optional) True for source edit, false for standard - */ - toggleSourceEdit : function(sourceEditMode){ - if(sourceEditMode === undefined){ - sourceEditMode = !this.sourceEditMode; - } - this.sourceEditMode = sourceEditMode === true; - var btn = this.tb.items.get('sourceedit'); - if(btn.pressed !== this.sourceEditMode){ - btn.toggle(this.sourceEditMode); - if(!btn.xtbHidden){ - return; - } - } - if(this.sourceEditMode){ - this.disableItems(true); - this.syncValue(); - this.iframe.className = 'x-hidden'; - this.el.removeClass('x-hidden'); - this.el.dom.removeAttribute('tabIndex'); - this.el.focus(); - }else{ - if(this.initialized){ - this.disableItems(false); - } - this.pushValue(); - this.iframe.className = ''; - this.el.addClass('x-hidden'); - this.el.dom.setAttribute('tabIndex', -1); - this.deferFocus(); - } - var lastSize = this.lastSize; - if(lastSize){ - delete this.lastSize; - this.setSize(lastSize); - } - this.fireEvent('editmodechange', this, this.sourceEditMode); - }, - - // private used internally - createLink : function(){ - var url = prompt(this.createLinkText, this.defaultLinkValue); - if(url && url != 'http:/'+'/'){ - this.relayCmd('createlink', url); - } - }, - - // private (for BoxComponent) - adjustSize : Ext.BoxComponent.prototype.adjustSize, - - // private (for BoxComponent) - getResizeEl : function(){ - return this.wrap; - }, - - // private (for BoxComponent) - getPositionEl : function(){ - return this.wrap; - }, - - // private - initEvents : function(){ - this.originalValue = this.getValue(); - }, - - /** - * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide - * @method - */ - markInvalid : Ext.emptyFn, - - /** - * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide - * @method - */ - clearInvalid : Ext.emptyFn, - - // docs inherit from Field - setValue : function(v){ - Ext.form.HtmlEditor.superclass.setValue.call(this, v); - this.pushValue(); - return this; - }, - - /** - * Protected method that will not generally be called directly. If you need/want - * custom HTML cleanup, this is the method you should override. - * @param {String} html The HTML to be cleaned - * @return {String} The cleaned HTML - */ - cleanHtml : function(html){ - html = String(html); - if(html.length > 5){ - if(Ext.isWebKit){ // strip safari nonsense - html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, ''); - } - } - if(html == this.defaultValue){ - html = ''; - } - return html; - }, - - /** - * Protected method that will not generally be called directly. Syncs the contents - * of the editor iframe with the textarea. - */ - syncValue : function(){ - if(this.initialized){ - var bd = this.getEditorBody(); - var html = bd.innerHTML; - if(Ext.isWebKit){ - var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element! - var m = bs.match(/text-align:(.*?);/i); - if(m && m[1]){ - html = '
' + html + '
'; - } - } - html = this.cleanHtml(html); - if(this.fireEvent('beforesync', this, html) !== false){ - this.el.dom.value = html; - this.fireEvent('sync', this, html); - } - } - }, - - //docs inherit from Field - getValue : function() { - this[this.sourceEditMode ? 'pushValue' : 'syncValue'](); - return Ext.form.HtmlEditor.superclass.getValue.call(this); - }, - - /** - * Protected method that will not generally be called directly. Pushes the value of the textarea - * into the iframe editor. - */ - pushValue : function(){ - if(this.initialized){ - var v = this.el.dom.value; - if(!this.activated && v.length < 1){ - v = this.defaultValue; - } - if(this.fireEvent('beforepush', this, v) !== false){ - this.getEditorBody().innerHTML = v; - if(Ext.isGecko){ - // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8 - var d = this.doc, - mode = d.designMode.toLowerCase(); - - d.designMode = mode.toggle('on', 'off'); - d.designMode = mode; - } - this.fireEvent('push', this, v); - } - } - }, - - // private - deferFocus : function(){ - this.focus.defer(10, this); - }, - - // docs inherit from Field - focus : function(){ - if(this.win && !this.sourceEditMode){ - this.win.focus(); - }else{ - this.el.focus(); - } - }, - - // private - initEditor : function(){ - //Destroying the component during/before initEditor can cause issues. - try{ - var dbody = this.getEditorBody(); - var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat'); - ss['background-attachment'] = 'fixed'; // w3c - dbody.bgProperties = 'fixed'; // ie - - Ext.DomHelper.applyStyles(dbody, ss); - - if(this.doc){ - try{ - Ext.EventManager.removeAll(this.doc); - }catch(e){} - } - - this.doc = this.getDoc(); - - Ext.EventManager.on(this.doc, { - 'mousedown': this.onEditorEvent, - 'dblclick': this.onEditorEvent, - 'click': this.onEditorEvent, - 'keyup': this.onEditorEvent, - buffer:100, - scope: this - }); - - if(Ext.isGecko){ - Ext.EventManager.on(this.doc, 'keypress', this.applyCommand, this); - } - if(Ext.isIE || Ext.isWebKit || Ext.isOpera){ - Ext.EventManager.on(this.doc, 'keydown', this.fixKeys, this); - } - this.initialized = true; - this.fireEvent('initialize', this); - this.doc.editorInitialized = true; - this.pushValue(); - }catch(e){} - }, - - // private - onDestroy : function(){ - if(this.monitorTask){ - Ext.TaskMgr.stop(this.monitorTask); - } - if(this.rendered){ - Ext.destroy(this.tb); - if(this.wrap){ - this.wrap.dom.innerHTML = ''; - this.wrap.remove(); - } - } - if(this.el){ - this.el.removeAllListeners(); - this.el.remove(); - } - - if(this.doc){ - try{ - Ext.EventManager.removeAll(this.doc); - for (var prop in this.doc){ - delete this.doc[prop]; - } - }catch(e){} - } - this.purgeListeners(); - }, - - // private - onFirstFocus : function(){ - this.activated = true; - this.disableItems(false); - if(Ext.isGecko){ // prevent silly gecko errors - this.win.focus(); - var s = this.win.getSelection(); - if(!s.focusNode || s.focusNode.nodeType != 3){ - var r = s.getRangeAt(0); - r.selectNodeContents(this.getEditorBody()); - r.collapse(true); - this.deferFocus(); - } - try{ - this.execCmd('useCSS', true); - this.execCmd('styleWithCSS', false); - }catch(e){} - } - this.fireEvent('activate', this); - }, - - // private - adjustFont: function(btn){ - var adjust = btn.itemId == 'increasefontsize' ? 1 : -1; - - var v = parseInt(this.doc.queryCommandValue('FontSize') || 2, 10); - if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){ - // Safari 3 values - // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px - if(v <= 10){ - v = 1 + adjust; - }else if(v <= 13){ - v = 2 + adjust; - }else if(v <= 16){ - v = 3 + adjust; - }else if(v <= 18){ - v = 4 + adjust; - }else if(v <= 24){ - v = 5 + adjust; - }else { - v = 6 + adjust; - } - v = v.constrain(1, 6); - }else{ - if(Ext.isSafari){ // safari - adjust *= 2; - } - v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0); - } - this.execCmd('FontSize', v); - }, - - // private - onEditorEvent : function(e){ - this.updateToolbar(); - }, - - - /** - * Protected method that will not generally be called directly. It triggers - * a toolbar update by reading the markup state of the current selection in the editor. - */ - updateToolbar: function(){ - - if(!this.activated){ - this.onFirstFocus(); - return; - } - - var btns = this.tb.items.map, doc = this.doc; - - if(this.enableFont && !Ext.isSafari2){ - var name = (this.doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase(); - if(name != this.fontSelect.dom.value){ - this.fontSelect.dom.value = name; - } - } - if(this.enableFormat){ - btns.bold.toggle(doc.queryCommandState('bold')); - btns.italic.toggle(doc.queryCommandState('italic')); - btns.underline.toggle(doc.queryCommandState('underline')); - } - if(this.enableAlignments){ - btns.justifyleft.toggle(doc.queryCommandState('justifyleft')); - btns.justifycenter.toggle(doc.queryCommandState('justifycenter')); - btns.justifyright.toggle(doc.queryCommandState('justifyright')); - } - if(!Ext.isSafari2 && this.enableLists){ - btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist')); - btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist')); - } - - Ext.menu.MenuMgr.hideAll(); - - this.syncValue(); - }, - - // private - relayBtnCmd : function(btn){ - this.relayCmd(btn.itemId); - }, - - /** - * Executes a Midas editor command on the editor document and performs necessary focus and - * toolbar updates. This should only be called after the editor is initialized. - * @param {String} cmd The Midas command - * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null) - */ - relayCmd : function(cmd, value){ - (function(){ - this.focus(); - this.execCmd(cmd, value); - this.updateToolbar(); - }).defer(10, this); - }, - - /** - * Executes a Midas editor command directly on the editor document. - * For visual commands, you should use {@link #relayCmd} instead. - * This should only be called after the editor is initialized. - * @param {String} cmd The Midas command - * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null) - */ - execCmd : function(cmd, value){ - this.doc.execCommand(cmd, false, value === undefined ? null : value); - this.syncValue(); - }, - - // private - applyCommand : function(e){ - if(e.ctrlKey){ - var c = e.getCharCode(), cmd; - if(c > 0){ - c = String.fromCharCode(c); - switch(c){ - case 'b': - cmd = 'bold'; - break; - case 'i': - cmd = 'italic'; - break; - case 'u': - cmd = 'underline'; - break; - } - if(cmd){ - this.win.focus(); - this.execCmd(cmd); - this.deferFocus(); - e.preventDefault(); - } - } - } - }, - - /** - * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated - * to insert text. - * @param {String} text - */ - insertAtCursor : function(text){ - if(!this.activated){ - return; - } - if(Ext.isIE){ - this.win.focus(); - var r = this.doc.selection.createRange(); - if(r){ - r.collapse(true); - r.pasteHTML(text); - this.syncValue(); - this.deferFocus(); - } - }else if(Ext.isGecko || Ext.isOpera){ - this.win.focus(); - this.execCmd('InsertHTML', text); - this.deferFocus(); - }else if(Ext.isWebKit){ - this.execCmd('InsertText', text); - this.deferFocus(); - } - }, - - // private - fixKeys : function(){ // load time branching for fastest keydown performance - if(Ext.isIE){ - return function(e){ - var k = e.getKey(), r; - if(k == e.TAB){ - e.stopEvent(); - r = this.doc.selection.createRange(); - if(r){ - r.collapse(true); - r.pasteHTML('    '); - this.deferFocus(); - } - }else if(k == e.ENTER){ - r = this.doc.selection.createRange(); - if(r){ - var target = r.parentElement(); - if(!target || target.tagName.toLowerCase() != 'li'){ - e.stopEvent(); - r.pasteHTML('
'); - r.collapse(false); - r.select(); - } - } - } - }; - }else if(Ext.isOpera){ - return function(e){ - var k = e.getKey(); - if(k == e.TAB){ - e.stopEvent(); - this.win.focus(); - this.execCmd('InsertHTML','    '); - this.deferFocus(); - } - }; - }else if(Ext.isWebKit){ - return function(e){ - var k = e.getKey(); - if(k == e.TAB){ - e.stopEvent(); - this.execCmd('InsertText','\t'); - this.deferFocus(); - } - }; - } - }(), - - /** - * Returns the editor's toolbar. This is only available after the editor has been rendered. - * @return {Ext.Toolbar} - */ - getToolbar : function(){ - return this.tb; - }, - - /** - * Object collection of toolbar tooltips for the buttons in the editor. The key - * is the command id associated with that button and the value is a valid QuickTips object. - * For example: -

-{
-    bold : {
-        title: 'Bold (Ctrl+B)',
-        text: 'Make the selected text bold.',
-        cls: 'x-html-editor-tip'
-    },
-    italic : {
-        title: 'Italic (Ctrl+I)',
-        text: 'Make the selected text italic.',
-        cls: 'x-html-editor-tip'
-    },
-    ...
-
- * @type Object - */ - buttonTips : { - bold : { - title: 'Bold (Ctrl+B)', - text: 'Make the selected text bold.', - cls: 'x-html-editor-tip' - }, - italic : { - title: 'Italic (Ctrl+I)', - text: 'Make the selected text italic.', - cls: 'x-html-editor-tip' - }, - underline : { - title: 'Underline (Ctrl+U)', - text: 'Underline the selected text.', - cls: 'x-html-editor-tip' - }, - increasefontsize : { - title: 'Grow Text', - text: 'Increase the font size.', - cls: 'x-html-editor-tip' - }, - decreasefontsize : { - title: 'Shrink Text', - text: 'Decrease the font size.', - cls: 'x-html-editor-tip' - }, - backcolor : { - title: 'Text Highlight Color', - text: 'Change the background color of the selected text.', - cls: 'x-html-editor-tip' - }, - forecolor : { - title: 'Font Color', - text: 'Change the color of the selected text.', - cls: 'x-html-editor-tip' - }, - justifyleft : { - title: 'Align Text Left', - text: 'Align text to the left.', - cls: 'x-html-editor-tip' - }, - justifycenter : { - title: 'Center Text', - text: 'Center text in the editor.', - cls: 'x-html-editor-tip' - }, - justifyright : { - title: 'Align Text Right', - text: 'Align text to the right.', - cls: 'x-html-editor-tip' - }, - insertunorderedlist : { - title: 'Bullet List', - text: 'Start a bulleted list.', - cls: 'x-html-editor-tip' - }, - insertorderedlist : { - title: 'Numbered List', - text: 'Start a numbered list.', - cls: 'x-html-editor-tip' - }, - createlink : { - title: 'Hyperlink', - text: 'Make the selected text a hyperlink.', - cls: 'x-html-editor-tip' - }, - sourceedit : { - title: 'Source Edit', - text: 'Switch to source editing mode.', - cls: 'x-html-editor-tip' - } - } - - // hide stuff that is not compatible - /** - * @event blur - * @hide - */ - /** - * @event change - * @hide - */ - /** - * @event focus - * @hide - */ - /** - * @event specialkey - * @hide - */ - /** - * @cfg {String} fieldClass @hide - */ - /** - * @cfg {String} focusClass @hide - */ - /** - * @cfg {String} autoCreate @hide - */ - /** - * @cfg {String} inputType @hide - */ - /** - * @cfg {String} invalidClass @hide - */ - /** - * @cfg {String} invalidText @hide - */ - /** - * @cfg {String} msgFx @hide - */ - /** - * @cfg {String} validateOnBlur @hide - */ - /** - * @cfg {Boolean} allowDomMove @hide - */ - /** - * @cfg {String} applyTo @hide - */ - /** - * @cfg {String} autoHeight @hide - */ - /** - * @cfg {String} autoWidth @hide - */ - /** - * @cfg {String} cls @hide - */ - /** - * @cfg {String} disabled @hide - */ - /** - * @cfg {String} disabledClass @hide - */ - /** - * @cfg {String} msgTarget @hide - */ - /** - * @cfg {String} readOnly @hide - */ - /** - * @cfg {String} style @hide - */ - /** - * @cfg {String} validationDelay @hide - */ - /** - * @cfg {String} validationEvent @hide - */ - /** - * @cfg {String} tabIndex @hide - */ - /** - * @property disabled - * @hide - */ - /** - * @method applyToMarkup - * @hide - */ - /** - * @method disable - * @hide - */ - /** - * @method enable - * @hide - */ - /** - * @method validate - * @hide - */ - /** - * @event valid - * @hide - */ - /** - * @method setDisabled - * @hide - */ - /** - * @cfg keys - * @hide - */ -}); -Ext.reg('htmleditor', Ext.form.HtmlEditor);/** - * @class Ext.form.TimeField - * @extends Ext.form.ComboBox - * Provides a time input field with a time dropdown and automatic time validation. Example usage: - *

-new Ext.form.TimeField({
-    minValue: '9:00 AM',
-    maxValue: '6:00 PM',
-    increment: 30
-});
-
- * @constructor - * Create a new TimeField - * @param {Object} config - * @xtype timefield - */ -Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, { - /** - * @cfg {Date/String} minValue - * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string - * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null). - */ - minValue : null, - /** - * @cfg {Date/String} maxValue - * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string - * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null). - */ - maxValue : null, - /** - * @cfg {String} minText - * The error text to display when the date in the cell is before minValue (defaults to - * 'The time in this field must be equal to or after {0}'). - */ - minText : "The time in this field must be equal to or after {0}", - /** - * @cfg {String} maxText - * The error text to display when the time is after maxValue (defaults to - * 'The time in this field must be equal to or before {0}'). - */ - maxText : "The time in this field must be equal to or before {0}", - /** - * @cfg {String} invalidText - * The error text to display when the time in the field is invalid (defaults to - * '{value} is not a valid time'). - */ - invalidText : "{0} is not a valid time", - /** - * @cfg {String} format - * The default time format string which can be overriden for localization support. The format must be - * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time - * format try 'H:i' instead. - */ - format : "g:i A", - /** - * @cfg {String} altFormats - * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined - * format (defaults to 'g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H'). - */ - altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H", - /** - * @cfg {Number} increment - * The number of minutes between each time value in the list (defaults to 15). - */ - increment: 15, - - // private override - mode: 'local', - // private override - triggerAction: 'all', - // private override - typeAhead: false, - - // private - This is the date to use when generating time values in the absence of either minValue - // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an - // arbitrary "safe" date that can be any date aside from DST boundary dates. - initDate: '1/1/2008', - - // private - initComponent : function(){ - if(typeof this.minValue == "string"){ - this.minValue = this.parseDate(this.minValue); - } - if(typeof this.maxValue == "string"){ - this.maxValue = this.parseDate(this.maxValue); - } - - if(!this.store){ - var min = this.parseDate(this.minValue) || new Date(this.initDate).clearTime(); - var max = this.parseDate(this.maxValue) || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1); - var times = []; - while(min <= max){ - times.push(min.dateFormat(this.format)); - min = min.add('mi', this.increment); - } - this.store = times; - } - Ext.form.TimeField.superclass.initComponent.call(this); - }, - - // inherited docs - getValue : function(){ - var v = Ext.form.TimeField.superclass.getValue.call(this); - return this.formatDate(this.parseDate(v)) || ''; - }, - - // inherited docs - setValue : function(value){ - return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value))); - }, - - // private overrides - validateValue : Ext.form.DateField.prototype.validateValue, - parseDate : Ext.form.DateField.prototype.parseDate, - formatDate : Ext.form.DateField.prototype.formatDate, - - // private - beforeBlur : function(){ - var v = this.parseDate(this.getRawValue()); - if(v){ - this.setValue(v.dateFormat(this.format)); - } - Ext.form.TimeField.superclass.beforeBlur.call(this); - } - - /** - * @cfg {Boolean} grow @hide - */ - /** - * @cfg {Number} growMin @hide - */ - /** - * @cfg {Number} growMax @hide - */ - /** - * @hide - * @method autoSize - */ -}); Ext.reg('timefield', Ext.form.TimeField);/** + * @class Ext.form.SliderField + * @extends Ext.form.Field + * Wraps a {@link Ext.Slider Slider} so it can be used as a form field. + * @constructor + * Creates a new SliderField + * @param {Object} config Configuration options. Note that you can pass in any slider configuration options, as well as + * as any field configuration options. + * @xtype sliderfield + */ +Ext.form.SliderField = Ext.extend(Ext.form.Field, { + + /** + * @cfg {Boolean} useTips + * True to use an Ext.slider.Tip to display tips for the value. Defaults to true. + */ + useTips : true, + + /** + * @cfg {Function} tipText + * A function used to display custom text for the slider tip. Defaults to null, which will + * use the default on the plugin. + */ + tipText : null, + + // private override + actionMode: 'wrap', + + /** + * Initialize the component. + * @private + */ + initComponent : function() { + var cfg = Ext.copyTo({ + id: this.id + '-slider' + }, this.initialConfig, ['vertical', 'minValue', 'maxValue', 'decimalPrecision', 'keyIncrement', 'increment', 'clickToChange', 'animate']); + + // only can use it if it exists. + if (this.useTips) { + var plug = this.tipText ? {getText: this.tipText} : {}; + cfg.plugins = [new Ext.slider.Tip(plug)]; + } + this.slider = new Ext.Slider(cfg); + Ext.form.SliderField.superclass.initComponent.call(this); + }, + + /** + * Set up the hidden field + * @param {Object} ct The container to render to. + * @param {Object} position The position in the container to render to. + * @private + */ + onRender : function(ct, position){ + this.autoCreate = { + id: this.id, + name: this.name, + type: 'hidden', + tag: 'input' + }; + Ext.form.SliderField.superclass.onRender.call(this, ct, position); + this.wrap = this.el.wrap({cls: 'x-form-field-wrap'}); + this.resizeEl = this.positionEl = this.wrap; + this.slider.render(this.wrap); + }, + + /** + * Ensure that the slider size is set automatically when the field resizes. + * @param {Object} w The width + * @param {Object} h The height + * @param {Object} aw The adjusted width + * @param {Object} ah The adjusted height + * @private + */ + onResize : function(w, h, aw, ah){ + Ext.form.SliderField.superclass.onResize.call(this, w, h, aw, ah); + this.slider.setSize(w, h); + }, + + /** + * Initialize any events for this class. + * @private + */ + initEvents : function(){ + Ext.form.SliderField.superclass.initEvents.call(this); + this.slider.on('change', this.onChange, this); + }, + + /** + * Utility method to set the value of the field when the slider changes. + * @param {Object} slider The slider object. + * @param {Object} v The new value. + * @private + */ + onChange : function(slider, v){ + this.setValue(v, undefined, true); + }, + + /** + * Enable the slider when the field is enabled. + * @private + */ + onEnable : function(){ + Ext.form.SliderField.superclass.onEnable.call(this); + this.slider.enable(); + }, + + /** + * Disable the slider when the field is disabled. + * @private + */ + onDisable : function(){ + Ext.form.SliderField.superclass.onDisable.call(this); + this.slider.disable(); + }, + + /** + * Ensure the slider is destroyed when the field is destroyed. + * @private + */ + beforeDestroy : function(){ + Ext.destroy(this.slider); + Ext.form.SliderField.superclass.beforeDestroy.call(this); + }, + + /** + * If a side icon is shown, do alignment to the slider + * @private + */ + alignErrorIcon : function(){ + this.errorIcon.alignTo(this.slider.el, 'tl-tr', [2, 0]); + }, + + /** + * Sets the minimum field value. + * @param {Number} v The new minimum value. + * @return {Ext.form.SliderField} this + */ + setMinValue : function(v){ + this.slider.setMinValue(v); + return this; + }, + + /** + * Sets the maximum field value. + * @param {Number} v The new maximum value. + * @return {Ext.form.SliderField} this + */ + setMaxValue : function(v){ + this.slider.setMaxValue(v); + return this; + }, + + /** + * Sets the value for this field. + * @param {Number} v The new value. + * @param {Boolean} animate (optional) Whether to animate the transition. If not specified, it will default to the animate config. + * @return {Ext.form.SliderField} this + */ + setValue : function(v, animate, /* private */ silent){ + // silent is used if the setValue method is invoked by the slider + // which means we don't need to set the value on the slider. + if(!silent){ + this.slider.setValue(v, animate); + } + return Ext.form.SliderField.superclass.setValue.call(this, this.slider.getValue()); + }, + + /** + * Gets the current value for this field. + * @return {Number} The current value. + */ + getValue : function(){ + return this.slider.getValue(); + } +}); + +Ext.reg('sliderfield', Ext.form.SliderField);/** * @class Ext.form.Label * @extends Ext.BoxComponent * Basic Label field. @@ -7019,6 +8326,11 @@ Ext.form.Action.prototype = { * during the time the action is being processed. */ +/** + * @cfg {Boolean} submitEmptyText If set to true, the emptyText value will be sent with the form + * when it is submitted. Defaults to true. + */ + /** * The type of action this Action instance performs. * Currently only "submit" and "load" are supported. @@ -7237,10 +8549,20 @@ Ext.extend(Ext.form.Action.Submit, Ext.form.Action, { // private run : function(){ - var o = this.options; - var method = this.getMethod(); - var isGet = method == 'GET'; + var o = this.options, + method = this.getMethod(), + isGet = method == 'GET'; if(o.clientValidation === false || this.form.isValid()){ + if (o.submitEmptyText === false) { + var fields = this.form.items, + emptyFields = []; + fields.each(function(f) { + if (f.el.getValue() == f.emptyText) { + emptyFields.push(f); + f.el.dom.value = ""; + } + }); + } Ext.Ajax.request(Ext.apply(this.createCallback(o), { form:this.form.el.dom, url:this.getUrl(isGet), @@ -7249,6 +8571,13 @@ Ext.extend(Ext.form.Action.Submit, Ext.form.Action, { params:!isGet ? this.getParams() : null, isUpload: this.form.fileUpload })); + if (o.submitEmptyText === false) { + Ext.each(emptyFields, function(f) { + if (f.applyEmptyText) { + f.applyEmptyText(); + } + }); + } }else if (o.clientValidation !== false){ // client validation failed this.failureType = Ext.form.Action.CLIENT_INVALID; this.form.afterAction(this, false); @@ -7264,8 +8593,8 @@ Ext.extend(Ext.form.Action.Submit, Ext.form.Action, { } if(result.errors){ this.form.markInvalid(result.errors); - this.failureType = Ext.form.Action.SERVER_INVALID; } + this.failureType = Ext.form.Action.SERVER_INVALID; this.form.afterAction(this, false); }, @@ -7328,7 +8657,7 @@ myFormPanel.{@link Ext.form.FormPanel#getForm getForm}().{@link Ext.form.BasicFo params: { consignmentRef: myConsignmentRef }, - failure: function(form, action() { + failure: function(form, action) { Ext.Msg.alert("Load failed", action.result.errorMessage); } }); @@ -7403,62 +8732,90 @@ Ext.extend(Ext.form.Action.Load, Ext.form.Action, { /** * @class Ext.form.Action.DirectLoad * @extends Ext.form.Action.Load - * Provides Ext.direct support for loading form data. This example illustrates usage - * of Ext.Direct to load a submit a form through Ext.Direct. + *

Provides Ext.direct support for loading form data.

+ *

This example illustrates usage of Ext.Direct to load a form through Ext.Direct.

*

 var myFormPanel = new Ext.form.FormPanel({
     // configs for FormPanel
     title: 'Basic Information',
-    border: false,
+    renderTo: document.body,
+    width: 300, height: 160,
     padding: 10,
-    buttons:[{
-        text: 'Submit',
-        handler: function(){
-            basicInfo.getForm().submit({
-                params: {
-                    uid: 5
-                }
-            });
-        }
-    }],
-    
+
     // configs apply to child items
     defaults: {anchor: '100%'},
     defaultType: 'textfield',
-    items: [
-        // form fields go here
-    ],
-    
+    items: [{
+        fieldLabel: 'Name',
+        name: 'name'
+    },{
+        fieldLabel: 'Email',
+        name: 'email'
+    },{
+        fieldLabel: 'Company',
+        name: 'company'
+    }],
+
     // configs for BasicForm
     api: {
+        // The server-side method to call for load() requests
         load: Profile.getBasicInfo,
         // The server-side must mark the submit handler as a 'formHandler'
         submit: Profile.updateBasicInfo
-    },    
-    paramOrder: ['uid']
+    },
+    // specify the order for the passed params
+    paramOrder: ['uid', 'foo']
 });
 
 // load the form
 myFormPanel.getForm().load({
+    // pass 2 arguments to server side getBasicInfo method (len=2)
     params: {
-        uid: 5
+        foo: 'bar',
+        uid: 34
     }
 });
+ * 
+ * The data packet sent to the server will resemble something like: + *

+[
+    {
+        "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
+        "data":[34,"bar"] // note the order of the params
+    }
+]
+ * 
+ * The form will process a data packet returned by the server that is similar + * to the following format: + *

+[
+    {
+        "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
+        "result":{
+            "success":true,
+            "data":{
+                "name":"Fred Flintstone",
+                "company":"Slate Rock and Gravel",
+                "email":"fred.flintstone@slaterg.com"
+            }
+        }
+    }
+]
  * 
*/ Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, { - constructor: function(form, opts) { + constructor: function(form, opts) { Ext.form.Action.DirectLoad.superclass.constructor.call(this, form, opts); }, - type: 'directload', - + type : 'directload', + run : function(){ var args = this.getParams(); - args.push(this.success, this); + args.push(this.success, this); this.form.api.load.apply(window, args); }, - - getParams: function() { + + getParams : function() { var buf = [], o = {}; var bp = this.form.baseParams; var p = this.options.params; @@ -7476,23 +8833,114 @@ Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, { // Direct actions have already been processed and therefore // we can directly set the result; Direct Actions do not have // a this.response property. - processResponse: function(result) { + processResponse : function(result) { this.result = result; - return result; + return result; + }, + + success : function(response, trans){ + if(trans.type == Ext.Direct.exceptions.SERVER){ + response = {}; + } + Ext.form.Action.DirectLoad.superclass.success.call(this, response); } }); /** * @class Ext.form.Action.DirectSubmit * @extends Ext.form.Action.Submit - * Provides Ext.direct support for submitting form data. - * See {@link Ext.form.Action.DirectLoad}. + *

Provides Ext.direct support for submitting form data.

+ *

This example illustrates usage of Ext.Direct to submit a form through Ext.Direct.

+ *

+var myFormPanel = new Ext.form.FormPanel({
+    // configs for FormPanel
+    title: 'Basic Information',
+    renderTo: document.body,
+    width: 300, height: 160,
+    padding: 10,
+    buttons:[{
+        text: 'Submit',
+        handler: function(){
+            myFormPanel.getForm().submit({
+                params: {
+                    foo: 'bar',
+                    uid: 34
+                }
+            });
+        }
+    }],
+
+    // configs apply to child items
+    defaults: {anchor: '100%'},
+    defaultType: 'textfield',
+    items: [{
+        fieldLabel: 'Name',
+        name: 'name'
+    },{
+        fieldLabel: 'Email',
+        name: 'email'
+    },{
+        fieldLabel: 'Company',
+        name: 'company'
+    }],
+
+    // configs for BasicForm
+    api: {
+        // The server-side method to call for load() requests
+        load: Profile.getBasicInfo,
+        // The server-side must mark the submit handler as a 'formHandler'
+        submit: Profile.updateBasicInfo
+    },
+    // specify the order for the passed params
+    paramOrder: ['uid', 'foo']
+});
+ * 
+ * The data packet sent to the server will resemble something like: + *

+{
+    "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
+    "result":{
+        "success":true,
+        "id":{
+            "extAction":"Profile","extMethod":"updateBasicInfo",
+            "extType":"rpc","extTID":"6","extUpload":"false",
+            "name":"Aaron Conran","email":"aaron@extjs.com","company":"Ext JS, LLC"
+        }
+    }
+}
+ * 
+ * The form will process a data packet returned by the server that is similar + * to the following: + *

+// sample success packet (batched requests)
+[
+    {
+        "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
+        "result":{
+            "success":true
+        }
+    }
+]
+
+// sample failure packet (one request)
+{
+        "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
+        "result":{
+            "errors":{
+                "email":"already taken"
+            },
+            "success":false,
+            "foo":"bar"
+        }
+}
+ * 
+ * Also see the discussion in {@link Ext.form.Action.DirectLoad}. */ Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, { - constructor: function(form, opts) { + constructor : function(form, opts) { Ext.form.Action.DirectSubmit.superclass.constructor.call(this, form, opts); }, - type: 'directsubmit', + type : 'directsubmit', // override of Submit run : function(){ var o = this.options; @@ -7506,29 +8954,35 @@ Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, { this.form.afterAction(this, false); } }, - - getParams: function() { + + getParams : function() { var o = {}; var bp = this.form.baseParams; var p = this.options.params; Ext.apply(o, p, bp); return o; - }, + }, // Direct actions have already been processed and therefore // we can directly set the result; Direct Actions do not have // a this.response property. - processResponse: function(result) { + processResponse : function(result) { this.result = result; - return result; + return result; + }, + + success : function(response, trans){ + if(trans.type == Ext.Direct.exceptions.SERVER){ + response = {}; + } + Ext.form.Action.DirectSubmit.superclass.success.call(this, response); } }); - Ext.form.Action.ACTION_TYPES = { 'load' : Ext.form.Action.Load, 'submit' : Ext.form.Action.Submit, - 'directload': Ext.form.Action.DirectLoad, - 'directsubmit': Ext.form.Action.DirectSubmit + 'directload' : Ext.form.Action.DirectLoad, + 'directsubmit' : Ext.form.Action.DirectSubmit }; /** * @class Ext.form.VTypes @@ -7551,7 +9005,7 @@ Ext.apply(Ext.form.VTypes, { timeMask: /[\d\s:amp]/i }); *
- * Another example: + * Another example: *

 // custom Vtype for vtype:'IPAddress'
 Ext.apply(Ext.form.VTypes, {
@@ -7566,10 +9020,10 @@ Ext.apply(Ext.form.VTypes, {
  */
 Ext.form.VTypes = function(){
     // closure these in so they are only created once.
-    var alpha = /^[a-zA-Z_]+$/;
-    var alphanum = /^[a-zA-Z0-9_]+$/;
-    var email = /^(\w+)([-+.][\w]+)*@(\w[-\w]*\.){1,5}([A-Za-z]){2,4}$/;
-    var url = /(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+    var alpha = /^[a-zA-Z_]+$/,
+        alphanum = /^[a-zA-Z0-9_]+$/,
+        email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
+        url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
 
     // All these messages and functions are configurable
     return {
@@ -7577,7 +9031,7 @@ Ext.form.VTypes = function(){
          * The function used to validate email addresses.  Note that this is a very basic validation -- complete
          * validation per the email RFC specifications is very complex and beyond the scope of this class, although
          * this function can be overridden if a more comprehensive validation scheme is desired.  See the validation
-         * section of the Wikipedia article on email addresses 
+         * section of the Wikipedia article on email addresses
          * for additional information.  This implementation is intended to validate the following emails:
          * 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
          * .
@@ -7594,12 +9048,12 @@ Ext.form.VTypes = function(){
          */
         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
         /**
-         * The keystroke filter mask to be applied on email input.  See the {@link #email} method for 
+         * The keystroke filter mask to be applied on email input.  See the {@link #email} method for
          * information about more complex email validation. Defaults to:
          * /[a-z0-9_\.\-@]/i
          * @type RegExp
          */
-        'emailMask' : /[a-z0-9_\.\-@]/i,
+        'emailMask' : /[a-z0-9_\.\-@\+]/i,
 
         /**
          * The function used to validate URLs
@@ -7615,7 +9069,7 @@ Ext.form.VTypes = function(){
          * @type String
          */
         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
-        
+
         /**
          * The function used to validate alpha values
          * @param {String} value The value
@@ -7658,4 +9112,4 @@ Ext.form.VTypes = function(){
          */
         'alphanumMask' : /[a-z0-9_]/i
     };
-}();
\ No newline at end of file
+}();