TinyMce在线编辑器完美打造成Asp.Net服务器自定义控件

玩了tiny_mce在线编辑器好几个星期,今天终于差不多把所有的功能都给完成了,确切的说是把编辑器的插件功能完美的整合在我的博客里面,解决一些小的bug,这还得意于它本身是开源免费的,这里我实现的功能主要有:

  1. 修改图片和多媒体文件上传和浏览功能;
  2. 增加signature个性签名(关联博客)和insertcode插入代码(整合CodeHighlighter代码高亮显示)功能插件;
  3. 修改编辑器内按下Ctrl+S键save保存插件功能,使之支持Postback到服务器端并触发OnSave事件。
  4. 修正编辑器内字体过小、设置编辑器不会自动移除div元素节点的等问题。

起初我引用tiny_mce编辑器都是直接嵌入的脚本的,摸索了一番待完善所有功能后,当然就要把它做成.net的自定义控件了,方便每一个页面调用,下面我就结合在做自定义控件的时候说一下Tinymce编辑器。

试试我的TinyMCE在线编辑器

先上自定义控件源代码:

ContractedBlock.gif TinyMce服务器自定义控件代码
None.gif using System;
None.gif
using System.Data;
None.gif
using System.Configuration;
None.gif
using System.Web;
None.gif
using System.Web.Security;
None.gif
using System.Web.UI;
None.gif
using System.Web.UI.WebControls;
None.gif
using System.Web.UI.WebControls.WebParts;
None.gif
using System.Web.UI.HtmlControls;
None.gif
using System.ComponentModel;
None.gif
None.gif
namespace Studio.Web
ExpandedBlockStart.gifContractedBlock.gif
... {
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// Name : TinyMce在线编辑器
InBlock.gif
/// Author : Jonllen
InBlock.gif
/// Site : www.jonllen.com
InBlock.gif
/// CreateDate : 2009-09-09 23:23
InBlock.gif
/// UpdateDate : 2009-09-10 21:18
ExpandedSubBlockEnd.gif
/// </summary>

InBlock.gif public class TinyMce : WebControl, INamingContainer, IPostBackEventHandler //IPostBackDataHandler
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
InBlock.gif
private string mode = "exact";
InBlock.gif
InBlock.gif [Description(
"模式")]
InBlock.gif
public string Mode
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return mode; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ mode = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string theme = "advanced";
InBlock.gif
InBlock.gif [Description(
"主题")]
InBlock.gif
public string Theme
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return theme; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ theme = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string language = "zh";
InBlock.gif
InBlock.gif [Description(
"语言")]
InBlock.gif
public string Language
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return language; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ language = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string plugins = "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";
InBlock.gif
InBlock.gif [Description(
"插件")]
InBlock.gif
public string Plugins
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return plugins; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ plugins = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
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";
InBlock.gif
InBlock.gif [Description(
"第一排按钮")]
InBlock.gif
public string ThemeAdvancedButtons1
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return theme_advanced_buttons1; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ theme_advanced_buttons1 = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string theme_advanced_buttons2 = "undo,redo,cut,copy,paste,face,separator,outdent,indent,removeformat,cleanup,separator,link,unlink,image,uploadImage,media,separator,save,emotions,signature,insertcode,separator,visualaid,fullscreen,preview,code";
InBlock.gif
InBlock.gif [Description(
"第二排按钮")]
InBlock.gif
public string ThemeAdvancedButtons2
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return theme_advanced_buttons2; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ theme_advanced_buttons2 = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string theme_advanced_buttons3;
InBlock.gif
InBlock.gif [Description(
"第三排按钮")]
InBlock.gif
public string ThemeAdvancedButtons3
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return theme_advanced_buttons3; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ theme_advanced_buttons3 = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string theme_advanced_buttons4;
InBlock.gif
InBlock.gif [Description(
"第四排按钮")]
InBlock.gif
public string ThemeAdvancedButtons4
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return theme_advanced_buttons4; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ theme_advanced_buttons4 = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string valid_elements = "script[language|type|src],style[media,type],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],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]],table[style|border|cellspacing|cellpadding|align|class|id],tr[style|align|valign],th[style|align|valign|colspan|rowspan],td[style|align|valign|colspan|rowspan]";
InBlock.gif
InBlock.gif [Description(
"验证的元素")]
InBlock.gif
public string ValidElements
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return valid_elements; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ valid_elements = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private bool convert_fonts_to_spans = true;
InBlock.gif
InBlock.gif [Description(
"是否将Font标签转化为Span")]
InBlock.gif
public bool ConvertFontsToSpans
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return convert_fonts_to_spans; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ convert_fonts_to_spans = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private bool theme_advanced_resizing = true;
InBlock.gif
InBlock.gif [Description(
"是否可缩放编辑器大小")]
InBlock.gif
public bool ThemeAdvancedResizing
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return theme_advanced_resizing; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ theme_advanced_resizing = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string content_css = "/tiny_mce/css/content.css";
InBlock.gif
InBlock.gif [Description(
"编辑器样式文件路径")]
InBlock.gif
public string ContentCss
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return content_css; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ content_css = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private bool relative_urls = true;
InBlock.gif
InBlock.gif [Description(
"是否使用相对路径(编辑器内图片、链接等内容)")]
InBlock.gif
public bool RelativeUrls
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return relative_urls; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ relative_urls = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private bool remove_script_host = true;
InBlock.gif
InBlock.gif [Description(
"是否移除脚本文件的主机域名(编辑器内)")]
InBlock.gif
public bool RemoveScriptHost
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return remove_script_host; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ remove_script_host = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private bool convert_urls = true;
InBlock.gif
InBlock.gif [Description(
"是否自动转换路径(编辑器内图片、链接等内容)")]
InBlock.gif
public bool ConvertUrls
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return convert_urls; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ convert_urls = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string save_onsavecallback;
InBlock.gif
InBlock.gif [Description(
"按下Ctrl+S键后触发的JavaScript客户端函数")]
InBlock.gif
public string SaveOnsavecallback
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return save_onsavecallback; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ save_onsavecallback = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string setup;
InBlock.gif
InBlock.gif [Description(
"初始化编辑器设置的JavaScript客户端函数")]
InBlock.gif
public string Setup
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return setup; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ setup = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string scriptsrc = "/tiny_mce/tiny_mce.js";
InBlock.gif
InBlock.gif [Description(
"编辑器主脚本文件路径")]
InBlock.gif
public string ScriptSrc
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return scriptsrc; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ scriptsrc = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif [Description(
"文本域控件的高度(以字符为单位)")]
InBlock.gif
public int Rows
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return this.textArea.Rows; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ this.textArea.Rows = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif [Description(
"文本域控件的宽度(以字符为单位)")]
InBlock.gif
public int Cols
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return this.textArea.Cols; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ this.textArea.Cols = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif [Description(
"文本域内容")]
InBlock.gif
public string Text
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return this.textArea.Value; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ this.textArea.Value = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif [Description(
"开始标记和结束标记之间的文本")]
InBlock.gif
public string InnerText
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
string text = this.textArea.Value;
InBlock.gif text
= System.Text.RegularExpressions.Regex.Replace(text, "<[^>]+>", "");
InBlock.gif text
= System.Text.RegularExpressions.Regex.Replace(text, "&[^;]+;", "");
InBlock.gif
return text.Replace("\r\n", "").Trim();
ExpandedSubBlockEnd.gif }

ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private Unit width;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// 重写父类Width属性指向textarea控件的宽度
ExpandedSubBlockEnd.gif
/// </summary>

InBlock.gif [Description("文本域宽度")]
InBlock.gif
public override Unit Width
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return width; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ width = value; this.textArea.Style["width"] = value.ToString(); }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private Unit height;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// 重写父类Height属性指向textarea指向的高度
ExpandedSubBlockEnd.gif
/// </summary>

InBlock.gif [Description("文本域高度")]
InBlock.gif
public override Unit Height
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return height; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ height = value; this.textArea.Style["height"] = value.ToString(); }
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
private string cssClass;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// 重写父类CssClass属性指向textarea控件的class样式类
ExpandedSubBlockEnd.gif
/// </summary>

InBlock.gif [Description("文本域样式类")]
InBlock.gif
public override string CssClass
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get ...{ return cssClass; }
ExpandedSubBlockStart.gifContractedSubBlock.gif
set ...{ cssClass = value; this.textArea.Attributes["class"] = value; }
ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// 重写父类ClientID只读属性指向textarea控件生成的客户端ID
ExpandedSubBlockEnd.gif
/// </summary>

InBlock.gif public override string ClientID
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
return base.ClientID + "_textarea";
ExpandedSubBlockEnd.gif }

ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// 服务器端保存事件
ExpandedSubBlockEnd.gif
/// </summary>

InBlock.gif public event EventHandler Save;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
/**//// <summary>
InBlock.gif
/// IPostBackEventHandler成员
InBlock.gif
/// </summary>
ExpandedSubBlockEnd.gif
/// <param name="eventArgument">传递的参数</param>

InBlock.gif public void RaisePostBackEvent(string eventArgument)
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
if (this.Save != null)
InBlock.gif
this.Save(this, new EventArgs());
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
InBlock.gif
private HtmlTextArea textArea;
InBlock.gif
InBlock.gif
private string itemKey = "TinyMce";
InBlock.gif
InBlock.gif
public TinyMce()
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
//初始化HtmlTextArea,并添加到子控件内
InBlock.gif
this.textArea = new HtmlTextArea();
InBlock.gif
this.textArea.ID = "textarea";
InBlock.gif
//引起回发的Html元素的name属性值必须为控件的 UniqueID,否则 RaisePostBackEvent 事件不会被调用
InBlock.gif
this.textArea.Name = this.UniqueID;
InBlock.gif
this.Controls.Add(textArea);
InBlock.gif
ExpandedSubBlockEnd.gif }

InBlock.gif
ContractedSubBlock.gifExpandedSubBlockStart.gif
重写父类Render生成HTML方法#region 重写父类Render生成HTML方法
InBlock.gif
protected override void Render(HtmlTextWriter writer)
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
//注释父类Render的方法,因为WebControl默认会生成一个<span>标签
InBlock.gif
//base.Render(writer);
InBlock.gif
InBlock.gif
//调用HtmlTextArea控件的Render方法生成HTML
InBlock.gif
this.textArea.RenderControl(writer);
InBlock.gif
InBlock.gif
//当前上下文为空则返回(在VS的ASPX设计视图页面)
InBlock.gif
if (Context == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
return;
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif
//生成Tiny_Mce在线编辑器脚本的字符串
InBlock.gif
System.Text.StringBuilder sb = new System.Text.StringBuilder();
InBlock.gif
InBlock.gif
int count = 0;
InBlock.gif
int.TryParse( Convert.ToString( HttpContext.Current.Items[this.itemKey]) ,out count);
InBlock.gif
//获取当前页面的编辑器数量,如果只有一个则引用Tiny_Mce在线编辑器主脚本文件,避免多个重复引用
InBlock.gif
if ( count == 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"<script type=\"text/javascript\" src=\"{0}\"></script>", this.scriptsrc);
ExpandedSubBlockEnd.gif }

InBlock.gif HttpContext.Current.Items[
this.itemKey] = count + 1;
InBlock.gif
InBlock.gif sb.Append(
"<script type=\"text/javascript\">");
InBlock.gif sb.Append(
"tinyMCE.init({");
InBlock.gif sb.AppendFormat(
"mode : \"{0}\",", this.mode);
InBlock.gif
if (!string.IsNullOrEmpty(this.textArea.ClientID))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"elements : \"{0}\",", this.textArea.ClientID);
ExpandedSubBlockEnd.gif }

InBlock.gif sb.AppendFormat(
"theme : \"{0}\",", this.theme);
InBlock.gif
InBlock.gif
if (this.theme != "simple")
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"language : \"{0}\",", this.language);
InBlock.gif sb.AppendFormat(
"plugins : \"{0}\",", this.plugins);
InBlock.gif
if (!string.IsNullOrEmpty(this.theme_advanced_buttons1))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"theme_advanced_buttons1 : \"{0}\",", this.theme_advanced_buttons1);
ExpandedSubBlockEnd.gif }

InBlock.gif
if (!string.IsNullOrEmpty(this.theme_advanced_buttons2))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"theme_advanced_buttons2 : \"{0}\",", this.theme_advanced_buttons2);
ExpandedSubBlockEnd.gif }

InBlock.gif
if (!string.IsNullOrEmpty(this.theme_advanced_buttons3))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"theme_advanced_buttons3 : \"{0}\",", this.theme_advanced_buttons3);
ExpandedSubBlockEnd.gif }

InBlock.gif
if (!string.IsNullOrEmpty(this.theme_advanced_buttons4))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"theme_advanced_buttons4 : \"{0}\",", this.theme_advanced_buttons4);
ExpandedSubBlockEnd.gif }

InBlock.gif sb.AppendFormat(
"valid_elements : \"{0}\",", this.valid_elements);
InBlock.gif sb.AppendFormat(
"convert_fonts_to_spans : {0},", this.convert_fonts_to_spans ? "true" : "false");
InBlock.gif sb.AppendFormat(
"theme_advanced_resizing : {0},", this.theme_advanced_resizing ? "true" : "false");
InBlock.gif sb.AppendFormat(
"relative_urls : {0},", this.relative_urls ? "true" : "false");
InBlock.gif sb.AppendFormat(
"remove_script_host : {0},", this.remove_script_host ? "true" : "false");
InBlock.gif sb.AppendFormat(
"convert_urls : {0},", this.convert_urls ? "true" : "false");
InBlock.gif
if (!string.IsNullOrEmpty(this.save_onsavecallback))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"save_onsavecallback : {0},", this.save_onsavecallback);
ExpandedSubBlockEnd.gif }

InBlock.gif
if (!string.IsNullOrEmpty(this.setup))
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif sb.AppendFormat(
"setup : {0},", this.setup);
ExpandedSubBlockEnd.gif }

InBlock.gif
if (string.IsNullOrEmpty(this.save_onsavecallback) && this.Save != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif
...{
InBlock.gif
//输出服务器端触发回发事件的eventTarget参数到tinymce编辑器初始话参数
InBlock.gif
sb.AppendFormat("onserversave : \"{0}\",", this.UniqueID);
ExpandedSubBlockEnd.gif }

ExpandedSubBlockEnd.gif }

InBlock.gif sb.AppendFormat(
"content_css : \"{0}\"", this.content_css);
InBlock.gif sb.Append(
"});");
InBlock.gif sb.Append(
"</script>");
InBlock.gif
InBlock.gif
//输出
InBlock.gif
writer.WriteLine(sb.ToString());
ExpandedSubBlockEnd.gif }

ExpandedSubBlockEnd.gif
#endregion

InBlock.gif
ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

首先做一个自定义控件需要考虑的继承类,是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这个免费开源的编辑器,谢谢!

本文为 Jonllen原创文章,遵循知识署名方式共享,首发于个人博客原文地址 http://jonllen.com/Article.aspx?aid=66,转载请保留此段声明。本文以"现状"提供,且没有任何担保,同时也没有授予任何权利。

转载于:https://www.cnblogs.com/Jonllen/archive/2009/09/12/TinyMce.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值