玩了tiny_mce在线编辑器好几个星期,今天终于差不多把所有的功能都给完成了,确切的说是把编辑器的插件功能完美的整合在我的博客里面,解决一些小的bug,这还得意于它本身是开源免费的,这里我实现的功能主要有:
- 修改图片和多媒体文件上传和浏览功能;
- 增加signature个性签名(关联博客)和insertcode插入代码(整合CodeHighlighter代码高亮显示)功能插件;
- 修改编辑器内按下Ctrl S键save保存插件功能,使之支持Postback到服务器端并触发OnSave事件。
- 修正编辑器内字体过小、设置编辑器不会自动移除div元素节点的等问题。
起初我引用tiny_mce编辑器都是直接嵌入的脚本的,摸索了一番待完善所有功能后,当然就要把它做成.net的自定义控件了,方便每一个页面调用,下面我就结合在做自定义控件的时候说一下Tinymce编辑器。
试试我的TinyMCE在线编辑器
先上自定义控件源代码:
TinyMce在线编辑器自定义控件类 1using System;
2using System.Data;
3using System.Configuration;
4using System.Web;
5using System.Web.Security;
6using System.Web.UI;
7using System.Web.UI.WebControls;
8using System.Web.UI.WebControls.WebParts;
9using System.Web.UI.HtmlControls;
10using System.ComponentModel;
11
12namespace Studio.Web
13...{
14 /**//// <summary>
15 /// Name : TinyMce在线编辑器
16 /// Author : Jonllen
17 /// Site : www.jonllen.com
18 /// CreateDate : 2009-09-09 23:23
19 /// UpdateDate : 2009-09-10 21:18
20 /// </summary>
21 public class TinyMce : WebControl, INamingContainer, IPostBackEventHandler //IPostBackDataHandler
22 ...{
23
24 private string mode = "exact";
25
26 [Description("模式")]
27 public string Mode
28 ...{
29 get ...{ return mode; }
30 set ...{ mode = value; }
31 }
32
33 private string theme = "advanced";
34
35 [Description("主题")]
36 public string Theme
37 ...{
38 get ...{ return theme; }
39 set ...{ theme = value; }
40 }
41
42 private string language = "zh";
43
44 [Description("语言")]
45 public string Language
46 ...{
47 get ...{ return language; }
48 set ...{ language = value; }
49 }
50
51 private string plugins = "wordcount,searchreplace,signature,save,safari,pagebreak,style,layer,table,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,insertcode,uploadImage";
52
53 [Description("插件")]
54 public string Plugins
55 ...{
56 get ...{ return plugins; }
57 set ...{ plugins = value; }
58 }
59
60 private string theme_advanced_buttons1 = "styleprops,formatselect,fontselect,fontsizeselect,separator,forecolor,backcolor,separator,bold,italic,underline,strikethrough,charmap,separator,bullist,numlist,separator, justifyleft, justifycenter, justifyright";
61
62 [Description("第一排按钮")]
63 public string ThemeAdvancedButtons1
64 ...{
65 get ...{ return theme_advanced_buttons1; }
66 set ...{ theme_advanced_buttons1 = value; }
67 }
68
69 private string theme_advanced_buttons2 = "undo,redo,cut,copy,paste,pastetext,pasteword,separator,outdent,indent,removeformat,cleanup,search,replace,separator,link,unlink,image,uploadImage,media,separator,save,emotions,signature,insertcode,separator,visualaid,fullscreen,preview,code";
70
71 [Description("第二排按钮")]
72 public string ThemeAdvancedButtons2
73 ...{
74 get ...{ return theme_advanced_buttons2; }
75 set ...{ theme_advanced_buttons2 = value; }
76 }
77
78 private string theme_advanced_buttons3;
79
80 [Description("第三排按钮")]
81 public string ThemeAdvancedButtons3
82 ...{
83 get ...{ return theme_advanced_buttons3; }
84 set ...{ theme_advanced_buttons3 = value; }
85 }
86
87 private string theme_advanced_buttons4;
88
89 [Description("第四排按钮")]
90 public string ThemeAdvancedButtons4
91 ...{
92 get ...{ return theme_advanced_buttons4; }
93 set ...{ theme_advanced_buttons4 = value; }
94 }
95
96 private string valid_elements = "script[language|type|src],style[media,type],link[id|type|title|rel|href],sub,sup,input[id|name|type|value|style|class|checked|disabled|title|onclick|ondblclick|onchange|onkeydown|onkeypress|onkeyup|onfocus|escape],textarea[id|name|style|class|rows|cols],form[id|name|action|method|target|enctype|onsubmit],object[id|classid|width|height],param[name|value],embed[id|src|width|height|wmode|flashvars|type],select[id|name|style|class|onchange|multiple],option[id|value],dl,dt,dd,fieldset,legend,label,h1,h2,h3,h4[class|id|style],div[class|id|style],pre,br,p[class|id|style],strong[class|id|style],em,span[style|class|id],ul[style|class|id]],li,ol,hr[width|size|noshade|style],img[title|src|border|alt|width|height|style|id|class|onclick],a[href|target|style|class|id|title],table[style|border|cellspacing|cellpadding|align|class|id],tr[style|align|valign],th[style|align|valign|colspan|rowspan],td[style|align|valign|colspan|rowspan]";
97
98 [Description("验证的元素")]
99 public string ValidElements
100 ...{
101 get ...{ return valid_elements; }
102 set ...{ valid_elements = value; }
103 }
104
105 private bool convert_fonts_to_spans = true;
106
107 [Description("是否将Font标签转化为Span")]
108 public bool ConvertFontsToSpans
109 ...{
110 get ...{ return convert_fonts_to_spans; }
111 set ...{ convert_fonts_to_spans = value; }
112 }
113
114 private bool theme_advanced_resizing = true;
115
116 [Description("是否可缩放编辑器大小")]
117 public bool ThemeAdvancedResizing
118 ...{
119 get ...{ return theme_advanced_resizing; }
120 set ...{ theme_advanced_resizing = value; }
121 }
122
123 private string theme_advanced_toolbar_location = "top";
124
125 [Description("工具栏上下对齐位置")]
126 public string ThemeAdvancedToolbarLocation
127 ...{
128 get ...{ return theme_advanced_toolbar_location; }
129 set ...{ theme_advanced_toolbar_location = value; }
130 }
131
132 private string theme_advanced_toolbar_align = "left";
133
134 [Description("工具栏左右对齐位置")]
135 public string ThemeAdvancedToolbarAlign
136 ...{
137 get ...{ return theme_advanced_toolbar_align; }
138 set ...{ theme_advanced_toolbar_align = value; }
139 }
140
141 private string theme_advanced_statusbar_location = "bottom";
142
143 [Description("状态栏上下对齐位置")]
144 public string ThemeAdvancedStatusbarLocation
145 ...{
146 get ...{ return theme_advanced_statusbar_location; }
147 set ...{ theme_advanced_statusbar_location = value; }
148 }
149
150 private string content_css = "/tiny_mce/css/content.css";
151
152 [Description("编辑器样式文件路径")]
153 public string ContentCss
154 ...{
155 get ...{ return content_css; }
156 set ...{ content_css = value; }
157 }
158
159 private bool relative_urls = true;
160
161 [Description("是否使用相对路径(编辑器内图片、链接等内容)")]
162 public bool RelativeUrls
163 ...{
164 get ...{ return relative_urls; }
165 set ...{ relative_urls = value; }
166 }
167
168 private bool remove_script_host = true;
169
170 [Description("是否移除脚本文件的主机域名(编辑器内)")]
171 public bool RemoveScriptHost
172 ...{
173 get ...{ return remove_script_host; }
174 set ...{ remove_script_host = value; }
175 }
176
177 private bool convert_urls = false;
178
179 [Description("是否自动转换路径(编辑器内图片、链接等内容)")]
180 public bool ConvertUrls
181 ...{
182 get ...{ return convert_urls; }
183 set ...{ convert_urls = value; }
184 }
185
186 private string save_onsavecallback;
187
188 [Description("按下Ctrl S键后触发的JavaScript客户端函数")]
189 public string SaveOnsavecallback
190 ...{
191 get ...{ return save_onsavecallback; }
192 set ...{ save_onsavecallback = value; }
193 }
194
195 private string setup;
196
197 [Description("初始化编辑器设置的JavaScript客户端函数")]
198 public string Setup
199 ...{
200 get ...{ return setup; }
201 set ...{ setup = value; }
202 }
203
204 private string scriptsrc = "/tiny_mce/tiny_mce.js";
205
206 [Description("编辑器主脚本文件路径")]
207 public string ScriptSrc
208 ...{
209 get ...{ return scriptsrc; }
210 set ...{ scriptsrc = value; }
211 }
212
213 [Description("文本域控件的高度(以字符为单位)")]
214 public int Rows
215 ...{
216 get ...{ return this.textArea.Rows; }
217 set ...{ this.textArea.Rows = value; }
218 }
219
220 [Description("文本域控件的宽度(以字符为单位)")]
221 public int Cols
222 ...{
223 get ...{ return this.textArea.Cols; }
224 set ...{ this.textArea.Cols = value; }
225 }
226
227 [Description("文本域内容")]
228 public string Text
229 ...{
230 get ...{ return this.textArea.Value; }
231 set ...{ this.textArea.Value = value; }
232 }
233
234 [Description("开始标记和结束标记之间的文本")]
235 public string InnerText
236 ...{
237 get
238 ...{
239 string text = this.textArea.Value;
240 text = System.Text.RegularExpressions.Regex.Replace(text, "<[^>] >", "");
241 text = System.Text.RegularExpressions.Regex.Replace(text, "&[^;] ;", "");
242 return text.Replace("\r\n", "").Trim();
243 }
244 }
245
246 private Unit width;
247
248 /**//// <summary>
249 /// 重写父类Width属性指向textarea控件的宽度
250 /// </summary>
251 [Description("文本域宽度")]
252 public override Unit Width
253 ...{
254 get ...{ return width; }
255 set ...{ width = value; this.textArea.Style["width"] = value.ToString(); }
256 }
257
258 private Unit height;
259
260 /**//// <summary>
261 /// 重写父类Height属性指向textarea指向的高度
262 /// </summary>
263 [Description("文本域高度")]
264 public override Unit Height
265 ...{
266 get ...{ return height; }
267 set ...{ height = value; this.textArea.Style["height"] = value.ToString(); }
268 }
269
270 private string cssClass;
271
272 /**//// <summary>
273 /// 重写父类CssClass属性指向textarea控件的class样式类
274 /// </summary>
275 [Description("文本域样式类")]
276 public override string CssClass
277 ...{
278 get ...{ return cssClass; }
279 set ...{ cssClass = value; this.textArea.Attributes["class"] = value; }
280 }
281
282 /**//// <summary>
283 /// 重写父类ClientID只读属性指向textarea控件生成的客户端ID
284 /// </summary>
285 public override string ClientID
286 ...{
287 get
288 ...{
289 return base.ClientID "_textarea";
290 }
291 }
292
293 /**//// <summary>
294 /// 服务器端保存事件
295 /// </summary>
296 public event EventHandler Save;
297
298 /**//// <summary>
299 /// IPostBackEventHandler成员
300 /// </summary>
301 /// <param name="eventArgument">传递的参数</param>
302 public void RaisePostBackEvent(string eventArgument)
303 ...{
304 if (this.Save != null)
305 this.Save(this, new EventArgs());
306 }
307
308
309 private HtmlTextArea textArea;
310
311 private string itemKey = "TinyMce";
312
313 public TinyMce()
314 ...{
315 //初始化HtmlTextArea,并添加到子控件内
316 this.textArea = new HtmlTextArea();
317 this.textArea.ID = "textarea";
318 //引起回发的Html元素的name属性值必须为控件的 UniqueID,否则 RaisePostBackEvent 事件不会被调用
319 this.textArea.Name = this.UniqueID;
320 this.Controls.Add(textArea);
321
322 }
323
324 重写父类Render生成HTML方法#region 重写父类Render生成HTML方法
325 protected override void Render(HtmlTextWriter writer)
326 ...{
327 //注释父类Render的方法,因为WebControl默认会生成一个<span>标签
328 //base.Render(writer);
329
330 //调用HtmlTextArea控件的Render方法生成HTML
331 this.textArea.RenderControl(writer);
332
333 //当前上下文为空则返回(在VS的ASPX设计视图页面)
334 if (Context == null)
335 ...{
336 return;
337 }
338
339 //生成Tiny_Mce在线编辑器脚本的字符串
340 System.Text.StringBuilder sb = new System.Text.StringBuilder();
341
342 int count = 0;
343 int.TryParse( Convert.ToString( HttpContext.Current.Items[this.itemKey]) ,out count);
344 //获取当前页面的编辑器数量,如果只有一个则引用Tiny_Mce在线编辑器主脚本文件,避免多个重复引用
345 if ( count == 0)
346 ...{
347 sb.AppendFormat("<script type=\"text/javascript\" src=\"{0}\"></script>", this.scriptsrc);
348 }
349 HttpContext.Current.Items[this.itemKey] = count 1;
350
351 sb.Append("<script type=\"text/javascript\">");
352 sb.Append("tinyMCE.init({");
353 sb.AppendFormat("mode : \"{0}\",", this.mode);
354 if (!string.IsNullOrEmpty(this.textArea.ClientID))
355 ...{
356 sb.AppendFormat("elements : \"{0}\",", this.textArea.ClientID);
357 }
358 sb.AppendFormat("theme : \"{0}\",", this.theme);
359
360 if (this.theme != "simple")
361 ...{
362 sb.AppendFormat("language : \"{0}\",", this.language);
363 sb.AppendFormat("plugins : \"{0}\",", this.plugins);
364 if (!string.IsNullOrEmpty(this.theme_advanced_buttons1))
365 ...{
366 sb.AppendFormat("theme_advanced_buttons1 : \"{0}\",", this.theme_advanced_buttons1);
367 }
368 if (!string.IsNullOrEmpty(this.theme_advanced_buttons2))
369 ...{
370 sb.AppendFormat("theme_advanced_buttons2 : \"{0}\",", this.theme_advanced_buttons2);
371 }
372 //if (!string.IsNullOrEmpty(this.theme_advanced_buttons3))
373 //{
374 sb.AppendFormat("theme_advanced_buttons3 : \"{0}\",", this.theme_advanced_buttons3);
375 //}
376
377 //if (!string.IsNullOrEmpty(this.theme_advanced_buttons4))
378 //{
379 sb.AppendFormat("theme_advanced_buttons4 : \"{0}\",", this.theme_advanced_buttons4);
380 //}
381 sb.AppendFormat("valid_elements : \"{0}\",", this.valid_elements);
382 sb.AppendFormat("convert_fonts_to_spans : {0},", this.convert_fonts_to_spans ? "true" : "false");
383 sb.AppendFormat("theme_advanced_resizing : {0},", this.theme_advanced_resizing ? "true" : "false");
384 sb.AppendFormat("relative_urls : {0},", this.relative_urls ? "true" : "false");
385 sb.AppendFormat("remove_script_host : {0},", this.remove_script_host ? "true" : "false");
386 sb.AppendFormat("convert_urls : {0},", this.convert_urls ? "true" : "false");
387 sb.AppendFormat("theme_advanced_toolbar_location : \"{0}\",", this.theme_advanced_toolbar_location);
388 sb.AppendFormat("theme_advanced_toolbar_align : \"{0}\",", this.theme_advanced_toolbar_align);
389 sb.AppendFormat("theme_advanced_statusbar_location : \"{0}\",", this.theme_advanced_statusbar_location);
390 if (!string.IsNullOrEmpty(this.save_onsavecallback))
391 ...{
392 sb.AppendFormat("save_onsavecallback : {0},", this.save_onsavecallback);
393 }
394 if (!string.IsNullOrEmpty(this.setup))
395 ...{
396 sb.AppendFormat("setup : {0},", this.setup);
397 }
398 if (string.IsNullOrEmpty(this.save_onsavecallback) && this.Save != null)
399 ...{
400 //输出服务器端触发回发事件的eventTarget参数到tinymce编辑器初始话参数
401 sb.AppendFormat("onserversave : \"{0}\",", this.UniqueID);
402 }
403 }
404 sb.AppendFormat("content_css : \"{0}\"", this.content_css);
405 sb.Append("});");
406 sb.Append("</script>");
407
408 //输出
409 writer.WriteLine(sb.ToString());
410 }
411 #endregion
412
413 }
414}
首先做一个自定义控件需要考虑的继承类,是Control还是WebControl?WebControl本身是继承自Control的类,它跟Control类相比拥有Web服务器控件的更多属性,因此我这里选择继承自WebControl类,其实我本来还试过直接继承自HtmlTextArea类的,熟悉tinymce编辑器的朋友知道,Tinymce编辑器初始化的时候只需要简单的指定一个textarea元素ID就有了,而HtmlTextArea类就是用于产生textarea的html服务器端控件,这样那些Cols、Rows、Style等属性也不需要重新定义,简单的Tinymce编辑器初始化代码如下:
<textarea id="txtContent" name="txtContent" rows="20" cols="100" style="height:200px"></textarea>
<script type="text/javascript" src="../tiny_mce/tiny_mce.js"></script>
<script type="text/javascript" >
tinyMCE.init({
mode : "exact",
elements : "txtContent",
theme : "simple",
language : "zh",
content_css : "/tiny_mce/css/content.css"
});
</script>
如果是继承自HtmlTextArea类的话,首先可以直接产生一个textarea控件,并可以设置Value等属性控制输出值,那么再输出tinymce编辑器主脚本路径和设置和elements为textarea客户端产生的id即可,好象可以行得通,我试着写个测试也通过了,不过我发现继承自Control类(HtmlTextArea类隐式继承Control)在VS切换到设计视图会有问题,在描绘控件控件时候会提示一些错误,有人说是VS版本的问题,我也在网上搜索一些相关资料,尝试了好几种方法都未解决,不过这个问题不会影响到程序的正常运行,喜欢追求完美的我便因为这个小问题毅然放弃了继承Control类。。。
选择好了继承自WebControl类后就开始Copy进TinyMce编辑器的一些属性,由于WebControl类默认不会产生textarea,所以我们需要在初始化的时候实例化一个HtmlTextArea类,以产生textarea控件,并且重写掉父类的Width、Height、CssClass、ClientID等属性,使之当设置这些属性的时候直接关联到textarea控件属性。重写Render输出html的方法以实现自定义输出,这里我注销掉了WebControl父类的Render方法,因为它默认会产生输出一个span标签,而这里不需要。后面接着调用HtmlTextArea控件的Render方法生成textarea,当在VS的ASPX设计视图里Context上下文为空则返回,所以我这里自定义控件在VS设计视图里面看到的就是一个textarea控件。最关键的后面产生JavaScript代码,在输出引用tinymce脚本主文件的时候做了一个判断,因为页面内可能包含了多个Tinymce编辑器控件,但tinymce的脚本主文件只需要引用一次即可,避免重复在页面引用输出,这里我是用Items暂存变量包保存页面的Tinymce编辑器个数,它在整个页面生命周期内有效,之前这个我是在TinyMce自定义控件类初始化的做判断,发现保存在Items暂存变量保存的值有问题,不知道是否为还未传递HttpContext对象的问题。接下来就是tinymce编辑器初试化一些参数的设置问题了,我类属性里面设置了一些默认值,这些都是我摸索着配置出来的默认设置,这里值得一提的是valid_elements属性,当我们在编辑器html源代码里面写一个空div但插入后却被莫名奇妙的被移除设置此属性即可,它主要是验证元素及拥有属性,默认设置的一些元素都无onclick等属性,所以你没有设置它再编辑器内怎么也给不了button按钮一个onclick事件,或插入不了一个空div元素,因为它们不在默认的valid_elements范围之内,不需要使用某些元素比如script脚本标记我们也可以通过设置valid_elements以过滤掉一些不需要使用的标签。
输出tinymce初始化脚本的时候这里还有一个重要的参数onserversave,是的,它就是触发TinyMce自定义控件的OnSave事件参数,tinymce编辑器本身是没有onserversave这个配置属性的,还好我在客户端使用js通过ed.getParam('onserversave')能获取在服务器端输出的值,那么这个onserversave值到底是什么呢?首先我们这个我的这个TinyMce自定义控件类实现IPostBackEventHandler了接口,它使服务器控件能够处理将窗体发送到服务器时引发的事件,也就是在tinymce编辑器内按Ctrl S键时Postback到服务器,并响应TinyMce自定义控件OnSave事件,需要实现RaisePostBackEvent方法,以处理响应事件,并传递eventArgument事件参数,我这里只是做了一个简单的判断,如果TinyMce自定义控件在服务器端设置了OnSave处理事件,则回调该事件,在一些比较复杂的复合控件里面我们可以通过eventArgument参数不同响应不同事件,比如我们在GridView控件里经常能看到编辑按钮是__doPostBack('GridView1','Edit$0')等字样,它所传递响应的就是GridView1控件第0行编辑按钮事件,服务器端获取的eventArgument就是这里的Edit$0,而__doPostBack函数第一个参数__EVENTTARGET是触发服务器控件的UniqueID(不是控件ClientID,相当于Name),说到这里有必要讲一下Asp.Net服务器控件__doPostBack回发事件处理机智,这个还是得从客户端submit说起,如果页面只有一些button服务器控件,这些按钮是直接生成sumbit的,所以这是一个再简单不过的提交表单动作,不需要借助__doPostBack回发函数,所以页面也不会产生任何__doPostBack函数的JavaScript代码,在点击sumbit按钮的时候form会把该按钮的name和value作为健值对post到服务器端,所以在服务器端就能获得触发postback的sumbit按钮name(即UniqueID),此时就很容易在页面里找到该控件并调用的它的Click方法了。当页面拖放了一个LinkButton链接按钮的时候就需要使用__doPostBack函数了,因为a是不能直接Postback的,我们可以把__doPostBack函数贴过来温顾一下:
<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="5o6sLhradLF1t/FSiu1eQ4vknA9uiZViTS36nXHjkdmNX6cRl4LIwK7Kna/xh9t9WhGFmwmdJA8yojvdQLLWOjS9ohcQem358kibQCPhQH9u2ffiui2v4Ao2wCX5GlGLMmS3 pqfb8VyWktLrj1zz7z7CGnnoGScuEsfP93TSIY3HnF1 X8NZT4eJfe2FuzwRVTX/Krq1bkfc6pPs3pcWmPHokdjsr4baM3pJdV 3EM5b1FwHfaIy2bDwMl6sVRCYxvFvumOJzKktI9eColGx4KN0hYz4hz60kcvPZUeFgU=" />
</div>
<script type="text/javascript">
<!--
var theForm = document.forms['aspnetForm'];
if (!theForm) {
theForm = document.aspnetForm;
}
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
// -->
</script>
其实很简单,先是找到aspnetForm这个表单,然后在再__doPostBack函数判断关联的onsubmit()是否返回ture(禁止提交表单的验证),之后将eventTarget和eventArgument参数分别给form表单隐藏域赋值,之后再提交表单,__EVENTTARGET即为触发事件控件的name(即UniqueID),__EVENTARGUMENT就是事件参数了,所以通过这样PostBack到服务器端就能找到对应按纽触发的事件了。
那我如何doPostBack使之触发TinyMce自定义服务器控件的OnSave事件呢?看懂了上面的__doPostBack函数不就知道了,不就是__doPostBack('TinyMce自定义服务器控件UniqueID','eventArgument参数')吗?修改Tinymce编辑器save插件的js源代码即可,找到如下代码块:
_save : function() {
var ed = this.editor, formObj, os, i, elementId;
formObj = tinymce.DOM.get(ed.id).form || tinymce.DOM.getParent(ed.id, 'form');
if (ed.getParam("save_enablewhendirty") && !ed.isDirty())
return;
tinyMCE.triggerSave();
// Use callback instead
if (os = ed.getParam("save_onsavecallback")) {
if (ed.execCallback('save_onsavecallback', ed)) {
ed.startContent = tinymce.trim(ed.getContent({format : 'raw'}));
ed.nodeChanged();
}
return;
}
if (formObj) {
ed.isNotDirty = true;
if (formObj.onsubmit == null || formObj.onsubmit() != false)
formObj.submit();
ed.nodeChanged();
} else
ed.windowManager.alert("Error: No form element found.");
}
这是它原来的代码,变量ed就是当前的编辑器实例了,tinyMCE.triggerSave()是把当前编辑器内容同步保存到对应的textarea内,ed.getParam("save_onsavecallback")是获取编辑器初始化配置参数,如果设置了save_onsavecallback函数则回调函数并返回,回传ed当前编辑器对象这个参数,如果没有save_onsavecallback函数则继续下面的formObj对象判断,tinyMCE会从textarea元素一直往上找到一个form,没有找到则弹窗提示,找到了则submit表单,这就是tinyMCE编辑器按Ctrl S键保存的处理,我现在要改的地方应该很明确了,就是不能让tingMCE就这样直接submit表单了,不然服务器端是没有办法知道是那个按钮要触发事件的,根据上面__doPostBack函数原理,我做了如下修改:
_save : function() {
var ed = this.editor, formObj, os, i, elementId;
formObj = tinymce.DOM.get(ed.id).form || tinymce.DOM.getParent(ed.id, 'form');
if (ed.getParam("save_enablewhendirty") && !ed.isDirty())
return;
tinyMCE.triggerSave();
// Use callback instead
if (os = ed.getParam("save_onsavecallback")) {
if (ed.execCallback('save_onsavecallback', ed)) {
ed.startContent = tinymce.trim(ed.getContent({format : 'raw'}));
ed.nodeChanged();
}
return;
}
if (formObj) {
ed.isNotDirty = true;
if (formObj.onsubmit == null || formObj.onsubmit() != false)
{
//Get Server Postback Event Target Argruments
var eventTarget = ed.getParam('onserversave');
if (eventTarget)
{
var eventTargetElem = document.getElementById('__EVENTTARGET');
if (!eventTargetElem)
{
eventTargetElem = document.createElement('input');
eventTargetElem.id = '__EVENTTARGET';
eventTargetElem.name = '__EVENTTARGET';
eventTargetElem.type = 'hidden';
formObj.appendChild(eventTargetElem);
}
eventTargetElem.value = eventTarget;
formObj.submit();
}
}
ed.nodeChanged();
} else {
ed.windowManager.alert("Error: No form element found.");
}
}
首先是获取初始化控件UniqueID的onserversave参数,这是从服务器端动态输出的,如果TinyMce自定义服务器控件关联了OnSave事件则输出,没有这里获取的就是undefined,我这里是做了判断有才submit回发到服务器端去,然后再去找一个ID为__EVENTTARGET的hidden元素,如果页面没有生成这个元素则创建一个并添加到表单域里面,之后设置它的value为onserversave参数,这一步很重要,在服务器端__EVENTTARGET隐藏的值就是认为是触发服务器控件事件的UniqueID,本来还有一个eventArgument事件参数,但是我这里的TinyMce自定义服务器控件就只有一个OnSave事件不需要传递eventArgument事件参数来判断是触发了那个事件,所有我就没有把__EVENTARGUMENT这个值加到表单域里面去了。这样,当我们在编辑器内按下Ctrl S键后就能Postback并触发TinyMce自定义服务器控件OnSave事件,当然这样对用户体验不够友好,因为只样毕竟是sumbit刷新页面了,噢等等!上面不是说过Tinymce编辑器有一个save_onsavecallback函数,是的,设置这个响应的js函数之后就不会sumbit到服务器端去了,这里只是说为TinyMce自定义服务器控件提供一个这样的事件。我的博客编辑器就是使用了在客户端无刷新的保存的Ajax函数,没当按下Ctrl S键后会在编辑器的左下方提示正在保存,这样不但能及时保存文章内容不会丢失,而且页面都不会刷新给人体验也很好。^_^
好吧,熬了两个晚上,终于总结完了这几个星期使用TinyMCE在线编辑器使用的一些心得,这里非常感谢Earth,是他在广佛都市网项目里面用了这个编辑器,让我有机会了解使用TinyMCE这个免费开源的编辑器,谢谢!
急需这样的一个控件。。。