zoukankan      html  css  js  c++  java
  • 一步步教你实现富文本编辑器(第三部分)

    这部分我们把富文本编辑器的代码打包成一个类。至于如何实现没有什么好说的,就是那五种方案,我取用的是原型,那是最JS,也是最ruby的。我们的所有实现都在原型进行,最后new出来就是!构造函数有一个必选参数,就是那个textarea的id,其他都是动态生成的,包括其样式。关于样式,我已提供了一个很好用的addSheet函数了。那么开始吧,我们要尽快做出第二部分最后阶段的样式再说!

    首先我为大家提供了一个模板,大家可以根据它自行完成我们讲过的部分。

     
    var Class = {
        create: function() {
          return function() {
            this.initialize.apply(this, arguments);
          }
        }
      }
      var extend = function(destination, source) {
        for (var property in source) {
          destination[property] = source[property];
        }
        return destination;
      }
      var RichTextEditor =  Class.create();//我们的富文本编辑器类
      RichTextEditor.prototype = {
        initialize:function(options){
          this.setOptions(options);
          this.drawEditor(this.options.textarea_id);
        },
        setOptions:function(options){
          this.options = { //这里集中设置默认属性
            id:'jeditor_'+ new Date().getTime(),
            textarea_id:null//用于textarea的ID,也就是我们的必选项
          }
          extend(this.options, options || {});//这里是用来重写默认属性
        },
        ID:function(id){return document.getElementById(id) },//getElementById的快捷方式
        TN:function(tn){ return document.getElementsByTagName(tn) },//getElementsByTagName的快捷方式
        CE:function(s){ return document.createElement(s)},//createElement的快捷方式
        drawEditor:function(id){
          var textarea = this.ID(id);
          textarea.style.display = "none";
        }
      }
    

    接着下来我们基本就是在drawEditor这个函数工作了,我们隐藏了原来的textarea后,然后在其下面生成一个div,当作富本文编辑器的工具栏,然后再在其下面生成iframe,这是我们富文本编辑器的工作区。工具栏的按钮很多,我们把这些按钮的名字以及隐藏在title的命令全部打包在一个对象,然后循环生成它们,并在循环中设置样式与绑定事件。这些和第二部分所讲的别无二致,我就不重复了,快手净脚搞出它们吧!

       var buttons = {
            'fontname': {
              '宋体':'SimSun',
              '隶书':'LiSu',
              '楷体':'KaiTi_GB2312',
              '幼圆':'YouYuan',
              '黑体':'SimHei',
              '雅黑':'Microsoft YaHei',
              '仿宋':'FangSong',
              'Comic Sans MS':'Comic Sans MS'
            },
            'fontsize': {
              '特小': 1,
              '很小': 2,
              '小': 3,
              '中': 4,
              '大': 5,
              '很大': 6,
              '特大':7
            },
            'removeformat':'还原',
            'bold': '加粗',
            'italic': '斜体',
            'underline': '下划线',
            'strikethrough':'删除线',
            'justifyleft': '居左',
            'justifycenter': '居中',
            'justifyright': '居右',
            'indent':'缩进',
            'outdent':'悬挂',
            'forecolor':'前景色',
            'backcolor':'背景色',
            'createlink': '超链接',
            'insertimage': '插图',
            'insertorderedlist':'有序列表',
            'insertunorderedlist':'无序列表',
            'html':'查看'
          };
    

    到这里,基本和第二部分差不多了。至于前景色与背景色,我们打算用我以前提供过的颜色选择器实现,现在我们的目标是那两个下拉选择框。我觉得那两个select太不人性化了,由于其级别很高,我们很难对它进行制定。作为可见即可得,我们要来在拉动那个select时,应该能给人们一个大概样子。因此select必须死。

    我们修改buttons对象,把fontname与fontsize提取出来单独处理!

    var fontFamilies = ['宋体','经典中圆简','微软雅黑', '黑体', '楷体', '隶书', '幼圆',
            'Arial', 'Arial Narrow', 'Arial Black', 'Comic Sans MS',
            'Courier New', 'Georgia', 'New Roman Times', 'Verdana']
    var fontSizes= [[1, 'xx-small', '最小'],
            [2, 'x-small', '特小'],
            [3, 'small', '小'],
            [4, 'medium', '中'],
            [5, 'large', '大'],
            [6, 'x-large', '特大'],
            [7, 'xx-large', '最大']];
    

    但是这样一来,我们原来的事件绑定机制就遭到灭顶之灾!我们必须奠出我们的addEvent函数。addEvent要求我们传入三个参数(需要绑定的元素,事件类型与绑定事件),后两个很明确了,问题是第一个,我们怎么找到这些元素呢?不过一个个加id吧。不用,我们在最开始的循环就把这些元素加入一个数组就是!

          for (var i in buttons){/*添加命令按钮的名字,样式*/
            var button = buttonClone.cloneNode("true");
            if(i == 'backcolor'){/*特殊处理背景色按钮*/
              if (!+"\v1"){
                button.setAttribute("title","background")
              }else{
                button.setAttribute("title","hilitecolor")
              }
            }
            button.setAttribute("title",i);/*把execCommand的命令参数放到title*/
            button.innerHTML = buttons[i];
            button.setAttribute("unselectable", "on");/*防止焦点转移到点击的元素上,从而保证文本的选中状态*/
            toolbar[i] = button;   /*★★★★把元素放进一个数组,用于事件绑定!★★★★*/
            fragment.appendChild(button);
          }
          toolbar.appendChild(fragment);
        }
    

    得益于javascript的事件机制,我们只对toolbar进行监听,就可以监听其所有子元素。另外,我们把格式化命令独立出来,简化我们的程序。

         this.addEvent(toolbar, 'click', function(){
            var e = arguments[0] || window.event,
            target = e.srcElement ? e.srcElement : e.target,
            command = target.getAttribute("title");
            switch (command){
              case 'createlink':
              case 'insertimage':
                var value = prompt('请输入网址:', 'http://');
                _format(command,value);
                break;
              case 'fontname'://这几个特殊处理
              case 'fontsize':
              case 'forecolor':
              case 'backcolor':
              case 'html':
                return;
              default:
                _format(command,'');
                break;
            }
          });
          
          var _format = function(x,y){//内部私有函数,处理富文本编辑器的格式化命令
            iframeDocument.execCommand(x,false,y);
            iframe.contentWindow.focus();
          }
    

    至于字体与字码,我们可以用div模拟select了!然后为它们绑定两个事件,一个是用来显示隐藏select,一个是用来执行格式化命令。

          var fontPicker = $.CE('div');
          fontPicker.setAttribute('unselectable', 'on');
          fontPicker.className = "fontpicker";
          toolbar.appendChild(fontPicker);//字体选择器与字码选择器都是共用一个虚拟select
          $.addEvent(toolbar['fontname'], 'click', function(){
            //根据情况选择载入虚拟select的内容
          })
    
          $.addEvent(toolbar['fontsize'], 'click', function(){
            //根据情况选择载入虚拟select的内容
          })
          var bind_select_event = function(button,picker){
            //显示或隐藏文字选择器
          }
          /************************用于生成文字选择器的内容************************/
        fontPickerHtml:function(type,array){
          var builder = [];
          for(var i = 0,l = array.length;i<l;i++){
            builder.push('<a unselectable="on" style="');
            if(type == 'fontname'){
              builder.push('font-family');
              builder.push(':\'');
              builder.push(array[i]); /*呈现一行(一行就是一种字体)*/
              builder.push('\';" href="javascript:void(0)">');
              builder.push(array[i]);
            }else if(type == 'fontsize'){     
              builder.push('font-size'); /*呈现一行(一行就是一种字号)*/
              builder.push(':');
              builder.push(array[i][1]);
              builder.push(';" sizevalue="');
              builder.push(array[i][0]);
              builder.push('" href="javascript:void(0)">');//IE的a元素必须有href才有悬浮效果
              builder.push(array[i][2]);
            }
            builder.push("</a>");
          }
          return builder.join('');
        }
    

    上面的代码其实有个问题,不过也可能是IE的问题,在IE中,当我们点击虚拟select的a元素时,execComman函数实际执行了两次,第一次确实是完成了格式化任务,第二次却因为参数为空而报错……囧!我不知道是哪里错了,不过我认为如果我们把事件直接绑定到a元素,而不是绑定到虚拟select的那个div元素,就肯定没问题。不过这样做代码非常复杂非常长,如何定位到这些a元素就要劳师动众一番。我是利用DOM2的事件传播机制缩短它的(嗯,写到这里,我好像明白了一些)。我用了一个很不值得推荐的方法,把execComman放到一个catch块中,吞掉这异常。

    更好的办法,我想到了。execCommand之所以执行发两次,是因为IE并没有阻止onclick事件继续向上冒泡,之于为什么会冒泡呢?!这又是个谜了!这是新的代码:

          $.addEvent(fontPicker,'click',function(){
              /*****************略************/
              _format(command,value);
              e.cancelBubble = true;//重点
              fontPicker.style.display = 'none';
            }
           });
          var _format = function(x,y){//内部私有函数,处理富文本编辑器的格式化命令
         //    try{
              iframeDocument.execCommand(x,false,y);
              iframe.contentWindow.focus();
          //    }catch(e){}
          }
    

    接着下来是背景色与前景色,以前我就做了一个颜色选择器,具体可参见这篇博文,我就不重复了!流程基本与文字选择器一样,我们这里得修改一下bind_select_event方法。

          var bind_select_event = function(button,picker){//显示或隐藏选择器
            button.style.position = 'relative';
            var command = button.getAttribute("title");
            if('backcolor' == command){
               command = !+"\v1" ? 'backcolor':'hilitecolor';
            }     
            /************略****************/
          }
    

    紧接着是查看按钮,这个简单,这里我把封装一下,让它看起来不那么乱。

          /********切换回代码界面*************/
          var _doHTML = function() {
            iframe.style.display = "none";
            textarea.style.display = "block";
            textarea.value = iframeDocument.body.innerHTML;
            textarea.focus();
          };
          /********切换回富文本编辑器界面*************/
          var _doRich = function() {
            iframe.style.display = "block";
            textarea.style.display = "none";
            iframeDocument.body.innerHTML = textarea.value;
            iframe.contentWindow.focus();
          };
          /********切换编辑模式的开关*************/
          var switchEditMode = true;
          $.addEvent(toolbar['html'], 'click', function(){
            if(switchEditMode){
              _doHTML();
              switchEditMode = false;
            }else{
              _doRich();
              switchEditMode = true;
            }
          });
    

    但这个不保证我们提交表单时textarea有东西,我们在iframe失去焦点时偷偷转移东西给textarea。这里的问题第二部分已详细提过,这里就不重复了

         $.addEvent(iframe.contentWindow,"blur",function(){
            textarea.value = iframeDocument.body.innerHTML;
          });
    

    “接着下来我们开始讲解复杂插入吧……”正想这样说,一看篇幅,改写成类比预期的费笔墨,今天就先开过头,下次再说。

    我们先多添加一个按钮,用于插入表格,点击它将弹出一个层,上面要求我们填写将要生成的表格的参数。

         var buttons = {//工具栏的按钮集合
          /*********略************/
            'table':'插入表格',
            'html':'查看'
          };
    
     var tableCreator = $.CE('div');
          tableCreator.className = 'tablecreator';
          toolbar.appendChild(tableCreator);
          tableCreator.innerHTML = $.tableHtml();
          $.addEvent(toolbar['table'],'click',function(){
            bind_select_event(this,tableCreator);
          });
    

    最后留个作业,希望各位博友们思考一下如何创建表格,并把插入到编辑光标之前。那么下一部分再见!

  • 相关阅读:
    Balanced Binary Tree
    Swap Nodes in Pairs
    Reverse Nodes in k-Group
    Reverse Linked List II
    Remove Nth Node From End of List
    Remove Duplicates from Sorted List II
    Remove Duplicates from Sorted List
    Partition List
    Merge Two Sorted Lists
    【Yii2.0】1.2 Apache检查配置文件语法
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1537022.html
Copyright © 2011-2022 走看看