65.9K
CodeProject 正在变化。 阅读更多。
Home

TinyMCE 和 ASP.NET MVC - 高级功能

2013 年 10 月 29 日

CPOL

10分钟阅读

viewsIcon

102275

downloadIcon

3817

在 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>
}

这里正在做一些事情:

  1. 导入必要的 JavaScript 脚本。基本的 jQuery 脚本已在 _Layout.cshtml View 中导入。
  2. 表单已定义,ID 为 formText。
  3. 页面顶部显示 HTML 提交的富文本区域,接下来处理。请注意,只有在确实有数据可用时才显示某些内容。另请注意,这些数据保存在隐藏字段(`RichText1Value` 和 `RichText2Value`)中,因为稍后将使用它们在 TinyMCE 组件的加载事件中重新填充富文本区域。
  4. 这里还有两件事需要提及:
    1. 用于 TinyMCE 的 Model 绑定值。它们是:`RitchText1` 和 `RichText2`。
    2. 另外请注意,正在创建两个隐藏字段变量(`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 加载时执行。看看这里发生了什么:

  1. TinyMCE 组件通过 `loadTinyMiceRichTextFeature(controlName, inputCopyIdentifier)` 方法附加到 `RichText1` 和 `RichText2` 字段。请注意,`RichText1` 和 `RichText2` 是将使用 TinyMCE 组件的 textarea 标签的 ID。另请注意,`loadTinyMiceRichTextFeature(...)` 方法使用第二个参数,该参数是与每个富文本区域关联的隐藏字段(`RichText1Value` 和 `RichText2Value`)的 jQuery 标识符。
  2. 使用 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);
}  

它做了很多事情,包括:

  1. 为作为第一个方法参数输入的文本区域设置 TinyMCE。此参数必须是 textarea 的 ID(`RichText1` 和 `RichText2`)。第二个参数用于标识将保存富文本区域中输入文本副本的隐藏字段(`RichText1Value` 和 `RichText2Value`)。它们用于验证,如下文所述。
  2. 现在是事件部分。
    1. 在 `updateTinyMiceHtmlCounter(...)` 方法中,`onkeyUp` 事件执行以下操作:对于添加到或从每个文本区域删除的每个字符,它计算已键入的 HTML 字符数,更新消息,并且如果此值超过某个限制,消息的颜色将发生变化。此外,每次调用此事件时,整个内容都会被复制到 `updateTinyMiceHtmlCounter(...)` 方法的 `inputCopyIdentifier` 参数标识的隐藏字段中。复制到隐藏字段对于验证目的非常重要。由于 TinyMCE 以开发人员无法控制的方式修改文本区域,因此无法在 TinyMCE 定义的富文本区域中进行验证。因此,最安全的验证方法是将内容复制到隐藏字段,并使用该字段进行验证。
    2. `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 版本,原因很简单:它的文档更丰富。

有了这个小型应用程序示例,您可能已经满足了大部分需求,使其比以往更容易使用。

© . All rights reserved.