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