~dongpo-deng/sahana-eden/test

« back to all changes in this revision

Viewing changes to static/scripts/ext/src/widgets/form/HtmlEditor.js

  • Committer: Deng Dongpo
  • Date: 2010-08-01 09:29:44 UTC
  • Revision ID: dongpo@dhcp-21193.iis.sinica.edu.tw-20100801092944-8t9obt4xtl7otesb
initial

Show diffs side-by-side

added added

removed removed

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