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