第 6 步

返回富文本编辑器的基本原理与实践

    1 if (!YAHOO.realazy)
    2     YAHOO.realazy = {};
    3 
    4 if (!YAHOO.realazy.RTE)
    5     YAHOO.realazy.RTE = {};
    6 
    7 (function(){
    8     var $D = YAHOO.util.Dom,
    9         $E = YAHOO.util.Event;
   10 
   11     var ua = YAHOO.env.ua,
   12         isIE = ua.ie,
   13         isGecko = ua.gecko,
   14         isOpera = ua.opera,
   15         isWebkit = ua.webkit;
   16 
   17     /**
   18      * 替换 textarea, 实现 user-editable 的内容区域
   19      * @param {string | HTMLElement} elTextarea 需替换的 textarea 元素的 id 或者引用
   20      * @class
   21      */
   22     YAHOO.realazy.RTE = function(elTextarea){
   23         if ('string' == typeof elTextarea) elTextarea = $D.get(elTextarea);
   24         this.textarea = elTextarea;
   25         this.toolbarItems = {};
   26     }
   27 
   28     /**
   29      * 预填充 iframe 的内容
   30      *
   31      */
   32     YAHOO.realazy.RTE.htmlContent =  ['<!DOCTYPE HTML PUBLIC "-/','/W3C/','/DTD HTML 4.01/','/EN" "http:/','/www.w3.org/TR/html4/strict.dtd"><html><head><title>编辑页面</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body>{CONTENT}</body></html>'].join('');
   33 
   34     /** @scope YAHOO.realazy.RTE.prototype */
   35     YAHOO.realazy.RTE.prototype = {
   36         
   37         /**
   38          * 生成编辑器
   39          */
   40         render: function(){
   41             // step 1: 生成 iframe 并隐藏 textarea
   42             this._createIframeAndHideTextarea();
   43             // step 2: 开启 iframe 的 designMode
   44             this._turnOnDesignMode();
   45             // step 3: 建立 toolbar
   46             this._buildToolbar();
   47             // step 4: 绑定工具条事件
   48             this._bindToolbarEvents();
   49             // step 5: 设置工具条的 unselectable
   50             if (isIE)
   51                 this._setToolbarItemUnselectable();
   52         },
   53         /**
   54          * 生成 iframe 并隐藏 textarea
   55          * @private
   56          */
   57         _createIframeAndHideTextarea: function(){
   58             var iframe = document.createElement('iframe');
   59             $D.addClass(iframe, 'rte-iframe');
   60             this.textarea.style.display = 'none';
   61             $D.insertBefore(iframe, this.textarea);
   62             this.iframe = iframe;
   63         },
   64         /**
   65          * 获取 iframe 的 contentWindow
   66          * @private
   67          */
   68         _getWin: function(){
   69             return this.iframe.contentWindow;
   70         },
   71 
   72         /**
   73          * 获取 iframe 的 document
   74          * @private
   75          */
   76         _getDoc: function(){
   77             return this._getWin().document;
   78         },
   79 
   80         /**
   81          * 开启 iframe 的 design mode
   82          * @private
   83          */
   84         _turnOnDesignMode: function(){
   85             try {
   86                 this._getDoc().open();
   87                 this._getDoc().write(YAHOO.realazy.RTE.htmlContent.replace(/{CONTENT}/, this.textarea.value));
   88                 this._getDoc().close();
   89                 this._getDoc().designMode = 'on';
   90             } catch(ex) {
   91                 setTimeout(arguments.callee, 0);
   92                 return;
   93             }
   94         },
   95 
   96         /**
   97          * 建立 editor 的 toolbar
   98          * @private
   99          */
  100         _buildToolbar: function(){
  101             var toolbar = document.createElement('div');
  102             $D.addClass(toolbar, 'rte-toolbar');
  103 
  104             var items = {
  105                 'fontname': {
  106                     'Arial': 'Arial',
  107                     '宋体': "SimSun, STSong, 'LiSong Pro'", 
  108                     '黑体': "SimHei, STHeiti, 'LiHei Pro'"
  109                 },
  110                 'fontsize': {
  111                     '最小': 1,
  112                     '较小': 2,
  113                     '': 3,
  114                     '': 4,
  115                     '': 5,
  116                     '较大': 6,
  117                     '最大': 7
  118                 },
  119                 'bold': '加粗',
  120                 'italic': '斜体',
  121                 'underline': '下划线',
  122                 'justifyleft': '居左',
  123                 'justifycenter': '居中',
  124                 'justifyright': '居右',
  125                 'createlink': '超链接',
  126                 'insertimage': '插图'
  127             }
  128             
  129             for (var k in items){
  130                 var span = document.createElement('span');
  131                 if (k == 'fontname' || k == 'fontsize'){
  132                     var select = document.createElement('select');
  133                     $D.addClass(select, k);
  134                     for (var kk in items[k]){
  135                         var option = new Option(kk, items[k][kk]);
  136                         if (!isGecko)
  137                             select.add(option);
  138                         else
  139                             select.appendChild(option);
  140                     }
  141                     span.appendChild(select);
  142                     this.toolbarItems[k] = select;
  143                 } else {
  144                     $D.addClass(span, k);
  145                     span.innerHTML = items[k];
  146                     this.toolbarItems[k] = span;
  147                 }
  148                 toolbar.appendChild(span);
  149             }
  150             
  151             $D.insertBefore(toolbar, this.iframe);
  152             this.toolbar = toolbar;
  153         },
  154         /**
  155          * execCommand
  156          * @param {string} sCmd command name
  157          * @param {string} sVal command Value
  158          * @private
  159          */
  160         _execCommand: function(sCmd, sVal){
  161                 this._getWin().focus();
  162                   this._getDoc().execCommand(sCmd, false, sVal);
  163         },
  164 
  165         /**
  166          * 绑定 toolbar 的 events
  167          * @private
  168          */
  169         _bindToolbarEvents: function(){
  170             var _this = this,
  171                 items = this.toolbarItems,
  172                 trim = YAHOO.lang.trim;
  173             
  174             $E.on([items['fontsize'], items['fontname']], 'change', function(e){
  175                 var cmd = this.className,
  176                     val = this.options[this.selectedIndex].value;
  177                 _this._execCommand(cmd, val);
  178             });
  179             
  180             $E.on(this.toolbar, 'click', function(e){
  181                 var t = $E.getTarget(e),
  182                     cmd = t.className,
  183                     nn = t.nodeName.toLowerCase(),
  184                     val = null,
  185                     isNeedVal = false;
  186                 if (nn != 'span' && !cmd) return;
  187                 switch (cmd){
  188                 case 'createlink':
  189                 case 'insertimage':
  190                     isNeedVal = true;
  191                     val = prompt('请输入网址:', 'http://');
  192                     break;
  193                 case 'fontname':
  194                 case 'fontsize':
  195                     return;
  196                 default:
  197                     break;
  198                 }
  199                 
  200                 if (isNeedVal && (!val || (trim(val) == 'http://'))) return;
  201                 this._execCommand(cmd, val);
  202                 
  203             }, this, true);
  204         },
  205 
  206         /**
  207          * 让 toolbar 上的各个项目不可选,以解决 selection 被冲掉的问题
  208          * @private
  209          */
  210         _setToolbarItemUnselectable: function(){
  211             function unsel(elm){
  212                 elm.setAttribute('unselectable', 'on');
  213             }
  214             unsel(this.toolbar);
  215             var els = this.toolbar.getElementsByTagName('*'),
  216                 nn;
  217             for (var i=0; i<els.length; ++i){
  218                 nn = els[i].nodeName;
  219                 if (nn && nn.toLowerCase() != 'input')
  220                     unsel(els[i]);
  221             }
  222         }
  223     }
  224 })();