source: trunk/web/addons/job_monarch/lib/extjs-30/src/widgets/form/HtmlEditor.js @ 625

Last change on this file since 625 was 625, checked in by ramonb, 15 years ago

lib/extjs-30:

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