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

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

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

    01.   
    02.var Class = {
    03.    create: function() {
    04.      return function() {
    05.        this.initialize.apply(this, arguments);
    06.      }
    07.    }
    08.  }
    09.  var extend = function(destination, source) {
    10.    for (var property in source) {
    11.      destination[property] = source[property];
    12.    }
    13.    return destination;
    14.  }
    15.  var RichTextEditor =  Class.create();//我们的富文本编辑器类
    16.  RichTextEditor.prototype = {
    17.    initialize:function(options){
    18.      this.setOptions(options);
    19.      this.drawEditor(this.options.textarea_id);
    20.    },
    21.    setOptions:function(options){
    22.      this.options = { //这里集中设置默认属性
    23.        id:'jeditor_'+ new Date().getTime(),
    24.        textarea_id:null//用于textarea的ID,也就是我们的必选项
    25.      }
    26.      extend(this.options, options || {});//这里是用来重写默认属性
    27.    },
    28.    ID:function(id){return document.getElementById(id) },//getElementById的快捷方式
    29.    TN:function(tn){ return document.getElementsByTagName(tn) },//getElementsByTagName的快捷方式
    30.    CE:function(s){ return document.createElement(s)},//createElement的快捷方式
    31.    drawEditor:function(id){
    32.      var textarea = this.ID(id);
    33.      textarea.style.display = "none";
    34.    }
    35.  }

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

    01.var buttons = {
    02.     'fontname': {
    03.       '宋体':'SimSun',
    04.       '隶书':'LiSu',
    05.       '楷体':'KaiTi_GB2312',
    06.       '幼圆':'YouYuan',
    07.       '黑体':'SimHei',
    08.       '雅黑':'Microsoft YaHei',
    09.       '仿宋':'FangSong',
    10.       'Comic Sans MS':'Comic Sans MS'
    11.     },
    12.     'fontsize': {
    13.       '特小': 1,
    14.       '很小': 2,
    15.       '小': 3,
    16.       '中': 4,
    17.       '大': 5,
    18.       '很大': 6,
    19.       '特大':7
    20.     },
    21.     'removeformat':'还原',
    22.     'bold': '加粗',
    23.     'italic': '斜体',
    24.     'underline': '下划线',
    25.     'strikethrough':'删除线',
    26.     'justifyleft': '居左',
    27.     'justifycenter': '居中',
    28.     'justifyright': '居右',
    29.     'indent':'缩进',
    30.     'outdent':'悬挂',
    31.     'forecolor':'前景色',
    32.     'backcolor':'背景色',
    33.     'createlink': '超链接',
    34.     'insertimage': '插图',
    35.     'insertorderedlist':'有序列表',
    36.     'insertunorderedlist':'无序列表',
    37.     'html':'查看'
    38.   };

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

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

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

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

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

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

    01.this.addEvent(toolbar, 'click', function(){
    02.   var e = arguments[0] || window.event,
    03.   target = e.srcElement ? e.srcElement : e.target,
    04.   command = target.getAttribute("title");
    05.   switch (command){
    06.     case 'createlink':
    07.     case 'insertimage':
    08.       var value = prompt('请输入网址:', 'http://');
    09.       _format(command,value);
    10.       break;
    11.     case 'fontname'://这几个特殊处理
    12.     case 'fontsize':
    13.     case 'forecolor':
    14.     case 'backcolor':
    15.     case 'html':
    16.       return;
    17.     default:
    18.       _format(command,'');
    19.       break;
    20.   }
    21. });
    22.   
    23. var _format = function(x,y){//内部私有函数,处理富文本编辑器的格式化命令
    24.   iframeDocument.execCommand(x,false,y);
    25.   iframe.contentWindow.focus();
    26. }

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

    01.  var fontPicker = $.CE('div');
    02.  fontPicker.setAttribute('unselectable', 'on');
    03.  fontPicker.className = "fontpicker";
    04.  toolbar.appendChild(fontPicker);//字体选择器与字码选择器都是共用一个虚拟select
    05.  $.addEvent(toolbar['fontname'], 'click', function(){
    06.    //根据情况选择载入虚拟select的内容
    07.  })
    08.  $.addEvent(toolbar['fontsize'], 'click', function(){
    09.    //根据情况选择载入虚拟select的内容
    10.  })
    11.  var bind_select_event = function(button,picker){
    12.    //显示或隐藏文字选择器
    13.  }
    14.  /************************用于生成文字选择器的内容************************/
    15.fontPickerHtml:function(type,array){
    16.  var builder = [];
    17.  for(var i = 0,l = array.length;i<l;i++){
    18.    builder.push('<a unselectable="on" style="');
    19.    if(type == 'fontname'){
    20.      builder.push('font-family');
    21.      builder.push(':\'');
    22.      builder.push(array[i]); /*呈现一行(一行就是一种字体)*/
    23.      builder.push('\';" href="javascript:void(0)">');
    24.      builder.push(array[i]);
    25.    }else if(type == 'fontsize'){     
    26.      builder.push('font-size'); /*呈现一行(一行就是一种字号)*/
    27.      builder.push(':');
    28.      builder.push(array[i][1]);
    29.      builder.push(';" sizevalue="');
    30.      builder.push(array[i][0]);
    31.      builder.push('" href="javascript:void(0)">');//IE的a元素必须有href才有悬浮效果
    32.      builder.push(array[i][2]);
    33.    }
    34.    builder.push("</a>");
    35.  }
    36.  return builder.join('');
    37.}

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

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

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

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

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

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

    01./********切换回代码界面*************/
    02.var _doHTML = function() {
    03.  iframe.style.display = "none";
    04.  textarea.style.display = "block";
    05.  textarea.value = iframeDocument.body.innerHTML;
    06.  textarea.focus();
    07.};
    08./********切换回富文本编辑器界面*************/
    09.var _doRich = function() {
    10.  iframe.style.display = "block";
    11.  textarea.style.display = "none";
    12.  iframeDocument.body.innerHTML = textarea.value;
    13.  iframe.contentWindow.focus();
    14.};
    15./********切换编辑模式的开关*************/
    16.var switchEditMode = true;
    17.$.addEvent(toolbar['html'], 'click', function(){
    18.  if(switchEditMode){
    19.    _doHTML();
    20.    switchEditMode = false;
    21.  }else{
    22.    _doRich();
    23.    switchEditMode = true;
    24.  }
    25.});

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

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

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

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

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

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

  • 相关阅读:
    C#命名约定:PascalCase和camelCase
    Windows8 App 四大名著完整本 隐私保护声明
    C#-编码习惯
    [转]C#之Console.Write()和Console.Read()及Console.Readline()的问题
    通过JavaScript动态输入计算
    在VS2008中加入ExtJS智能提示—>(方法一)
    在VS2008中加入ExtJS智能提示—>(方法二)
    (一)javascript面向对象:(1)类
    上证指数波浪分析2013/03/12
    Springsecurity源码Filter之HeaderWriterFilter(十二)
  • 原文地址:https://www.cnblogs.com/liufei88866/p/1537621.html
Copyright © 2011-2022 走看看