X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/src/widgets/Editor.js diff --git a/src/widgets/Editor.js b/src/widgets/Editor.js new file mode 100644 index 00000000..c9600222 --- /dev/null +++ b/src/widgets/Editor.js @@ -0,0 +1,385 @@ +/*! + * Ext JS Library 3.0.0 + * Copyright(c) 2006-2009 Ext JS, LLC + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @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); \ No newline at end of file