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