provide installation instructions
[extjs.git] / source / widgets / form / HtmlEditor.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.form.HtmlEditor\r
11  * @extends Ext.form.Field\r
12  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be \r
13  * automatically hidden when needed.  These are noted in the config options where appropriate.\r
14  * <br><br>The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not \r
15  * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}.\r
16  * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT\r
17  * supported by this editor.</b>\r
18  * <br><br>An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within\r
19  * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.\r
20  * <br><br>Example usage:\r
21  * <pre><code>\r
22 // Simple example rendered with default options:\r
23 Ext.QuickTips.init();  // enable tooltips\r
24 new Ext.form.HtmlEditor({\r
25     renderTo: Ext.getBody(),\r
26     width: 800,\r
27     height: 300\r
28 });\r
29 \r
30 // Passed via xtype into a container and with custom options:\r
31 Ext.QuickTips.init();  // enable tooltips\r
32 new Ext.Panel({\r
33     title: 'HTML Editor',\r
34     renderTo: Ext.getBody(),\r
35     width: 600,\r
36     height: 300,\r
37     frame: true,\r
38     layout: 'fit',\r
39     items: {\r
40         xtype: 'htmleditor',\r
41         enableColors: false,\r
42         enableAlignments: false\r
43     }\r
44 });\r
45 </code></pre>\r
46  * @constructor\r
47  * Create a new HtmlEditor\r
48  * @param {Object} config\r
49  */\r
50 \r
51 Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {\r
52     /**\r
53      * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)\r
54      */\r
55     enableFormat : true,\r
56     /**\r
57      * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)\r
58      */\r
59     enableFontSize : true,\r
60     /**\r
61      * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)\r
62      */\r
63     enableColors : true,\r
64     /**\r
65      * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)\r
66      */\r
67     enableAlignments : true,\r
68     /**\r
69      * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)\r
70      */\r
71     enableLists : true,\r
72     /**\r
73      * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)\r
74      */\r
75     enableSourceEdit : true,\r
76     /**\r
77      * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)\r
78      */\r
79     enableLinks : true,\r
80     /**\r
81      * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)\r
82      */\r
83     enableFont : true,\r
84     /**\r
85      * @cfg {String} createLinkText The default text for the create link prompt\r
86      */\r
87     createLinkText : 'Please enter the URL for the link:',\r
88     /**\r
89      * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)\r
90      */\r
91     defaultLinkValue : 'http:/'+'/',\r
92     /**\r
93      * @cfg {Array} fontFamilies An array of available font families\r
94      */\r
95     fontFamilies : [\r
96         'Arial',\r
97         'Courier New',\r
98         'Tahoma',\r
99         'Times New Roman',\r
100         'Verdana'\r
101     ],\r
102     defaultFont: 'tahoma',\r
103 \r
104     // private properties\r
105     validationEvent : false,\r
106     deferHeight: true,\r
107     initialized : false,\r
108     activated : false,\r
109     sourceEditMode : false,\r
110     onFocus : Ext.emptyFn,\r
111     iframePad:3,\r
112     hideMode:'offsets',\r
113     defaultAutoCreate : {\r
114         tag: "textarea",\r
115         style:"width:500px;height:300px;",\r
116         autocomplete: "off"\r
117     },\r
118 \r
119     // private\r
120     initComponent : function(){\r
121         this.addEvents(\r
122             /**\r
123              * @event initialize\r
124              * Fires when the editor is fully initialized (including the iframe)\r
125              * @param {HtmlEditor} this\r
126              */\r
127             'initialize',\r
128             /**\r
129              * @event activate\r
130              * Fires when the editor is first receives the focus. Any insertion must wait\r
131              * until after this event.\r
132              * @param {HtmlEditor} this\r
133              */\r
134             'activate',\r
135              /**\r
136              * @event beforesync\r
137              * Fires before the textarea is updated with content from the editor iframe. Return false\r
138              * to cancel the sync.\r
139              * @param {HtmlEditor} this\r
140              * @param {String} html\r
141              */\r
142             'beforesync',\r
143              /**\r
144              * @event beforepush\r
145              * Fires before the iframe editor is updated with content from the textarea. Return false\r
146              * to cancel the push.\r
147              * @param {HtmlEditor} this\r
148              * @param {String} html\r
149              */\r
150             'beforepush',\r
151              /**\r
152              * @event sync\r
153              * Fires when the textarea is updated with content from the editor iframe.\r
154              * @param {HtmlEditor} this\r
155              * @param {String} html\r
156              */\r
157             'sync',\r
158              /**\r
159              * @event push\r
160              * Fires when the iframe editor is updated with content from the textarea.\r
161              * @param {HtmlEditor} this\r
162              * @param {String} html\r
163              */\r
164             'push',\r
165              /**\r
166              * @event editmodechange\r
167              * Fires when the editor switches edit modes\r
168              * @param {HtmlEditor} this\r
169              * @param {Boolean} sourceEdit True if source edit, false if standard editing.\r
170              */\r
171             'editmodechange'\r
172         )\r
173     },\r
174 \r
175     // private\r
176     createFontOptions : function(){\r
177         var buf = [], fs = this.fontFamilies, ff, lc;\r
178         for(var i = 0, len = fs.length; i< len; i++){\r
179             ff = fs[i];\r
180             lc = ff.toLowerCase();\r
181             buf.push(\r
182                 '<option value="',lc,'" style="font-family:',ff,';"',\r
183                     (this.defaultFont == lc ? ' selected="true">' : '>'),\r
184                     ff,\r
185                 '</option>'\r
186             );\r
187         }\r
188         return buf.join('');\r
189     },\r
190     \r
191     /*\r
192      * Protected method that will not generally be called directly. It\r
193      * is called when the editor creates its toolbar. Override this method if you need to\r
194      * add custom toolbar buttons.\r
195      * @param {HtmlEditor} editor\r
196      */\r
197     createToolbar : function(editor){\r
198         \r
199         var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();\r
200         \r
201         function btn(id, toggle, handler){\r
202             return {\r
203                 itemId : id,\r
204                 cls : 'x-btn-icon x-edit-'+id,\r
205                 enableToggle:toggle !== false,\r
206                 scope: editor,\r
207                 handler:handler||editor.relayBtnCmd,\r
208                 clickEvent:'mousedown',\r
209                 tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,\r
210                 tabIndex:-1\r
211             };\r
212         }\r
213 \r
214         // build the toolbar\r
215         var tb = new Ext.Toolbar({\r
216             renderTo:this.wrap.dom.firstChild\r
217         });\r
218 \r
219         // stop form submits\r
220         tb.el.on('click', function(e){\r
221             e.preventDefault();\r
222         });\r
223 \r
224         if(this.enableFont && !Ext.isSafari2){\r
225             this.fontSelect = tb.el.createChild({\r
226                 tag:'select',\r
227                 cls:'x-font-select',\r
228                 html: this.createFontOptions()\r
229             });\r
230             this.fontSelect.on('change', function(){\r
231                 var font = this.fontSelect.dom.value;\r
232                 this.relayCmd('fontname', font);\r
233                 this.deferFocus();\r
234             }, this);\r
235             tb.add(\r
236                 this.fontSelect.dom,\r
237                 '-'\r
238             );\r
239         };\r
240 \r
241         if(this.enableFormat){\r
242             tb.add(\r
243                 btn('bold'),\r
244                 btn('italic'),\r
245                 btn('underline')\r
246             );\r
247         };\r
248 \r
249         if(this.enableFontSize){\r
250             tb.add(\r
251                 '-',\r
252                 btn('increasefontsize', false, this.adjustFont),\r
253                 btn('decreasefontsize', false, this.adjustFont)\r
254             );\r
255         };\r
256 \r
257         if(this.enableColors){\r
258             tb.add(\r
259                 '-', {\r
260                     itemId:'forecolor',\r
261                     cls:'x-btn-icon x-edit-forecolor',\r
262                     clickEvent:'mousedown',\r
263                     tooltip: tipsEnabled ? editor.buttonTips['forecolor'] || undefined : undefined,\r
264                     tabIndex:-1,\r
265                     menu : new Ext.menu.ColorMenu({\r
266                         allowReselect: true,\r
267                         focus: Ext.emptyFn,\r
268                         value:'000000',\r
269                         plain:true,\r
270                         selectHandler: function(cp, color){\r
271                             this.execCmd('forecolor', Ext.isSafari || Ext.isIE ? '#'+color : color);\r
272                             this.deferFocus();\r
273                         },\r
274                         scope: this,\r
275                         clickEvent:'mousedown'\r
276                     })\r
277                 }, {\r
278                     itemId:'backcolor',\r
279                     cls:'x-btn-icon x-edit-backcolor',\r
280                     clickEvent:'mousedown',\r
281                     tooltip: tipsEnabled ? editor.buttonTips['backcolor'] || undefined : undefined,\r
282                     tabIndex:-1,\r
283                     menu : new Ext.menu.ColorMenu({\r
284                         focus: Ext.emptyFn,\r
285                         value:'FFFFFF',\r
286                         plain:true,\r
287                         allowReselect: true,\r
288                         selectHandler: function(cp, color){\r
289                             if(Ext.isGecko){\r
290                                 this.execCmd('useCSS', false);\r
291                                 this.execCmd('hilitecolor', color);\r
292                                 this.execCmd('useCSS', true);\r
293                                 this.deferFocus();\r
294                             }else{\r
295                                 this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isSafari || Ext.isIE ? '#'+color : color);\r
296                                 this.deferFocus();\r
297                             }\r
298                         },\r
299                         scope:this,\r
300                         clickEvent:'mousedown'\r
301                     })\r
302                 }\r
303             );\r
304         };\r
305 \r
306         if(this.enableAlignments){\r
307             tb.add(\r
308                 '-',\r
309                 btn('justifyleft'),\r
310                 btn('justifycenter'),\r
311                 btn('justifyright')\r
312             );\r
313         };\r
314 \r
315         if(!Ext.isSafari2){\r
316             if(this.enableLinks){\r
317                 tb.add(\r
318                     '-',\r
319                     btn('createlink', false, this.createLink)\r
320                 );\r
321             };\r
322 \r
323             if(this.enableLists){\r
324                 tb.add(\r
325                     '-',\r
326                     btn('insertorderedlist'),\r
327                     btn('insertunorderedlist')\r
328                 );\r
329             }\r
330             if(this.enableSourceEdit){\r
331                 tb.add(\r
332                     '-',\r
333                     btn('sourceedit', true, function(btn){\r
334                         this.toggleSourceEdit(btn.pressed);\r
335                     })\r
336                 );\r
337             }\r
338         }\r
339 \r
340         this.tb = tb;\r
341     },\r
342 \r
343     /**\r
344      * Protected method that will not generally be called directly. It\r
345      * is called when the editor initializes the iframe with HTML contents. Override this method if you\r
346      * want to change the initialization markup of the iframe (e.g. to add stylesheets).\r
347      */\r
348     getDocMarkup : function(){\r
349         return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style></head><body></body></html>';\r
350     },\r
351 \r
352     // private\r
353     getEditorBody : function(){\r
354         return this.doc.body || this.doc.documentElement;\r
355     },\r
356 \r
357     // private\r
358     getDoc : function(){\r
359         return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);\r
360     },\r
361 \r
362     // private\r
363     getWin : function(){\r
364         return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];\r
365     },\r
366 \r
367     // private\r
368     onRender : function(ct, position){\r
369         Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);\r
370         this.el.dom.style.border = '0 none';\r
371         this.el.dom.setAttribute('tabIndex', -1);\r
372         this.el.addClass('x-hidden');\r
373         if(Ext.isIE){ // fix IE 1px bogus margin\r
374             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')\r
375         }\r
376         this.wrap = this.el.wrap({\r
377             cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}\r
378         });\r
379 \r
380         this.createToolbar(this);\r
381 \r
382         this.tb.items.each(function(item){\r
383            if(item.itemId != 'sourceedit'){\r
384                 item.disable();\r
385             }\r
386         });\r
387 \r
388         var iframe = document.createElement('iframe');\r
389         iframe.name = Ext.id();\r
390         iframe.frameBorder = '0';\r
391 \r
392         iframe.src = Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;";\r
393 \r
394         this.wrap.dom.appendChild(iframe);\r
395 \r
396         this.iframe = iframe;\r
397 \r
398         this.initFrame();\r
399 \r
400         if(this.autoMonitorDesignMode !== false){\r
401             this.monitorTask = Ext.TaskMgr.start({\r
402                 run: this.checkDesignMode,\r
403                 scope: this,\r
404                 interval:100\r
405             });\r
406         }\r
407 \r
408         if(!this.width){\r
409             var sz = this.el.getSize();\r
410             this.setSize(sz.width, this.height || sz.height);\r
411         }\r
412     },\r
413 \r
414     initFrame : function(){\r
415         this.doc = this.getDoc();\r
416         this.win = this.getWin();\r
417 \r
418         this.doc.open();\r
419         this.doc.write(this.getDocMarkup());\r
420         this.doc.close();\r
421 \r
422         var task = { // must defer to wait for browser to be ready\r
423             run : function(){\r
424                 if(this.doc.body || this.doc.readyState == 'complete'){\r
425                     Ext.TaskMgr.stop(task);\r
426                     this.doc.designMode="on";\r
427                     this.initEditor.defer(10, this);\r
428                 }\r
429             },\r
430             interval : 10,\r
431             duration:10000,\r
432             scope: this\r
433         };\r
434         Ext.TaskMgr.start(task);\r
435     },\r
436 \r
437 \r
438     checkDesignMode : function(){\r
439         if(this.wrap && this.wrap.dom.offsetWidth){\r
440             var doc = this.getDoc();\r
441             if(!doc){\r
442                 return;\r
443             }\r
444             if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){\r
445                 this.initFrame();\r
446             }\r
447         }\r
448     },\r
449 \r
450     // private\r
451     onResize : function(w, h){\r
452         Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);\r
453         if(this.el && this.iframe){\r
454             if(typeof w == 'number'){\r
455                 var aw = w - this.wrap.getFrameWidth('lr');\r
456                 this.el.setWidth(this.adjustWidth('textarea', aw));\r
457                 this.iframe.style.width = Math.max(aw, 0) + 'px';\r
458             }\r
459             if(typeof h == 'number'){\r
460                 var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();\r
461                 this.el.setHeight(this.adjustWidth('textarea', ah));\r
462                 this.iframe.style.height = Math.max(ah, 0) + 'px';\r
463                 if(this.doc){\r
464                     this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';\r
465                 }\r
466             }\r
467         }\r
468     },\r
469 \r
470     /**\r
471      * Toggles the editor between standard and source edit mode.\r
472      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard\r
473      */\r
474     toggleSourceEdit : function(sourceEditMode){\r
475         if(sourceEditMode === undefined){\r
476             sourceEditMode = !this.sourceEditMode;\r
477         }\r
478         this.sourceEditMode = sourceEditMode === true;\r
479         var btn = this.tb.items.get('sourceedit');\r
480         if(btn.pressed !== this.sourceEditMode){\r
481             btn.toggle(this.sourceEditMode);\r
482             return;\r
483         }\r
484         if(this.sourceEditMode){\r
485             this.tb.items.each(function(item){\r
486                 if(item.itemId != 'sourceedit'){\r
487                     item.disable();\r
488                 }\r
489             });\r
490             this.syncValue();\r
491             this.iframe.className = 'x-hidden';\r
492             this.el.removeClass('x-hidden');\r
493             this.el.dom.removeAttribute('tabIndex');\r
494             this.el.focus();\r
495         }else{\r
496             if(this.initialized){\r
497                 this.tb.items.each(function(item){\r
498                     item.enable();\r
499                 });\r
500             }\r
501             this.pushValue();\r
502             this.iframe.className = '';\r
503             this.el.addClass('x-hidden');\r
504             this.el.dom.setAttribute('tabIndex', -1);\r
505             this.deferFocus();\r
506         }\r
507         var lastSize = this.lastSize;\r
508         if(lastSize){\r
509             delete this.lastSize;\r
510             this.setSize(lastSize);\r
511         }\r
512         this.fireEvent('editmodechange', this, this.sourceEditMode);\r
513     },\r
514 \r
515     // private used internally\r
516     createLink : function(){\r
517         var url = prompt(this.createLinkText, this.defaultLinkValue);\r
518         if(url && url != 'http:/'+'/'){\r
519             this.relayCmd('createlink', url);\r
520         }\r
521     },\r
522 \r
523     // private (for BoxComponent)\r
524     adjustSize : Ext.BoxComponent.prototype.adjustSize,\r
525 \r
526     // private (for BoxComponent)\r
527     getResizeEl : function(){\r
528         return this.wrap;\r
529     },\r
530 \r
531     // private (for BoxComponent)\r
532     getPositionEl : function(){\r
533         return this.wrap;\r
534     },\r
535 \r
536     // private\r
537     initEvents : function(){\r
538         this.originalValue = this.getValue();\r
539     },\r
540 \r
541     /**\r
542      * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide\r
543      * @method\r
544      */\r
545     markInvalid : Ext.emptyFn,\r
546     \r
547     /**\r
548      * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide\r
549      * @method\r
550      */\r
551     clearInvalid : Ext.emptyFn,\r
552 \r
553     // docs inherit from Field\r
554     setValue : function(v){\r
555         Ext.form.HtmlEditor.superclass.setValue.call(this, v);\r
556         this.pushValue();\r
557     },\r
558 \r
559     /**\r
560      * Protected method that will not generally be called directly. If you need/want\r
561      * custom HTML cleanup, this is the method you should override.\r
562      * @param {String} html The HTML to be cleaned\r
563      * @return {String} The cleaned HTML\r
564      */\r
565     cleanHtml : function(html){\r
566         html = String(html);\r
567         if(html.length > 5){\r
568             if(Ext.isSafari){ // strip safari nonsense\r
569                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');\r
570             }\r
571         }\r
572         if(html == '&nbsp;'){\r
573             html = '';\r
574         }\r
575         return html;\r
576     },\r
577 \r
578     /**\r
579      * Protected method that will not generally be called directly. Syncs the contents\r
580      * of the editor iframe with the textarea.\r
581      */\r
582     syncValue : function(){\r
583         if(this.initialized){\r
584             var bd = this.getEditorBody();\r
585             var html = bd.innerHTML;\r
586             if(Ext.isSafari){\r
587                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!\r
588                 var m = bs.match(/text-align:(.*?);/i);\r
589                 if(m && m[1]){\r
590                     html = '<div style="'+m[0]+'">' + html + '</div>';\r
591                 }\r
592             }\r
593             html = this.cleanHtml(html);\r
594             if(this.fireEvent('beforesync', this, html) !== false){\r
595                 this.el.dom.value = html;\r
596                 this.fireEvent('sync', this, html);\r
597             }\r
598         }\r
599     },\r
600     \r
601     //docs inherit from Field\r
602     getValue : function() {\r
603         this.syncValue();\r
604         return Ext.form.HtmlEditor.superclass.getValue.call(this);\r
605     },\r
606 \r
607 \r
608     /**\r
609      * Protected method that will not generally be called directly. Pushes the value of the textarea\r
610      * into the iframe editor.\r
611      */\r
612     pushValue : function(){\r
613         if(this.initialized){\r
614             var v = this.el.dom.value;\r
615             if(!this.activated && v.length < 1){\r
616                 v = '&nbsp;';\r
617             }\r
618             if(this.fireEvent('beforepush', this, v) !== false){\r
619                 this.getEditorBody().innerHTML = v;\r
620                 this.fireEvent('push', this, v);\r
621             }\r
622         }\r
623     },\r
624 \r
625     // private\r
626     deferFocus : function(){\r
627         this.focus.defer(10, this);\r
628     },\r
629 \r
630     // docs inherit from Field\r
631     focus : function(){\r
632         if(this.win && !this.sourceEditMode){\r
633             this.win.focus();\r
634         }else{\r
635             this.el.focus();\r
636         }\r
637     },\r
638 \r
639     // private\r
640     initEditor : function(){\r
641         var dbody = this.getEditorBody();\r
642         var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');\r
643         ss['background-attachment'] = 'fixed'; // w3c\r
644         dbody.bgProperties = 'fixed'; // ie\r
645 \r
646         Ext.DomHelper.applyStyles(dbody, ss);\r
647 \r
648         if(this.doc){\r
649             try{\r
650                 Ext.EventManager.removeAll(this.doc);\r
651             }catch(e){}\r
652         }\r
653 \r
654         this.doc = this.getDoc();\r
655 \r
656         Ext.EventManager.on(this.doc, {\r
657             'mousedown': this.onEditorEvent,\r
658             'dblclick': this.onEditorEvent,\r
659             'click': this.onEditorEvent,\r
660             'keyup': this.onEditorEvent,\r
661             buffer:100,\r
662             scope: this\r
663         });\r
664 \r
665         if(Ext.isGecko){\r
666             Ext.EventManager.on(this.doc, 'keypress', this.applyCommand, this);\r
667         }\r
668         if(Ext.isIE || Ext.isSafari || Ext.isOpera){\r
669             Ext.EventManager.on(this.doc, 'keydown', this.fixKeys, this);\r
670         }\r
671         this.initialized = true;\r
672 \r
673         this.fireEvent('initialize', this);\r
674 \r
675         this.doc.editorInitialized = true;\r
676 \r
677         this.pushValue();\r
678     },\r
679 \r
680     // private\r
681     onDestroy : function(){\r
682         if(this.monitorTask){\r
683             Ext.TaskMgr.stop(this.monitorTask);\r
684         }\r
685         if(this.rendered){\r
686             this.tb.items.each(function(item){\r
687                 if(item.menu){\r
688                     item.menu.removeAll();\r
689                     if(item.menu.el){\r
690                         item.menu.el.destroy();\r
691                     }\r
692                 }\r
693                 item.destroy();\r
694             });\r
695             this.wrap.dom.innerHTML = '';\r
696             this.wrap.remove();\r
697         }\r
698     },\r
699 \r
700     // private\r
701     onFirstFocus : function(){\r
702         this.activated = true;\r
703         this.tb.items.each(function(item){\r
704            item.enable();\r
705         });\r
706         if(Ext.isGecko){ // prevent silly gecko errors\r
707             this.win.focus();\r
708             var s = this.win.getSelection();\r
709             if(!s.focusNode || s.focusNode.nodeType != 3){\r
710                 var r = s.getRangeAt(0);\r
711                 r.selectNodeContents(this.getEditorBody());\r
712                 r.collapse(true);\r
713                 this.deferFocus();\r
714             }\r
715             try{\r
716                 this.execCmd('useCSS', true);\r
717                 this.execCmd('styleWithCSS', false);\r
718             }catch(e){}\r
719         }\r
720         this.fireEvent('activate', this);\r
721     },\r
722 \r
723     // private\r
724     adjustFont: function(btn){\r
725         var adjust = btn.itemId == 'increasefontsize' ? 1 : -1;\r
726 \r
727         var v = parseInt(this.doc.queryCommandValue('FontSize') || 2, 10);\r
728         if(Ext.isSafari3 || Ext.isAir){\r
729             // Safari 3 values\r
730             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px\r
731             if(v <= 10){\r
732                 v = 1 + adjust;\r
733             }else if(v <= 13){\r
734                 v = 2 + adjust;\r
735             }else if(v <= 16){\r
736                 v = 3 + adjust;\r
737             }else if(v <= 18){\r
738                 v = 4 + adjust;\r
739             }else if(v <= 24){\r
740                 v = 5 + adjust;\r
741             }else {\r
742                 v = 6 + adjust;\r
743             }\r
744             v = v.constrain(1, 6);\r
745         }else{\r
746             if(Ext.isSafari){ // safari\r
747                 adjust *= 2;\r
748             }\r
749             v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0);\r
750         }\r
751         this.execCmd('FontSize', v);\r
752     },\r
753 \r
754     // private\r
755     onEditorEvent : function(e){\r
756         this.updateToolbar();\r
757     },\r
758 \r
759 \r
760     /**\r
761      * Protected method that will not generally be called directly. It triggers\r
762      * a toolbar update by reading the markup state of the current selection in the editor.\r
763      */\r
764     updateToolbar: function(){\r
765 \r
766         if(!this.activated){\r
767             this.onFirstFocus();\r
768             return;\r
769         }\r
770 \r
771         var btns = this.tb.items.map, doc = this.doc;\r
772 \r
773         if(this.enableFont && !Ext.isSafari2){\r
774             var name = (this.doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase();\r
775             if(name != this.fontSelect.dom.value){\r
776                 this.fontSelect.dom.value = name;\r
777             }\r
778         }\r
779         if(this.enableFormat){\r
780             btns.bold.toggle(doc.queryCommandState('bold'));\r
781             btns.italic.toggle(doc.queryCommandState('italic'));\r
782             btns.underline.toggle(doc.queryCommandState('underline'));\r
783         }\r
784         if(this.enableAlignments){\r
785             btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));\r
786             btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));\r
787             btns.justifyright.toggle(doc.queryCommandState('justifyright'));\r
788         }\r
789         if(!Ext.isSafari2 && this.enableLists){\r
790             btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist'));\r
791             btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist'));\r
792         }\r
793         \r
794         Ext.menu.MenuMgr.hideAll();\r
795 \r
796         this.syncValue();\r
797     },\r
798 \r
799     // private\r
800     relayBtnCmd : function(btn){\r
801         this.relayCmd(btn.itemId);\r
802     },\r
803 \r
804     /**\r
805      * Executes a Midas editor command on the editor document and performs necessary focus and\r
806      * toolbar updates. <b>This should only be called after the editor is initialized.</b>\r
807      * @param {String} cmd The Midas command\r
808      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)\r
809      */\r
810     relayCmd : function(cmd, value){\r
811         (function(){\r
812             this.focus();\r
813             this.execCmd(cmd, value);\r
814             this.updateToolbar();\r
815         }).defer(10, this);\r
816     },\r
817 \r
818     /**\r
819      * Executes a Midas editor command directly on the editor document.\r
820      * For visual commands, you should use {@link #relayCmd} instead.\r
821      * <b>This should only be called after the editor is initialized.</b>\r
822      * @param {String} cmd The Midas command\r
823      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)\r
824      */\r
825     execCmd : function(cmd, value){\r
826         this.doc.execCommand(cmd, false, value === undefined ? null : value);\r
827         this.syncValue();\r
828     },\r
829 \r
830     // private\r
831     applyCommand : function(e){\r
832         if(e.ctrlKey){\r
833             var c = e.getCharCode(), cmd;\r
834             if(c > 0){\r
835                 c = String.fromCharCode(c);\r
836                 switch(c){\r
837                     case 'b':\r
838                         cmd = 'bold';\r
839                     break;\r
840                     case 'i':\r
841                         cmd = 'italic';\r
842                     break;\r
843                     case 'u':\r
844                         cmd = 'underline';\r
845                     break;\r
846                 }\r
847                 if(cmd){\r
848                     this.win.focus();\r
849                     this.execCmd(cmd);\r
850                     this.deferFocus();\r
851                     e.preventDefault();\r
852                 }\r
853             }\r
854         }\r
855     },\r
856 \r
857     /**\r
858      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated\r
859      * to insert text.\r
860      * @param {String} text\r
861      */\r
862     insertAtCursor : function(text){\r
863         if(!this.activated){\r
864             return;\r
865         }\r
866         if(Ext.isIE){\r
867             this.win.focus();\r
868             var r = this.doc.selection.createRange();\r
869             if(r){\r
870                 r.collapse(true);\r
871                 r.pasteHTML(text);\r
872                 this.syncValue();\r
873                 this.deferFocus();\r
874             }\r
875         }else if(Ext.isGecko || Ext.isOpera){\r
876             this.win.focus();\r
877             this.execCmd('InsertHTML', text);\r
878             this.deferFocus();\r
879         }else if(Ext.isSafari){\r
880             this.execCmd('InsertText', text);\r
881             this.deferFocus();\r
882         }\r
883     },\r
884 \r
885     // private\r
886     fixKeys : function(){ // load time branching for fastest keydown performance\r
887         if(Ext.isIE){\r
888             return function(e){\r
889                 var k = e.getKey(), r;\r
890                 if(k == e.TAB){\r
891                     e.stopEvent();\r
892                     r = this.doc.selection.createRange();\r
893                     if(r){\r
894                         r.collapse(true);\r
895                         r.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');\r
896                         this.deferFocus();\r
897                     }\r
898                 }else if(k == e.ENTER){\r
899                     r = this.doc.selection.createRange();\r
900                     if(r){\r
901                         var target = r.parentElement();\r
902                         if(!target || target.tagName.toLowerCase() != 'li'){\r
903                             e.stopEvent();\r
904                             r.pasteHTML('<br />');\r
905                             r.collapse(false);\r
906                             r.select();\r
907                         }\r
908                     }\r
909                 }\r
910             };\r
911         }else if(Ext.isOpera){\r
912             return function(e){\r
913                 var k = e.getKey();\r
914                 if(k == e.TAB){\r
915                     e.stopEvent();\r
916                     this.win.focus();\r
917                     this.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');\r
918                     this.deferFocus();\r
919                 }\r
920             };\r
921         }else if(Ext.isSafari){\r
922             return function(e){\r
923                 var k = e.getKey();\r
924                 if(k == e.TAB){\r
925                     e.stopEvent();\r
926                     this.execCmd('InsertText','\t');\r
927                     this.deferFocus();\r
928                 }\r
929              };\r
930         }\r
931     }(),\r
932 \r
933     /**\r
934      * Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>\r
935      * @return {Ext.Toolbar}\r
936      */\r
937     getToolbar : function(){\r
938         return this.tb;\r
939     },\r
940 \r
941     /**\r
942      * Object collection of toolbar tooltips for the buttons in the editor. The key\r
943      * is the command id associated with that button and the value is a valid QuickTips object.\r
944      * For example:\r
945 <pre><code>\r
946 {\r
947     bold : {\r
948         title: 'Bold (Ctrl+B)',\r
949         text: 'Make the selected text bold.',\r
950         cls: 'x-html-editor-tip'\r
951     },\r
952     italic : {\r
953         title: 'Italic (Ctrl+I)',\r
954         text: 'Make the selected text italic.',\r
955         cls: 'x-html-editor-tip'\r
956     },\r
957     ...\r
958 </code></pre>\r
959     * @type Object\r
960      */\r
961     buttonTips : {\r
962         bold : {\r
963             title: 'Bold (Ctrl+B)',\r
964             text: 'Make the selected text bold.',\r
965             cls: 'x-html-editor-tip'\r
966         },\r
967         italic : {\r
968             title: 'Italic (Ctrl+I)',\r
969             text: 'Make the selected text italic.',\r
970             cls: 'x-html-editor-tip'\r
971         },\r
972         underline : {\r
973             title: 'Underline (Ctrl+U)',\r
974             text: 'Underline the selected text.',\r
975             cls: 'x-html-editor-tip'\r
976         },\r
977         increasefontsize : {\r
978             title: 'Grow Text',\r
979             text: 'Increase the font size.',\r
980             cls: 'x-html-editor-tip'\r
981         },\r
982         decreasefontsize : {\r
983             title: 'Shrink Text',\r
984             text: 'Decrease the font size.',\r
985             cls: 'x-html-editor-tip'\r
986         },\r
987         backcolor : {\r
988             title: 'Text Highlight Color',\r
989             text: 'Change the background color of the selected text.',\r
990             cls: 'x-html-editor-tip'\r
991         },\r
992         forecolor : {\r
993             title: 'Font Color',\r
994             text: 'Change the color of the selected text.',\r
995             cls: 'x-html-editor-tip'\r
996         },\r
997         justifyleft : {\r
998             title: 'Align Text Left',\r
999             text: 'Align text to the left.',\r
1000             cls: 'x-html-editor-tip'\r
1001         },\r
1002         justifycenter : {\r
1003             title: 'Center Text',\r
1004             text: 'Center text in the editor.',\r
1005             cls: 'x-html-editor-tip'\r
1006         },\r
1007         justifyright : {\r
1008             title: 'Align Text Right',\r
1009             text: 'Align text to the right.',\r
1010             cls: 'x-html-editor-tip'\r
1011         },\r
1012         insertunorderedlist : {\r
1013             title: 'Bullet List',\r
1014             text: 'Start a bulleted list.',\r
1015             cls: 'x-html-editor-tip'\r
1016         },\r
1017         insertorderedlist : {\r
1018             title: 'Numbered List',\r
1019             text: 'Start a numbered list.',\r
1020             cls: 'x-html-editor-tip'\r
1021         },\r
1022         createlink : {\r
1023             title: 'Hyperlink',\r
1024             text: 'Make the selected text a hyperlink.',\r
1025             cls: 'x-html-editor-tip'\r
1026         },\r
1027         sourceedit : {\r
1028             title: 'Source Edit',\r
1029             text: 'Switch to source editing mode.',\r
1030             cls: 'x-html-editor-tip'\r
1031         }\r
1032     }\r
1033 \r
1034     // hide stuff that is not compatible\r
1035     /**\r
1036      * @event blur\r
1037      * @hide\r
1038      */\r
1039     /**\r
1040      * @event change\r
1041      * @hide\r
1042      */\r
1043     /**\r
1044      * @event focus\r
1045      * @hide\r
1046      */\r
1047     /**\r
1048      * @event specialkey\r
1049      * @hide\r
1050      */\r
1051     /**\r
1052      * @cfg {String} fieldClass @hide\r
1053      */\r
1054     /**\r
1055      * @cfg {String} focusClass @hide\r
1056      */\r
1057     /**\r
1058      * @cfg {String} autoCreate @hide\r
1059      */\r
1060     /**\r
1061      * @cfg {String} inputType @hide\r
1062      */\r
1063     /**\r
1064      * @cfg {String} invalidClass @hide\r
1065      */\r
1066     /**\r
1067      * @cfg {String} invalidText @hide\r
1068      */\r
1069     /**\r
1070      * @cfg {String} msgFx @hide\r
1071      */\r
1072     /**\r
1073      * @cfg {String} validateOnBlur @hide\r
1074      */\r
1075     /**\r
1076      * @cfg {Boolean} allowDomMove  @hide\r
1077      */\r
1078     /**\r
1079      * @cfg {String} applyTo @hide\r
1080      */\r
1081     /**\r
1082      * @cfg {String} autoHeight  @hide\r
1083      */\r
1084     /**\r
1085      * @cfg {String} autoWidth  @hide\r
1086      */\r
1087     /**\r
1088      * @cfg {String} cls  @hide\r
1089      */\r
1090     /**\r
1091      * @cfg {String} disabled  @hide\r
1092      */\r
1093     /**\r
1094      * @cfg {String} disabledClass  @hide\r
1095      */\r
1096     /**\r
1097      * @cfg {String} msgTarget  @hide\r
1098      */\r
1099     /**\r
1100      * @cfg {String} readOnly  @hide\r
1101      */\r
1102     /**\r
1103      * @cfg {String} style  @hide\r
1104      */\r
1105     /**\r
1106      * @cfg {String} validationDelay  @hide\r
1107      */\r
1108     /**\r
1109      * @cfg {String} validationEvent  @hide\r
1110      */\r
1111     /**\r
1112      * @cfg {String} tabIndex  @hide\r
1113      */\r
1114     /**\r
1115      * @property disabled\r
1116      * @hide\r
1117      */\r
1118     /**\r
1119      * @method applyToMarkup\r
1120      * @hide\r
1121      */\r
1122     /**\r
1123      * @method disable\r
1124      * @hide\r
1125      */\r
1126     /**\r
1127      * @method enable\r
1128      * @hide\r
1129      */\r
1130     /**\r
1131      * @method validate\r
1132      * @hide\r
1133      */\r
1134     /**\r
1135      * @event valid\r
1136      * @hide\r
1137      */\r
1138     /**\r
1139      * @method setDisabled\r
1140      * @hide\r
1141      */\r
1142     /**\r
1143      * @cfg keys\r
1144      * @hide\r
1145      */\r
1146 });\r
1147 Ext.reg('htmleditor', Ext.form.HtmlEditor);