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