HtmlEditor.js
上传用户:shuoshiled
上传日期:2018-01-28
资源大小:10124k
文件大小:37k
源码类别:

中间件编程

开发平台:

JavaScript

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