X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..6e39d509471fe9b4e2660e0d1631b350d0c66f40:/pkgs/pkg-forms-debug.js diff --git a/pkgs/pkg-forms-debug.js b/pkgs/pkg-forms-debug.js index e3776075..aa61db0d 100644 --- a/pkgs/pkg-forms-debug.js +++ b/pkgs/pkg-forms-debug.js @@ -1,5 +1,5 @@ /*! - * Ext JS Library 3.0.0 + * Ext JS Library 3.1.0 * Copyright(c) 2006-2009 Ext JS, LLC * 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 @@ -269,6 +289,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(){ @@ -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,20 +411,41 @@ 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 + /** + * @private + * Subclasses should provide the validation implementation by overriding this + * @param {Mixed} value + */ validateValue : function(value){ return true; }, + + /** + * 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 || ''; + }, /** - * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and - * applying {@link #invalidClass} to the field's element. + *

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){ @@ -396,6 +465,7 @@ var form = new Ext.form.FormPanel({ t.style.display = this.msgDisplay; } } + this.activeError = msg; this.fireEvent('invalid', this, msg); }, @@ -418,6 +488,7 @@ var form = new Ext.form.FormPanel({ t.style.display = 'none'; } } + delete this.activeError; this.fireEvent('valid', this); }, @@ -470,7 +541,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 +561,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; } /** @@ -645,44 +696,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 +801,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 +895,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 +913,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 +963,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 +1015,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 +1026,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,8 +1053,70 @@ 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 + *

Validates a value according to the field's validation rules and marks the field as invalid + * if the validation fails. Validation rules are processed in the following order:

+ *
*

See also {@link #mode}.

*/ @@ -2281,8 +2469,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 +2479,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}.

*/ @@ -2482,6 +2672,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: @@ -2588,10 +2791,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 +2819,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'); @@ -2656,7 +2858,8 @@ var combo = new Ext.form.ComboBox({ parentEl: this.getListParent(), shadow: this.shadow, cls: [cls, this.listClass].join(' '), - constrain:false + constrain:false, + zindex: 12000 }); var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth); @@ -2803,17 +3006,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,6 +3045,13 @@ 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); @@ -2859,8 +3073,6 @@ var menu = new Ext.menu.Menu({ "enter" : function(e){ this.onViewClick(); - this.delayedCheck = true; - this.unsetDelayCheck.defer(10, this); }, "esc" : function(e){ @@ -2874,14 +3086,21 @@ var menu = new Ext.menu.Menu({ 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,7 +3108,7 @@ 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); } }, @@ -2907,34 +3126,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(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,7 +3187,7 @@ 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){ @@ -3109,10 +3323,13 @@ 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 if(s.getCount() === 0){ + this.onEmptyResults(); } if(doFocus !== false){ this.el.focus(); @@ -3122,12 +3339,13 @@ var menu = new Ext.menu.Menu({ // 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); @@ -3213,7 +3431,7 @@ 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,16 +3450,16 @@ var menu = new Ext.menu.Menu({ // private beforeBlur : function(){ - var val = this.getRawValue(); - if(this.forceSelection){ + 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.isDefined(this.lastSelectionText) ? this.lastSelectionText : ''; + this.el.dom.value = Ext.isEmpty(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); } @@ -3333,12 +3551,16 @@ var menu = new Ext.menu.Menu({ if(this.isExpanded() || !this.hasFocus){ return; } + if(this.bufferSize){ + this.doResize(this.bufferSize); + delete this.bufferSize; + } this.list.alignTo(this.wrap, this.listAlign); this.list.show(); if(Ext.isGecko2){ this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac } - Ext.getDoc().on({ + this.mon(Ext.getDoc(), { scope: this, mousewheel: this.collapseIf, mousedown: this.collapseIf @@ -3353,7 +3575,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 +3607,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. @@ -3459,18 +3682,11 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, { // private initEvents : function(){ Ext.form.Checkbox.superclass.initEvents.call(this); - this.mon(this.el, 'click', this.onClick, this); - this.mon(this.el, 'change', this.onClick, this); - }, - - // private - getResizeEl : function(){ - return this.wrap; - }, - - // private - getPositionEl : function(){ - return this.wrap; + this.mon(this.el, { + scope: this, + click: this.onClick, + change: this.onClick + }); }, /** @@ -3501,6 +3717,11 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, { }else{ this.checked = this.el.dom.checked; } + // Need to repaint for IE, otherwise positioning is broken + if(Ext.isIE){ + this.wrap.repaint(); + } + this.resizeEl = this.positionEl = this.wrap; }, // private @@ -3522,7 +3743,7 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, { if(this.rendered){ return this.el.dom.checked; } - return false; + return this.checked; }, // private @@ -3591,7 +3812,7 @@ Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, { * checkbox/radio controls using automatic layout. This config can take several types of values: * - * + * *

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 +5231,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 +5306,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 +5326,7 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { this.bodyCfg.enctype = 'multipart/form-data'; } this.initItems(); - + this.addEvents( /** * @event clientvalidation @@ -5067,19 +5353,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 +5364,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 +5393,20 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { Ext.FormPanel.superclass.onRender.call(this, ct, position); this.form.initEl(this.body); }, - + // private beforeDestroy : function(){ this.stopMonitoring(); - 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); + this.form.items.clear(); + Ext.FormPanel.superclass.beforeDestroy.call(this); }, - // 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,38 +5414,65 @@ 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(ct, c) { - // If a single form Field, add it - if (this.isField(c)) { + 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 + 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 - 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); + processRemove : function(c){ + // 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){ + var isDestroyed = function(o) { + return !!o.isDestroyed; + } + this.form.items.filterBy(isDestroyed, this.form).each(this.form.remove, this.form); } }, @@ -5195,7 +5506,7 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { * @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); + this.form.load.apply(this.form, arguments); }, // private @@ -5242,7 +5553,6 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { Ext.reg('form', Ext.FormPanel); Ext.form.FormPanel = Ext.FormPanel; - /** * @class Ext.form.FieldSet * @extends Ext.Panel @@ -5476,14 +5786,6 @@ Ext.form.FieldSet = Ext.extend(Ext.Panel, { * @cfg {Object/Array} tbar * @hide */ - /** - * @cfg {String} tabTip - * @hide - */ - /** - * @cfg {Boolean} titleCollapse - * @hide - */ /** * @cfg {Array} tools * @hide @@ -5542,1178 +5844,1229 @@ Ext.form.FieldSet = Ext.extend(Ext.Panel, { */ }); 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(); +/** + * @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); + }) + ); + } } - 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); + // 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(); - } - 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; + }, this); } - 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')); - } + // stop form submits + this.mon(tb.el, 'click', function(e){ + e.preventDefault(); + }); + - 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 - */ -}); + + this.tb = tb; + }, + + 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){ + if(this.initialized){ + var newDM = readOnly ? 'off' : 'on', + doc = this.getDoc(); + if(String(doc.designMode).toLowerCase() != newDM){ + doc.designMode = newDM; + } + this.disableItems(!readOnly); + } + Ext.form.HtmlEditor.superclass.setReadOnly.call(this, 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). + */ + getDocMarkup : function(){ + return ''; + }, + + // 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); + // is this needed? + // 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.src = Ext.SSL_SECURE_URL; + 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); + 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.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){ + 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){ + 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.readOnly){ + 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 + 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 + var d = this.getDoc(), + 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(), + ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat'), + 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(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.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 @@ -6734,15 +7087,15 @@ 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). + * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined). */ - minValue : null, + 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 null). + * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined). */ - maxValue : null, + maxValue : undefined, /** * @cfg {String} minText * The error text to display when the date in the cell is before minValue (defaults to @@ -6794,26 +7147,67 @@ Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, { // private initComponent : function(){ - if(typeof this.minValue == "string"){ - this.minValue = this.parseDate(this.minValue); + if(Ext.isDefined(this.minValue)){ + this.setMinValue(this.minValue, true); } - if(typeof this.maxValue == "string"){ - this.maxValue = this.parseDate(this.maxValue); + if(Ext.isDefined(this.maxValue)){ + this.setMaxValue(this.maxValue, true); } - 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; + this.generateStore(true); } Ext.form.TimeField.superclass.initComponent.call(this); }, + + /** + * Replaces any existing {@link #minValue} with the new time and refreshes the store. + * @param {Date/String} value The minimum time that can be selected + */ + setMinValue: function(value, /* private */ initial){ + this.setLimit(value, true, initial); + return this; + }, + + /** + * Replaces any existing {@link #maxValue} with the new time and refreshes the store. + * @param {Date/String} value The maximum time that can be selected + */ + setMaxValue: function(value, /* private */ initial){ + this.setLimit(value, false, initial); + return this; + }, + + // private + 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 + 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(), isMin ? 0 : 59, 0); + this[isMin ? 'minValue' : 'maxValue'] = val; + if(!initial){ + this.generateStore(); + } + } + }, + // inherited docs getValue : function(){ var v = Ext.form.TimeField.superclass.getValue.call(this); @@ -7264,8 +7658,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 +7722,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 +7797,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 +7898,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 +8019,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 @@ -7566,10 +8085,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 {