TinyMCE 和 ASP.NET MVC - 高级功能






4.78/5 (11投票s)
在 ASP.NET MVC 中设置 TinyMCE 富文本编辑器,使用一些不易部署的高级功能
引言
TinyMCE 是一个强大的 JavaScript 富文本编辑器,相对容易集成到您的应用程序中,并获得 LGPL 许可,这意味着您可以将其用于商业应用程序。作为一款 Javascript 组件,它可以与您想要的任何 Web 技术结合使用:PHP、JSF、Ruby on Rails 等。本文将以 ASP.NET MVC 中的 TinyMCE 使用为例,正如读者可能已经知道的,ASP.NET MVC 是一个完整的 MVC 框架;因此,它适应其他 Web 开发框架非常简单。
然而,本文不会关注 TinyMCE 的基础知识,因为这些内容在其文档中很容易找到。本文将展示如何使用那些不易理解其实现的高级功能,为读者提供使用这款出色组件的非常有用的帮助。本文将重点关注以下功能:
- 在同一个表单中使用多个编辑器
- 使用 jQuery 进行验证 - 需要一些调整。
- 设置允许的最大 HTML 字符数。
- 通过 TinyMCE 加载事件,在页面加载后将内容加载到富文本区域。
- 使用 TinyMCE 时操作 HTML 标签。
背景
由于以下因素,TinyMCE 确实应该被考虑作为富文本编辑器的选择:
- 免费用于商业用途。
- 它拥有富文本编辑器所需的大部分功能,例如:字体更改、链接管理、文本操作、视频使用等。
- 易于使用。
- 拥有良好的文档。
- 拥有各种论坛和讨论,无疑将帮助开发人员。
- 由于它是一个纯粹的 JavaScript 工具,因此可以与您想要的任何 Web 框架结合使用。
使用代码
应用程序概述
此处将描述应用程序的工作方式,以便读者更好地理解代码,并充分了解所实现的功能。
首先,当应用程序启动时,将显示如下所示的屏幕。
您可以看到屏幕上有两个富文本区域。要使用它们,只需用您想要的任何功能填写文本即可。另请注意,当您在富文本区域中填写内容时,计数器会显示正在使用的 HTML 字符数,并且有一个允许的最大数量。下图显示了一个示例。
之后,按“创建”按钮提交表单。接下来显示的是一个屏幕,其中包含之前在富文本区域中的相同数据,以及每个富文本区域发送的未格式化纯转义 HTML 文本,如下所示:
请注意屏幕顶部显示的这些值。它们包含文本以及转义的 HTML 字符,这些字符格式化了富文本区域中的文本。这些数据是软件必须存储的数据,因为这些数据包含了富文本区域的格式化文本。
观察富文本区域中的文本。如前所述,它们已重新加载到此处。这是在 TinyMCE 组件的加载事件中完成的。
最后,下面显示了验证错误消息(这些消息是使用 jQuery 实现的):
在第一个富文本区域中,验证失败,因为文本是必填的;在第二个区域中,验证不成功,因为插入了超过 5000 个字符。
理解代码
现在是时候理解代码本身了。解压项目 zip 文件后,您应该使用 Microsoft Visual Studio 2010(或更高版本)或 Microsoft Visual Web Developer 2010(或更高版本)打开项目。您会发现这是一个标准的 ASP.NET MVC 3 项目。以下是关于此项目需要注意的主要方面:
- 在名为 Scripts 的文件夹中,有一个名为 tiny_mce 的文件夹。它包含从 TinyMCE 主页下载的 JavaScript 代码。
- 同样,在 Scripts 文件夹中,有一个名为 JHomeINdex.js 的文件。此文件使用 TinyMCE 操作 UI。
- 在 Controllers 文件夹中,有一个用于此应用程序的 Controller - HomeControl.cs。
- 在 Models 文件夹中,有一个用于此小型软件的 Model - SampleModel。
- 在 View 文件夹中,Home 文件夹内有一个 Index.cshtml,这是此应用程序使用的唯一 View(网页),而 _Layout.cshtml 是此小型应用程序的“母版页”。
从 Model - SampleModel 开始,请看下面的代码:
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace MvcAppTnyMCE.Models
{
public class SampleModel
{
[AllowHtml]
[Display(Name = "Ritch Text 1")]
public String RichText1 {get; set;}
[AllowHtml]
public String RichText1FullHtml { get; set; }
[AllowHtml]
[Display(Name = "Ritch Text 2")]
public String RichText2 {get; set;}
[AllowHtml]
public String RichText2FullHtml { get; set; }
}
}
请注意,显示的四个字段与程序运行时屏幕上显示的文本区域相关。`RitchText1` 和 `RichText2` 字段直接绑定到 TinyMCE 组件使用的 textarea。`RichText1FullHtml` 和 `RichText2FullHtml` 用于显示按下“创建”按钮后从富文本区域提交的数据。如果您回顾本文并查看软件的功能,您会发现 Model 中使用的元素足以满足应用程序的需求。此外,请注意,在参数中使用了 `AllowHtml` 标签,以避免设置和获取 HTML 内容的安全问题。最后,请注意每个字段的显示名称已在此处定义。
接下来,将分析 Controller - HomeController.cs(如下所示):
using System.Web.Mvc;
using MvcAppTnyMCE.Models;
namespace MvcAppTnyMCE.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(SampleModel model)
{
model.RichText1FullHtml = model.RichText1;
model.RichText2FullHtml = model.RichText2;
model.RichText1 = null;
model.RichText2 = null;
return View(model);
}
}
}
这个 Controller 非常简单。第一个方法只是显示 View - Index.cshtml。第二个方法从 Model 中检索数据 - 获取用户在两个富文本区域(`RitchText1` 和 `RichText2`)中输入的值,并将它们设置在字段(`RichText1FullHtml` 和 `RichText2FullHtml`)中,以便将它们显示为 HTML 元素。最后,为了周全考虑,将 `RitchText1` 和 `RichText2` 的值置为 null,这样就可以确保它们将在 TinyMCE 组件的加载事件中设置。
继续,接下来值得讨论的代码是 View 元素 - Index.cshtml。请看下面的代码:
@model MvcAppTnyMCE.Models.SampleModel
@using MvcAppTnyMCE.Models;
@{
ViewBag.Title = "TinyMCE Sample";
}
<h2>TinyMCE Sample</h2>
@section JavaScriptSection {
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/tiny_mce/tiny_mce.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/JHomeIndex.js")" type="text/javascript"></script>
}
@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "formText" })) {
<fieldset>
<legend>TinyMCE Sample</legend>
@{
if (Model != null && Model.RichText1FullHtml != null && Model.RichText1FullHtml.Length > 0)
{
<p>
The submitted text from "Ritch Text 1" is:
</p>
<div>
@Model.RichText1FullHtml;
</div>
<input id="RichText1Value" name="RichText1Value"
type="hidden" value="@Server.HtmlDecode(Model.RichText1FullHtml)"/>
}
if (Model != null && Model.RichText2FullHtml != null && Model.RichText2FullHtml.Length > 0)
{
<p>
The submitted text from "Ritch Text 2" is:
</p>
<div>
@Model.RichText2FullHtml;
</div>
<input id="RichText2Value" name="RichText2Value"
type="hidden" value="@Server.HtmlDecode(Model.RichText2FullHtml)"/>
}
}
<p>
@Html.LabelFor(m => m.RichText1)
<input id="RichText1Copy" name="RichText1Copy" type="hidden"/>
@Html.TextAreaFor(m => m.RichText1, new { @class = "mceEditor" })
</p>
<p>
@Html.LabelFor(m => m.RichText2)
<input id="RichText2Copy" name="RichText2Copy" type="hidden"/>
@Html.TextAreaFor(m => m.RichText2, new { @class = "mceEditor" })
</p>
<p>
<input id="saveButton" type="submit" value="Create" />
</p>
</fieldset>
}
这里正在做一些事情:
- 导入必要的 JavaScript 脚本。基本的 jQuery 脚本已在 _Layout.cshtml View 中导入。
- 表单已定义,ID 为 formText。
- 页面顶部显示 HTML 提交的富文本区域,接下来处理。请注意,只有在确实有数据可用时才显示某些内容。另请注意,这些数据保存在隐藏字段(`RichText1Value` 和 `RichText2Value`)中,因为稍后将使用它们在 TinyMCE 组件的加载事件中重新填充富文本区域。
- 这里还有两件事需要提及:
- 用于 TinyMCE 的 Model 绑定值。它们是:`RitchText1` 和 `RichText2`。
- 另外请注意,正在创建两个隐藏字段变量(`RichText1Copy` 和 `RichText2Copy`)。如稍后将看到的,这些变量用于使用 jQuery 进行验证。这是通过 jQuery 实现成功验证的必要解决方法。
现在是核心代码,即使用 JavaScript 操作 UI 的 JHomeIndex.js 脚本。请看下面的代码:
// max number of chars allwed in a tinyMice component
var tinyMiceMaxCharLength = 5000;
function loadTinyMiceRichTextFeature(controlName, inputCopyIdentifier) {
var configArray = {
// General options
mode: "textareas",
theme: "advanced",
encoding: "xml",
mode : "specific_textareas",
editor_selector : "mceEditor",
mode: "none",
plugins: "autolink,lists,pagebreak,style,table,advhr,advlink,emotions,
iespell,inlinepopups,insertdatetime,preview,searchreplace,print,paste,
directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,
template,wordcount,advlist,autosave,visualblocks",
// Theme options
theme_advanced_buttons1: "save,newdocument,|,bold,italic,underline,
strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect",
theme_advanced_buttons2: "fontselect,fontsizeselect,!,cut,copy,paste,
pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent",
theme_advanced_buttons3: "blockquote,|,undo,redo,|,link,unlink,anchor,|,
insertdate,inserttime,preview,!,hr,removeformat,visualaid,|,
sub,sup,|,charmap,emotions,iespell,media,advhr,|,print",
theme_advanced_buttons4: "tablecontrols",
theme_advanced_buttons5: "moveforward,movebackward,absolute,|,cite,abbr,
acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak,
restoredraft,visualblocks,|,forecolor,backcolor,fullscreen,|,ltr,rtl,|",
theme_advanced_toolbar_location: "top",
theme_advanced_toolbar_align: "left",
theme_advanced_statusbar_location: "bottom",
theme_advanced_resizing: true,
theme_advanced_path: false,
// Example content CSS (should be your site CSS)
content_css: "../../Content/Site.css",
// Drop lists for link/image/media/template bloglists
template_external_list_url: "lists/template_list.js",
external_link_list_url: "lists/link_list.js",
external_image_list_url: "lists/image_list.js",
media_external_list_url: "lists/media_list.js",
// Style formats
style_formats: [
{ title: 'Bold Text', inline: 'b' },
{ title: 'Red Text', inline: 'span', styles: { color: '#ff0000'} },
{ title: 'Red Title', block: 'h1', styles: { color: '#ff0000'} }
],
charLimit: tinyMiceMaxCharLength, // this is a default value which can get modified later
//set up a new editor function
setup: function (ed) {
//peform this action every time a key is pressed
ed.onKeyUp.add(function (ed, e) {
updateTinyMiceHtmlCounter(ed, e, inputCopyIdentifier);
});
// event called when the content is to be loaded in the rich text editor
ed.onLoadContent.add(function (ed, o) {
if (tinyMCE.editors != null && tinyMCE.editors.length > 0) {
var trueValue = "#formText " + " #" + controlName + "Value";
if ($(trueValue) != null && $(trueValue).val() != null) {
// update content
tinyMCE.activeEditor.setContent($(trueValue).val());
// trigger click so validation will be performed even
// if no data is modified in the editor
updateTinyMiceHtmlCounter(ed, o, inputCopyIdentifier);
}
}
});
}
};
tinyMCE.settings = configArray;
tinyMCE.execCommand('mceAddControl', true, controlName);
}
// update tiny mice html counter
function updateTinyMiceHtmlCounter(ed, e, inputCopyIdentifier) {
//define local variables
var tinymax, tinylen, htmlcount;
//manually setting our max character limit
tinymax = ed.settings.charLimit;
//grabbing the length of the curent editors content
tinylen = ed.getContent().length;
//setting up the text string that will display in the path area
htmlcount = "# of HTML characters: " + tinylen + "/" + tinymax;
//if the user has exceeded the max turn the path bar red.
if (tinylen > tinymax) {
htmlcount = "<span style='font-weight:bold; color: #f00;'>" +
htmlcount + "</span>";
}
// copy content to follow up text area so it can be validated
$(inputCopyIdentifier).val(ed.getContent());
//this line writes the html count into the path row of the active editor
tinymce.DOM.setHTML(tinymce.DOM.get(tinyMCE.activeEditor.id + '_path_row'), htmlcount);
}
var inputCopyId1 = "#formText #RichText1Copy";
var inputCopyId2 = "#formText #RichText2Copy";
$().ready(function () {
// load rich text feature
loadTinyMiceRichTextFeature("RichText1", inputCopyId1);
loadTinyMiceRichTextFeature("RichText2", inputCopyId2);
// allow the validation of hidden fields so the tinymce textarea
// can be validated
$.validator.setDefaults({ ignore: "" });
// validate form
validateForm();
});
// validate form
function validateForm() {
$().ready(function () {
$("#formText").validate({
rules: {
RichText1Copy: {
required: true,
maxlength: tinyMiceMaxCharLength
},
RichText2Copy: {
maxlength: tinyMiceMaxCharLength
}
}
});
});
}
尽管这段代码看起来很长,但并不难理解。其中一些部分将详细解释,以便读者了解其用途。
请注意以下代码:
$().ready(function () {
// load rich text feature
loadTinyMiceRichTextFeature("RichText1", inputCopyId1);
loadTinyMiceRichTextFeature("RichText2", inputCopyId2);
// allow the validation of hidden fields so the tinymce textarea
// can be validated
$.validator.setDefaults({ ignore: "" });
// validate form
validateForm();
});
这段代码在 Index.cshtml 加载时执行。看看这里发生了什么:
- TinyMCE 组件通过 `loadTinyMiceRichTextFeature(controlName, inputCopyIdentifier)` 方法附加到 `RichText1` 和 `RichText2` 字段。请注意,`RichText1` 和 `RichText2` 是将使用 TinyMCE 组件的 textarea 标签的 ID。另请注意,`loadTinyMiceRichTextFeature(...)` 方法使用第二个参数,该参数是与每个富文本区域关联的隐藏字段(`RichText1Value` 和 `RichText2Value`)的 jQuery 标识符。
- 使用 jQuery 进行的验证也在这里通过 `validateForm()` 方法设置。
现在,请看下面显示的 `loadTinyMiceRichTextFeature(...)` 方法:
// max number of chars allwed in a tinyMice component
var tinyMiceMaxCharLength = 5000;
function loadTinyMiceRichTextFeature(controlName, inputCopyIdentifier) {
var configArray = {
// General options
mode: "textareas",
theme: "advanced",
encoding: "xml",
mode : "specific_textareas",
editor_selector : "mceEditor",
mode: "none",
plugins: "autolink,lists,pagebreak,style,table,advhr,advlink,emotions,iespell,
inlinepopups,insertdatetime,preview,searchreplace,print,paste,directionality,
fullscreen,noneditable,visualchars,nonbreaking,
xhtmlxtras,template,wordcount,advlist,autosave,visualblocks",
// Theme options
theme_advanced_buttons1: "save,newdocument,|,bold,italic,underline,strikethrough,|,
justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect",
theme_advanced_buttons2: "fontselect,fontsizeselect,!,cut,copy,paste,
pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent",
theme_advanced_buttons3: "blockquote,|,undo,redo,|,link,unlink,anchor,|,insertdate,
inserttime,preview,!,hr,removeformat,visualaid,|,sub,sup,|,
charmap,emotions,iespell,media,advhr,|,print",
theme_advanced_buttons4: "tablecontrols",
theme_advanced_buttons5: "moveforward,movebackward,absolute,|,cite,abbr,acronym,del,
ins,attribs,|,visualchars,nonbreaking,template,pagebreak,restoredraft,
visualblocks,|,forecolor,backcolor,fullscreen,|,ltr,rtl,|",
theme_advanced_toolbar_location: "top",
theme_advanced_toolbar_align: "left",
theme_advanced_statusbar_location: "bottom",
theme_advanced_resizing: true,
theme_advanced_path: false,
// Example content CSS (should be your site CSS)
content_css: "../../Content/Site.css",
// Drop lists for link/image/media/template bloglists
template_external_list_url: "lists/template_list.js",
external_link_list_url: "lists/link_list.js",
external_image_list_url: "lists/image_list.js",
media_external_list_url: "lists/media_list.js",
// Style formats
style_formats: [
{ title: 'Bold Text', inline: 'b' },
{ title: 'Red Text', inline: 'span', styles: { color: '#ff0000'} },
{ title: 'Red Title', block: 'h1', styles: { color: '#ff0000'} }
],
charLimit: tinyMiceMaxCharLength, // this is a default value which can get modified later
//set up a new editor function
setup: function (ed) {
//peform this action every time a key is pressed
ed.onKeyUp.add(function (ed, e) {
updateTinyMiceHtmlCounter(ed, e, inputCopyIdentifier);
});
// event called when the content is to be loaded in the rich text editor
ed.onLoadContent.add(function (ed, o) {
if (tinyMCE.editors != null && tinyMCE.editors.length > 0) {
var trueValue = "#formText " + " #" + controlName + "Value";
if ($(trueValue) != null && $(trueValue).val() != null) {
// update content
tinyMCE.activeEditor.setContent($(trueValue).val());
// trigger click so validation will be performed even
// if no data is modified in the editor
updateTinyMiceHtmlCounter(ed, o, inputCopyIdentifier);
}
}
});
}
};
tinyMCE.settings = configArray;
tinyMCE.execCommand('mceAddControl', true, controlName);
}
它做了很多事情,包括:
- 为作为第一个方法参数输入的文本区域设置 TinyMCE。此参数必须是 textarea 的 ID(`RichText1` 和 `RichText2`)。第二个参数用于标识将保存富文本区域中输入文本副本的隐藏字段(`RichText1Value` 和 `RichText2Value`)。它们用于验证,如下文所述。
- 现在是事件部分。
- 在 `updateTinyMiceHtmlCounter(...)` 方法中,`onkeyUp` 事件执行以下操作:对于添加到或从每个文本区域删除的每个字符,它计算已键入的 HTML 字符数,更新消息,并且如果此值超过某个限制,消息的颜色将发生变化。此外,每次调用此事件时,整个内容都会被复制到 `updateTinyMiceHtmlCounter(...)` 方法的 `inputCopyIdentifier` 参数标识的隐藏字段中。复制到隐藏字段对于验证目的非常重要。由于 TinyMCE 以开发人员无法控制的方式修改文本区域,因此无法在 TinyMCE 定义的富文本区域中进行验证。因此,最安全的验证方法是将内容复制到隐藏字段,并使用该字段进行验证。
- `onLoadContent(...)` 事件负责在 TinyMCE 加载到页面时加载数据。因此,每次调用此事件时,都需要验证是否有数据要在富文本区域中加载,并执行加载。请注意,此方法从表单顶部的隐藏字段(`RichText1Value` 和 `RichText2Value`)获取值,其中显示了 HTML 内容。如果这些值确实存在,它们将被放入 TinyMCE 组件中。您可能会认为,在 Index.cshtml 中通过绑定直接设置值是一种简单的解决方案。然而,这样做时,TinyMCE 无法正确解释 HTML,因此该解决方案不适用。
最后要讨论的部分是验证,它由下面显示的 `validateForm()` 方法完成:
// validate form
function validateForm() {
$().ready(function () {
$("#formText").validate({
rules: {
RichText1Copy: {
required: true,
maxlength: tinyMiceMaxCharLength
},
RichText2Copy: {
maxlength: tinyMiceMaxCharLength
}
}
});
});
}
如您所见,这是一个简单的 jQuery 验证。然而,这里验证的是隐藏字段,这些隐藏字段在每次通过 `updateTinyMiceHtmlCounter(...)` 方法更改字符时接收来自富文本区域的值。如前所述,这是以这种方式实现的,因为如果有人尝试验证 TinyMCE 使用的文本区域,验证将不起作用,因为 TinyMCE 以不可预测的方式修改这些字段。请注意,此处验证的是字段是否为必填字段以及允许的最大 HTML 字符数。
这段代码乍一看可能很复杂,但花点时间理解它,您就会得出结论,它其实相当简单。
按 HTML 字符数限制与按键入字符数限制
可能会出现的一个讨论是限制 HTML 字符数而非键入字符数的原因。有几个原因:
- 在 JavaScript 中计算键入字符数并不容易,并且可能导致性能问题。
- 实际的字符限制是 HTML 字符,而不是键入字符。因此,按键入字符数限制可能会导致一种情况,即没有足够的空间来保存数据,因为 HTML 字符数始终高于键入字符数。
- TinyMCE 并没有为此提供任何简单的解决方案,因此一切都必须手动完成。
结论
设置 TinyMCE 的基础知识非常简单,其文档足以完成此操作。然而,其他更复杂的任务,例如使用多个组件、使用事件进行验证和加载数据,并不那么简单。尽管如此,通过各种论坛和一些调查,本文作者得以解决所有问题,以满足各种软件需求。这是 TinyMCE 的一个关键优势。有各种论坛和讨论可以帮助开发人员克服困难。
另一个重要特性是,由于它是一个纯粹的 Javascript 组件,因此可以与各种 Web 开发技术结合使用。这已经在此处提到,但强调这个伟大的方面很重要。
TinyMCE 有两种版本:JavaScript 版和 jQuery 版。奇怪的是,本文使用的是 JavaScript 版本,原因很简单:它的文档更丰富。
有了这个小型应用程序示例,您可能已经满足了大部分需求,使其比以往更容易使用。