这部分我们把富文本编辑器的代码打包成一个类。至于如何实现没有什么好说的,就是那五种方案,我取用的是原型,那是最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.
});
最后留个作业,希望各位博友们思考一下如何创建表格,并把插入到编辑光标之前。