provide installation instructions
[extjs.git] / source / widgets / Editor.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 /**\r
10  * @class Ext.Editor\r
11  * @extends Ext.Component\r
12  * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic.\r
13  * @constructor\r
14  * Create a new Editor\r
15  * @param {Ext.form.Field} field The Field object (or descendant)\r
16  * @param {Object} config The config object\r
17  */\r
18 Ext.Editor = function(field, config){\r
19     this.field = field;\r
20     Ext.Editor.superclass.constructor.call(this, config);\r
21 };\r
22 \r
23 Ext.extend(Ext.Editor, Ext.Component, {\r
24     /**\r
25      * @cfg {Boolean/String} autoSize\r
26      * True for the editor to automatically adopt the size of the underlying field, "width" to adopt the width only,\r
27      * or "height" to adopt the height only (defaults to false)\r
28      */\r
29     /**\r
30      * @cfg {Boolean} revertInvalid\r
31      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field\r
32      * validation fails (defaults to true)\r
33      */\r
34     /**\r
35      * @cfg {Boolean} ignoreNoChange\r
36      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and\r
37      * the value has not changed (defaults to false).  Applies only to string values - edits for other data types\r
38      * will never be ignored.\r
39      */\r
40     /**\r
41      * @cfg {Boolean} hideEl\r
42      * False to keep the bound element visible while the editor is displayed (defaults to true)\r
43      */\r
44     /**\r
45      * @cfg {Mixed} value\r
46      * The data value of the underlying field (defaults to "")\r
47      */\r
48     value : "",\r
49     /**\r
50      * @cfg {String} alignment\r
51      * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?").\r
52      */\r
53     alignment: "c-c?",\r
54     /**\r
55      * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"\r
56      * for bottom-right shadow (defaults to "frame")\r
57      */\r
58     shadow : "frame",\r
59     /**\r
60      * @cfg {Boolean} constrain True to constrain the editor to the viewport\r
61      */\r
62     constrain : false,\r
63     /**\r
64      * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)\r
65      */\r
66     swallowKeys : true,\r
67     /**\r
68      * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed (defaults to false)\r
69      */\r
70     completeOnEnter : false,\r
71     /**\r
72      * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed (defaults to false)\r
73      */\r
74     cancelOnEsc : false,\r
75     /**\r
76      * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)\r
77      */\r
78     updateEl : false,\r
79 \r
80     initComponent : function(){\r
81         Ext.Editor.superclass.initComponent.call(this);\r
82         this.addEvents(\r
83             /**\r
84              * @event beforestartedit\r
85              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning\r
86              * false from the handler of this event.\r
87              * @param {Editor} this\r
88              * @param {Ext.Element} boundEl The underlying element bound to this editor\r
89              * @param {Mixed} value The field value being set\r
90              */\r
91             "beforestartedit",\r
92             /**\r
93              * @event startedit\r
94              * Fires when this editor is displayed\r
95              * @param {Ext.Element} boundEl The underlying element bound to this editor\r
96              * @param {Mixed} value The starting field value\r
97              */\r
98             "startedit",\r
99             /**\r
100              * @event beforecomplete\r
101              * Fires after a change has been made to the field, but before the change is reflected in the underlying\r
102              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.\r
103              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this\r
104              * event will not fire since no edit actually occurred.\r
105              * @param {Editor} this\r
106              * @param {Mixed} value The current field value\r
107              * @param {Mixed} startValue The original field value\r
108              */\r
109             "beforecomplete",\r
110             /**\r
111              * @event complete\r
112              * Fires after editing is complete and any changed value has been written to the underlying field.\r
113              * @param {Editor} this\r
114              * @param {Mixed} value The current field value\r
115              * @param {Mixed} startValue The original field value\r
116              */\r
117             "complete",\r
118             /**\r
119              * @event canceledit\r
120              * Fires after editing has been canceled and the editor's value has been reset.\r
121              * @param {Editor} this\r
122              * @param {Mixed} value The user-entered field value that was discarded\r
123              * @param {Mixed} startValue The original field value that was set back into the editor after cancel\r
124              */\r
125             "canceledit",\r
126             /**\r
127              * @event specialkey\r
128              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check\r
129              * {@link Ext.EventObject#getKey} to determine which key was pressed.\r
130              * @param {Ext.form.Field} this\r
131              * @param {Ext.EventObject} e The event object\r
132              */\r
133             "specialkey"\r
134         );\r
135     },\r
136 \r
137     // private\r
138     onRender : function(ct, position){\r
139         this.el = new Ext.Layer({\r
140             shadow: this.shadow,\r
141             cls: "x-editor",\r
142             parentEl : ct,\r
143             shim : this.shim,\r
144             shadowOffset:4,\r
145             id: this.id,\r
146             constrain: this.constrain\r
147         });\r
148         this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden");\r
149         if(this.field.msgTarget != 'title'){\r
150             this.field.msgTarget = 'qtip';\r
151         }\r
152         this.field.inEditor = true;\r
153         this.field.render(this.el);\r
154         if(Ext.isGecko){\r
155             this.field.el.dom.setAttribute('autocomplete', 'off');\r
156         }\r
157         this.field.on("specialkey", this.onSpecialKey, this);\r
158         if(this.swallowKeys){\r
159             this.field.el.swallowEvent(['keydown','keypress']);\r
160         }\r
161         this.field.show();\r
162         this.field.on("blur", this.onBlur, this);\r
163         if(this.field.grow){\r
164             this.field.on("autosize", this.el.sync,  this.el, {delay:1});\r
165         }\r
166     },\r
167 \r
168     // private\r
169     onSpecialKey : function(field, e){\r
170         var key = e.getKey();\r
171         if(this.completeOnEnter && key == e.ENTER){\r
172             e.stopEvent();\r
173             this.completeEdit();\r
174         }else if(this.cancelOnEsc && key == e.ESC){\r
175             this.cancelEdit();\r
176         }else{\r
177             this.fireEvent('specialkey', field, e);\r
178         }\r
179         if(this.field.triggerBlur && (key == e.ENTER || key == e.ESC || key == e.TAB)){\r
180             this.field.triggerBlur();\r
181         }\r
182     },\r
183 \r
184     /**\r
185      * Starts the editing process and shows the editor.\r
186      * @param {Mixed} el The element to edit\r
187      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults\r
188       * to the innerHTML of el.\r
189      */\r
190     startEdit : function(el, value){\r
191         if(this.editing){\r
192             this.completeEdit();\r
193         }\r
194         this.boundEl = Ext.get(el);\r
195         var v = value !== undefined ? value : this.boundEl.dom.innerHTML;\r
196         if(!this.rendered){\r
197             this.render(this.parentEl || document.body);\r
198         }\r
199         if(this.fireEvent("beforestartedit", this, this.boundEl, v) === false){\r
200             return;\r
201         }\r
202         this.startValue = v;\r
203         this.field.setValue(v);\r
204         this.doAutoSize();\r
205         this.el.alignTo(this.boundEl, this.alignment);\r
206         this.editing = true;\r
207         this.show();\r
208     },\r
209 \r
210     // private\r
211     doAutoSize : function(){\r
212         if(this.autoSize){\r
213             var sz = this.boundEl.getSize();\r
214             switch(this.autoSize){\r
215                 case "width":\r
216                     this.setSize(sz.width,  "");\r
217                 break;\r
218                 case "height":\r
219                     this.setSize("",  sz.height);\r
220                 break;\r
221                 default:\r
222                     this.setSize(sz.width,  sz.height);\r
223             }\r
224         }\r
225     },\r
226 \r
227     /**\r
228      * Sets the height and width of this editor.\r
229      * @param {Number} width The new width\r
230      * @param {Number} height The new height\r
231      */\r
232     setSize : function(w, h){\r
233         delete this.field.lastSize;\r
234         this.field.setSize(w, h);\r
235         if(this.el){\r
236                 if(Ext.isGecko2 || Ext.isOpera){\r
237                     // prevent layer scrollbars\r
238                     this.el.setSize(w, h);\r
239                 }\r
240             this.el.sync();\r
241         }\r
242     },\r
243 \r
244     /**\r
245      * Realigns the editor to the bound field based on the current alignment config value.\r
246      */\r
247     realign : function(){\r
248         this.el.alignTo(this.boundEl, this.alignment);\r
249     },\r
250 \r
251     /**\r
252      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.\r
253      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false)\r
254      */\r
255     completeEdit : function(remainVisible){\r
256         if(!this.editing){\r
257             return;\r
258         }\r
259         var v = this.getValue();\r
260         if(this.revertInvalid !== false && !this.field.isValid()){\r
261             v = this.startValue;\r
262             this.cancelEdit(true);\r
263         }\r
264         if(String(v) === String(this.startValue) && this.ignoreNoChange){\r
265             this.editing = false;\r
266             this.hide();\r
267             return;\r
268         }\r
269         if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){\r
270             this.editing = false;\r
271             if(this.updateEl && this.boundEl){\r
272                 this.boundEl.update(v);\r
273             }\r
274             if(remainVisible !== true){\r
275                 this.hide();\r
276             }\r
277             this.fireEvent("complete", this, v, this.startValue);\r
278         }\r
279     },\r
280 \r
281     // private\r
282     onShow : function(){\r
283         this.el.show();\r
284         if(this.hideEl !== false){\r
285             this.boundEl.hide();\r
286         }\r
287         this.field.show();\r
288         if(Ext.isIE && !this.fixIEFocus){ // IE has problems with focusing the first time\r
289             this.fixIEFocus = true;\r
290             this.deferredFocus.defer(50, this);\r
291         }else{\r
292             this.field.focus();\r
293         }\r
294         this.fireEvent("startedit", this.boundEl, this.startValue);\r
295     },\r
296 \r
297     deferredFocus : function(){\r
298         if(this.editing){\r
299             this.field.focus();\r
300         }\r
301     },\r
302 \r
303     /**\r
304      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be\r
305      * reverted to the original starting value.\r
306      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after\r
307      * cancel (defaults to false)\r
308      */\r
309     cancelEdit : function(remainVisible){\r
310         if(this.editing){\r
311             var v = this.getValue();\r
312             this.setValue(this.startValue);\r
313             if(remainVisible !== true){\r
314                 this.hide();\r
315             }\r
316             this.fireEvent("canceledit", this, v, this.startValue);\r
317         }\r
318     },\r
319 \r
320     // private\r
321     onBlur : function(){\r
322         if(this.allowBlur !== true && this.editing){\r
323             this.completeEdit();\r
324         }\r
325     },\r
326 \r
327     // private\r
328     onHide : function(){\r
329         if(this.editing){\r
330             this.completeEdit();\r
331             return;\r
332         }\r
333         this.field.blur();\r
334         if(this.field.collapse){\r
335             this.field.collapse();\r
336         }\r
337         this.el.hide();\r
338         if(this.hideEl !== false){\r
339             this.boundEl.show();\r
340         }\r
341     },\r
342 \r
343     /**\r
344      * Sets the data value of the editor\r
345      * @param {Mixed} value Any valid value supported by the underlying field\r
346      */\r
347     setValue : function(v){\r
348         this.field.setValue(v);\r
349     },\r
350 \r
351     /**\r
352      * Gets the data value of the editor\r
353      * @return {Mixed} The data value\r
354      */\r
355     getValue : function(){\r
356         return this.field.getValue();\r
357     },\r
358 \r
359     beforeDestroy : function(){\r
360         Ext.destroy(this.field);\r
361         this.field = null;\r
362     }\r
363 });\r
364 Ext.reg('editor', Ext.Editor);