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