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