/** * @class Ext.Editor * @extends Ext.Component * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic. * @constructor * Create a new Editor * @param {Object} config The config object * @xtype editor */ Ext.Editor = function(field, config){ if(field.field){ this.field = Ext.create(field.field, 'textfield'); config = Ext.apply({}, field); // copy so we don't disturb original config delete config.field; }else{ this.field = field; } Ext.Editor.superclass.constructor.call(this, config); }; Ext.extend(Ext.Editor, Ext.Component, { /** * @cfg {Ext.form.Field} field * The Field object (or descendant) or config object for field */ /** * @cfg {Boolean} allowBlur * True to {@link #completeEdit complete the editing process} if in edit mode when the * field is blurred. Defaults to false. */ /** * @cfg {Boolean/String} autoSize * True for the editor to automatically adopt the size of the element being edited, "width" to adopt the width only, * or "height" to adopt the height only (defaults to false) */ /** * @cfg {Boolean} revertInvalid * True to automatically revert the field value and cancel the edit when the user completes an edit and the field * validation fails (defaults to true) */ /** * @cfg {Boolean} ignoreNoChange * True to skip the edit completion process (no save, no events fired) if the user completes an edit and * the value has not changed (defaults to false). Applies only to string values - edits for other data types * will never be ignored. */ /** * @cfg {Boolean} hideEl * False to keep the bound element visible while the editor is displayed (defaults to true) */ /** * @cfg {Mixed} value * The data value of the underlying field (defaults to "") */ value : "", /** * @cfg {String} alignment * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?"). */ alignment: "c-c?", /** * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" * for bottom-right shadow (defaults to "frame") */ shadow : "frame", /** * @cfg {Boolean} constrain True to constrain the editor to the viewport */ constrain : false, /** * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true) */ swallowKeys : true, /** * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed (defaults to false) */ completeOnEnter : false, /** * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed (defaults to false) */ cancelOnEsc : false, /** * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false) */ updateEl : false, initComponent : function(){ Ext.Editor.superclass.initComponent.call(this); this.addEvents( /** * @event beforestartedit * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning * false from the handler of this event. * @param {Editor} this * @param {Ext.Element} boundEl The underlying element bound to this editor * @param {Mixed} value The field value being set */ "beforestartedit", /** * @event startedit * Fires when this editor is displayed * @param {Ext.Element} boundEl The underlying element bound to this editor * @param {Mixed} value The starting field value */ "startedit", /** * @event beforecomplete * Fires after a change has been made to the field, but before the change is reflected in the underlying * field. Saving the change to the field can be canceled by returning false from the handler of this event. * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this * event will not fire since no edit actually occurred. * @param {Editor} this * @param {Mixed} value The current field value * @param {Mixed} startValue The original field value */ "beforecomplete", /** * @event complete * Fires after editing is complete and any changed value has been written to the underlying field. * @param {Editor} this * @param {Mixed} value The current field value * @param {Mixed} startValue The original field value */ "complete", /** * @event canceledit * Fires after editing has been canceled and the editor's value has been reset. * @param {Editor} this * @param {Mixed} value The user-entered field value that was discarded * @param {Mixed} startValue The original field value that was set back into the editor after cancel */ "canceledit", /** * @event specialkey * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check * {@link Ext.EventObject#getKey} to determine which key was pressed. * @param {Ext.form.Field} this * @param {Ext.EventObject} e The event object */ "specialkey" ); }, // private onRender : function(ct, position){ this.el = new Ext.Layer({ shadow: this.shadow, cls: "x-editor", parentEl : ct, shim : this.shim, shadowOffset: this.shadowOffset || 4, id: this.id, constrain: this.constrain }); if(this.zIndex){ this.el.setZIndex(this.zIndex); } this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden"); if(this.field.msgTarget != 'title'){ this.field.msgTarget = 'qtip'; } this.field.inEditor = true; this.field.render(this.el); if(Ext.isGecko){ this.field.el.dom.setAttribute('autocomplete', 'off'); } this.mon(this.field, "specialkey", this.onSpecialKey, this); if(this.swallowKeys){ this.field.el.swallowEvent(['keydown','keypress']); } this.field.show(); this.mon(this.field, "blur", this.onBlur, this); if(this.field.grow){ this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1}); } }, // private onSpecialKey : function(field, e){ var key = e.getKey(); if(this.completeOnEnter && key == e.ENTER){ e.stopEvent(); this.completeEdit(); }else if(this.cancelOnEsc && key == e.ESC){ this.cancelEdit(); }else{ this.fireEvent('specialkey', field, e); } if(this.field.triggerBlur && (key == e.ENTER || key == e.ESC || key == e.TAB)){ this.field.triggerBlur(); } }, /** * Starts the editing process and shows the editor. * @param {Mixed} el The element to edit * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults * to the innerHTML of el. */ startEdit : function(el, value){ if(this.editing){ this.completeEdit(); } this.boundEl = Ext.get(el); var v = value !== undefined ? value : this.boundEl.dom.innerHTML; if(!this.rendered){ this.render(this.parentEl || document.body); } if(this.fireEvent("beforestartedit", this, this.boundEl, v) === false){ return; } this.startValue = v; this.field.setValue(v); this.doAutoSize(); this.el.alignTo(this.boundEl, this.alignment); this.editing = true; this.show(); }, // private doAutoSize : function(){ if(this.autoSize){ var sz = this.boundEl.getSize(); switch(this.autoSize){ case "width": this.setSize(sz.width, ""); break; case "height": this.setSize("", sz.height); break; default: this.setSize(sz.width, sz.height); } } }, /** * Sets the height and width of this editor. * @param {Number} width The new width * @param {Number} height The new height */ setSize : function(w, h){ delete this.field.lastSize; this.field.setSize(w, h); if(this.el){ if(Ext.isGecko2 || Ext.isOpera){ // prevent layer scrollbars this.el.setSize(w, h); } this.el.sync(); } }, /** * Realigns the editor to the bound field based on the current alignment config value. */ realign : function(){ this.el.alignTo(this.boundEl, this.alignment); }, /** * Ends the editing process, persists the changed value to the underlying field, and hides the editor. * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false) */ completeEdit : function(remainVisible){ if(!this.editing){ return; } var v = this.getValue(); if(!this.field.isValid()){ if(this.revertInvalid !== false){ this.cancelEdit(remainVisible); } return; } if(String(v) === String(this.startValue) && this.ignoreNoChange){ this.hideEdit(remainVisible); return; } if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){ v = this.getValue(); if(this.updateEl && this.boundEl){ this.boundEl.update(v); } this.hideEdit(remainVisible); this.fireEvent("complete", this, v, this.startValue); } }, // private onShow : function(){ this.el.show(); if(this.hideEl !== false){ this.boundEl.hide(); } this.field.show(); if(Ext.isIE && !this.fixIEFocus){ // IE has problems with focusing the first time this.fixIEFocus = true; this.deferredFocus.defer(50, this); }else{ this.field.focus(); } this.fireEvent("startedit", this.boundEl, this.startValue); }, deferredFocus : function(){ if(this.editing){ this.field.focus(); } }, /** * Cancels the editing process and hides the editor without persisting any changes. The field value will be * reverted to the original starting value. * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after * cancel (defaults to false) */ cancelEdit : function(remainVisible){ if(this.editing){ var v = this.getValue(); this.setValue(this.startValue); this.hideEdit(remainVisible); this.fireEvent("canceledit", this, v, this.startValue); } }, // private hideEdit: function(remainVisible){ if(remainVisible !== true){ this.editing = false; this.hide(); } }, // private onBlur : function(){ if(this.allowBlur !== true && this.editing){ this.completeEdit(); } }, // private onHide : function(){ if(this.editing){ this.completeEdit(); return; } this.field.blur(); if(this.field.collapse){ this.field.collapse(); } this.el.hide(); if(this.hideEl !== false){ this.boundEl.show(); } }, /** * Sets the data value of the editor * @param {Mixed} value Any valid value supported by the underlying field */ setValue : function(v){ this.field.setValue(v); }, /** * Gets the data value of the editor * @return {Mixed} The data value */ getValue : function(){ return this.field.getValue(); }, beforeDestroy : function(){ Ext.destroy(this.field); this.field = null; } }); Ext.reg('editor', Ext.Editor);