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