跳转到内容

XSLTForms/TinyMCE

来自维基教科书,开放世界中的开放书籍

与其他 XForms 实现一样,XSLTForms 支持一个非标准控件来编辑混合内容(包含元素和字符数据的 XML)。

XSLTForms 可以(至少在理论上)与 CKEditorTinyMCE 一起使用。本页面描述了将 TinyMCE 与 XSLTForms 一起使用。它基于 XSLTForms 的 638 版本;其他版本的行为可能有所不同。

使用 TinyMCE 与 XSLTForms 的框架

[编辑 | 编辑源代码]

要在 XForm 中使用 TinyMCE 作为控件,需要做几件事。一个 使用 TinyMCE 的简单最小示例 可在 AgenceXML 网站上获得;它说明了以下所有要点。

  • 一个 script 元素应该指向 TinyMCE 库并指定 TinyMCE 版本号。
  • 应该提供一个 XSD 模式,它定义了使用 TinyMCE 编辑元素的适当简单类型;此类型定义中的 xsd:appinfo 元素包含 TinyMCE 用来自定义编辑器的初始化对象。
  • 使用 TinyMCE 编辑的元素应绑定到 XSD 模式中声明的简单类型。

指向 TinyMCE Javascript 并提供 TinyMCE 版本号

[编辑 | 编辑源代码]

包含具有以下属性的 XHTML script 元素

  • type,其值为 text/javascript
  • src 提供对 TinyMCE 库的引用(相对于 XForm)
  • data-uri 指定正在使用的哪个富文本编辑器;对于 TinyMCE,使用值 http://www.tinymce.com
  • data-version 指定正在使用的 TinyMCE 版本

内容可以是空的;Javascript 注释 (/* */) 就可以。

例如

    <script type="text/javascript"
            src="xsltforms/scripts/tinymce_4.0.21/tinymce.min.js"
            data-uri="http://www.tinymce.com"
            data-version="4.0.21"
            >/* */</script>

data-version 属性由 XSLTForms 用于调整其对 TinyMCE 的调用;它区分 TinyMCE 的版本 3 和 4。(或者,更准确地说,它区分以“3.”开头的版本号和所有其他版本。)但是,请注意,并非所有版本的 TinyMCE 4 都与 XSLTForms 版本 638 兼容(参见 以下)。

提供富文本类型和 TinyMCE 初始化对象

[编辑 | 编辑源代码]

将 TinyMCE 与 XSLTForms 一起使用的关键部分是提供一个 xsd:schema 元素,它可以嵌入 XHTML 标头。以下是一个非常简单的示例

      <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:xforms="http://www.w3.org/2002/xforms"
                  targetNamespace="http://www.agencexml.com/xsltforms/rte">
        <xsd:simpleType name="standardHTML">
          <xsd:restriction base="xforms:HTMLFragment" xsltforms:rte="TinyMCE"/>

          <xsd:annotation>
            <xsd:appinfo> {
                plugins: [ "code" ],
                toolbar: 'undo redo | bold italic | bullist'
              } </xsd:appinfo>
          </xsd:annotation>
        </xsd:simpleType>
      </xsd:schema>

如您所见,此模式定义了一个简单类型,它具有许多值得注意的属性

  • 该类型的扩展名为 {http://www.agencexml.com/xsltforms/rte}standardHTML。(尚不清楚名称是否必须为此名称或是否可以有所不同。[推测(未经证实):它可以有所不同,以便不同实例的 TinyMCE 具有不同的自定义功能可以共存于同一个表单中。])
  • 它的基本类型是 {http://www.w3.org/2002/xforms}HTMLFragment。(这似乎是必不可少的;xsltforms.js 中有代码检查这一点。)
  • 其定义中的 xsd:restriction 元素带有属性-值规范 xsltforms:rte="TinyMCE"(其中前缀 xsltforms 绑定到命名空间 http://www.agencexml.com/xsltforms)。XSLTForms 使用此属性的值来区分特定于 TinyMCE 的代码和特定于 CKEditor 的代码。
  • 类型定义包含一个 xsd:appinfo 元素,其字符数据内容是一个 Javascript 对象。严格来说,appinfo 元素是可选的(如果没有,则假定默认值为 { }),但在实践中几乎总是需要它。appinfo 元素中描述的 Javascript 对象将(经过一些修改)作为 TinyMCE 的 init() 函数的参数传递给 TinyMCE;它是自定义 TinyMCE 的基本机制。

将实例绑定到富文本类型

[编辑 | 编辑源代码]

使用 TinyMCE 编辑的元素应绑定到上面描述的模式中为其声明的类型。

例如

  <xf:bind nodeset="richtext" type="rte:standardHTML"/>

请注意,要编辑的元素的内容应该是只有字符数据:字符串,没有子元素。该字符串可以(并且通常会)包含元素和属性的序列化 XML 表示。

参见 序列化形式和 XML 形式,如下所示。

序列化形式和 XML 形式

[编辑 | 编辑源代码]

使用 TinyMCE 和 CKEditor 等混合内容编辑器需要表单设计人员仔细区分称为 XML 文档或片段的正常 XML 形式 和相同材料的 序列化形式。一些其他构造也会带来相同的挑战,例如 transform()serialize() 函数以及 setnode 操作。(这个主题有广泛的混淆可能性,因为 XML 规范定义了 XML 文档的序列化形式;我们有时会在 XML 文档中写入 XML 元素的序列化形式也使事情变得复杂。如果读者发现这里讨论很令人困惑,请耐心等待;对表单进行实验也可能有所帮助。)

XML 形式

[编辑 | 编辑源代码]

我们在这里所说的实例文档的 XML 形式 在不同的时刻以两种不同的方式表示

  • 在 XHTML+XForms 文档本身中,它表示为一个正常的 XML 文档或元素,使用 XML 语法。
  • 在使用 XSLTForms 传递的表单的运行实例中,它表示为一个 DOM 对象。

前者通过以下 XForm 片段说明,该片段显示了一个名为 normal-document 的实例,其根元素名为 data,并有一个名为 richtext 的子元素,该子元素又有一个名为 charge 的子元素。

      <xf:instance id="normal-document" xmlns="">
          <data><richtext>
              <charge>
                    <p>uncertain<en>
                        <p>Gruen (<i>Historia</i> 1966) 38 suggests a 
                            <procedure pid="c-maiestas" lang="lat">maiestas</procedure> 
                            trial for seditious behavior as tribune.</p>
                     </en></p>
              </charge>
          </richtext></data>
      </xf:instance>

当此 XML 被 XForms 处理器读取时,将根据文档对象模型 (DOM) 的规则构建一个数据结构来表示它。

然而,在编辑混合内容时,TinyMCE 不会对数据的 DOM 表示进行操作;它对包含用尖括号标记的标签的字符串进行操作:要编辑的数据的序列化形式。(此设计选择的理由尚不清楚。)

序列化形式

[edit | edit source]

如果打算使用 TinyMCE 编辑文档实例,使用文档的正常 XML 形式将导致不理想的结果。(TinyMCE 将获取 richtext 元素的字符串值,可能将其包装在 p 元素中(取决于配置),并丢失 procedure 上的属性等细节。)为了使用 TinyMCE 进行有效编辑,实例文档必须以 序列化形式 呈现,即作为包含一系列字符(包括尖括号)的字符串。要在 XML 上下文中(例如 XForm)写入此类字符串,必须转义数据序列化形式中出现的左尖括号和和号。完成此操作后,xf:instance 元素将看起来像这样

    <xf:instance id="serialized-document" xmlns="">
        <data><richtext>
&lt;charge>
    &lt;p>uncertain&lt;en>&lt;p>Gruen (&lt;i>Historia&lt;/i> 1966) 38 suggests a &lt;procedure pid="c-maiestas" lang="lat">maiestas&lt;/procedure> trial for seditious behavior as tribune.&lt;/p>&lt;/en>&lt;/p>
&lt;/charge>
        </richtext></data>
    </xf:instance>

或者作为另一种选择(使用 CDATA 标记的部分),像这样

      <xf:instance id="serialized-document-2" xmlns="">
          <data><richtext><![CDATA[
              <charge>
                    <p>uncertain<en>
                        <p>Gruen (<i>Historia</i> 1966) 38 suggests a 
                            <procedure pid="c-maiestas" lang="lat">maiestas</procedure> 
                            trial for seditious behavior as tribune.</p>
                     </en></p>
              </charge>
          ]]></richtext></data>
      </xf:instance>

与 XML 形式一样,实例文档的 序列化形式 在不同的时刻以两种不同的方式表示

  • 在使用 XSLTForms 传递的表单的运行实例中,它表示为符合 XML 语法规则的一系列字符。
  • 在 XHTML+XForms 文档本身中,它表示为一个 转义 的字符串。


来回移动

[edit | edit source]

如果 TinyMCE 要用在具有正常 XML 结构的文档实例(或实例的一部分)上,则必须序列化文档。serialize() 函数在此处很有用。用户编辑完数据的序列化版本后,如果希望恢复其正常的 XML 结构,则必须重新解析序列化数据;xf:setnode 扩展元素为此目的很有用。

[需要示例,显示从正常形式到序列化形式的来回移动。]

控制样式

[edit | edit source]

XForms 富文本控件可以通过 XSLTForms 的通常方式进行样式设置。

例如,以下 style 元素设置了类 large-textarea 的元素内的文本区域控件的字体系列、高度和宽度。

    <style type="text/css">
			.large-textarea textarea {
				font-family: Courier, sans-serif;
				height: 10em;
				width: 500px;
			}
    </style>

然后,可以通过简单地将其分配给类 large-textarea 来将实际的 XForms 控件链接到此样式

  <textarea ref="richtext" 
            class="large-textarea" 
            mediatype="application/xhtml+xml"/>

有关在 XForms 中设置控件样式的更多信息,请参阅 本维基教科书关于 CSS 的讨论

自定义 TinyMCE 编辑器

[edit | edit source]

自定义 TinyMCE 的基本机制是 初始化对象。这是一个用户指定的 Javascript 对象,TinyMCE 在初始化期间会参考它。

在 TinyMCE 文档中,它显示为对 TinyMCE init() 函数的调用中的函数参数。TinyMCE 文档中的示例可能看起来像下面的示例;这显示了一个简单的初始化对象,其属性为 selectortoolbar

  <script>
      tinymce.init({
        selector: '#mytextarea',
        toolbar: 'undo redo | bold italic | bullist'
      });
  </script>

在 XSLTForms 中,对 TinyMCE init() 函数的调用由 XSLTForms 处理,而不是由 XForm 作者处理。因此,初始化对象不是在对 init() 的调用中指定,而是在要编辑的元素所绑定的简单类型中的 xsd:appinfo 元素的字符内容中指定。在 XSLTForms 上下文中,不会使用 selector 属性(如果提供,它将被忽略,并由 XSLTForms 覆盖),因此上面所示的初始化对象的等效项将看起来像这样(周围的架构未显示)

  <xsd:appinfo> {
      toolbar: 'undo redo | bold italic | bullist'
  } </xsd:appinfo>

以下小节描述了一些简单的自定义示例,但有关自定义 TinyMCE 的所有可能方法的完整信息,请参阅您正在使用的 TinyMCE 版本的文档。(此处的示例使用 TinyMCE 4。)

除了少数例外,TinyMCE 文档中描述的每个初始化属性都可以在 xsd:appinfo 元素中指定,XSLTForms 会将其值传递给 TinyMCE,因此其效果将与 TinyMCE 文档中描述的一致。

例外情况如下

  • location 属性(TinyMCE 文档中强调,因为 TinyMCE 要求)由 XSLTForms 提供,基于数据的类型绑定。用户传入的任何 location 值都将被 XSLTForms 为正在初始化的特定控件实例生成的 ID 覆盖。
  • mode 属性(似乎没有在 TinyMCE 文档中描述,但可能会在 editor.setMode() 调用中使用)由 XSLTForms 覆盖,值为 "none"
  • setup 属性(其值为一个函数,TinyMCE 在编辑器实例初始化期间会评估该函数)由 XSLTForms 提供。用户传入的任何 setup 值都将被覆盖。需要使用 setup 属性的 TinyMCE 自定义(包括自定义按钮的规范)因此不可用。有关解决方法,请参阅下面的自定义按钮讨论。


在 TinyMCE 编辑器中设置元素样式

[edit | edit source]

要在 TinyMCE 编辑器中显示元素时使用的样式表可以通过在 TinyMCE 初始化对象中将 'content_css' 属性的值设置为其 URI(相对于 XForm 的位置)来提供。例如

content_css: 'mce-control.css'

设置工具栏、菜单等。

[edit | edit source]

toolbar 属性可用于指定工具栏中包含的内容;它有助于消除不需要的工具,以便拥有更简单的界面。

'自定义' 元素和 '有效' 元素

[edit | edit source]

TinyMCE 努力生成干净的 HTML,并允许 TinyMCE 库的用户进一步约束 HTML。

如果初始化对象指定了 valid_elements 列表,则数据中的其他元素将在数据清理时被剥离(这发生在将数据发送回 XSLTForms 之前,以及在其他时间)。因此,valid_elements 可用于将要编辑的数据限制为一组指定的元素。

也可以在 custom_elements 属性中指定非 XHTML 元素。

不在 XHTML 中的 XML 元素

[edit | edit source]

在典型的 XForms 应用程序中,要编辑的 XML 不一定是 XHTML;可以通过将它们的名字指定为 custom_elements 属性的值来支持具有未知于 XHTML 规范的名字的元素。

[似乎有必要在 custom_elements 属性和 valid_elements 属性中都指定自定义元素。测试以获取更多信息是一个好主意。]

还可以指定允许哪些元素作为特定元素的子元素。在数据清理期间,如果 TinyMCE 认为数据中存在的元素作为其父元素的子元素不合法,则它可能会重构数据。[需要更多细节。]

意外、复杂情况、问题

[edit | edit source]

版本问题

[edit | edit source]

在哪里找到 TinyMCE,在哪里放置 TinyMCE

[edit | edit source]

有一段时间,XSLTForms 附带了 TinyMCE 版本 3.4.6;代码放置在 XSLTForms 目录内的 scripts/tinymce_3.4.6/tiny_mce.js 位置。使用其他版本的 TinyMCE 时,可以从 TinyMCE 网站下载最小化版本并将其安装在 scripts 的适当命名子目录中。其他位置也可能有效。

TinyMCE 自版本 3.4.6 以来已发生了一些变化;一些变化涉及外观和感觉(与 4 相比,TinyMCE 3 看起来过时了),另一些变化涉及 API。

XSLTForms 支持哪些版本的 TinyMCE?

[edit | edit source]

XSLTForms 638 支持 TinyMCE 的版本 3 和版本 4;AgenceXML 网站上提供的 TinyMCE 的最小示例使用的是 TinyMCE 4.0.28。但似乎并非所有版本的 TinyMCE 4 都能与 XSLTForms 版本 638 一起使用;似乎可以使用的最新版本是 4.3.12(2016 年 5 月)。更高版本的版本会产生一个永远不会消失的“正在加载...”消息,以及 Javascript 错误控制台上的错误消息。

注意:目前预计 XSLTForms 的 640 版本将支持最新版本的 TinyMCE,即 4.5.3。

自定义按钮

[编辑 | 编辑源代码]

addButton() 方法

[编辑 | 编辑源代码]

为了定义编辑器的自定义按钮,TinyMCE 文档建议在 TinyMCE 编辑器对象上调用 addButton() 方法;该方法的参数是一个对象,用于指定按钮标识符、按钮标签或图标、按钮点击时要运行的函数等。

TinyMCE 文档给出了一个例子

editor.addButton('mybutton', {
  text: "My Button",
  onclick: function () {
     alert("My Button clicked!");
  }
});

setup() 回调函数

[编辑 | 编辑源代码]

第一个明显的复杂之处是,所讨论的编辑器对象只有在调用 TinyMCE 的 init() 函数后才会可用。因此,上面显示的代码不能简单地放在页面中的 script 元素中。相反,用户需要为 TinyMCE 提供一个回调函数,该函数将在初始化过程中执行。(一些读者可能在其他名称下知道回调函数,比如钩子或用户退出。)用户通过将该函数作为 TinyMCE 初始化对象中 setup 属性的值来提供它;setup() 函数接收一个参数,即编辑器对象。

  <xsd:appinfo> {
        // other properties here ...

        setup: function(editor) {
            editor.addButton('mybutton', {
                text: "My Button",
                onclick: function () { alert("Clicked!"); 
            });
          }

    } </xsd:appinfo>

按钮操作

[编辑 | 编辑源代码]

在实践中,可能会出现其他问题。如果按钮的目的是为自定义短语级元素做内置 bolditalic 按钮对 HTML strongem 元素所做的事情,那么首先需要使用上面描述的 custom_elementsvalid_elements 属性来声明并使自定义元素有效。

其次,必须有函数来执行按钮点击时所需的动作。如上所示,该函数不应该接收任何参数。因此,为了与编辑器交互,我们需要在 TinyMCE 编辑器对象可用的上下文中声明该函数:即在 setup() 函数内部,其中编辑器对象作为参数传入。

一个例子

[编辑 | 编辑源代码]

假设我们想要用一个自定义元素标记当前选中的文本,即在当前选择之前插入一个开始标签,在当前选择之后插入一个结束标签。为了减少代码中的冗余,我们可以使用一个通用的 Javascript 函数来定义它,该函数接收元素类型名称作为参数,以及任意数量的元素特定函数,这些函数调用通用函数

    function insertElem(gi) {
              var s = '&lt;' + gi + '&gt;' 
                    + editor.selection.getContent()
                    + '&lt;/' + gi + '&gt;';
              editor.insertContent(s); 
    } 
    function insertPerson () { insertElem('person'); }
    function insertProc () { insertElem('procedure'); }

将所有内容组合到初始化对象中,我们得到一个类似下面的 appinfo 元素

  <xsd:appinfo> {
        // other properties here ...

        // adjust toolbar to taste, but don't forget to add the custom buttons
        // 'insertperson' and 'insertprocedure'
        toolbar: 'undo redo | insertperson insertprocedure italic | bullist',
        custom_elements:  "charge,~person,~procedure,en",
        valid_elements:  "charge,p,i,person[pid],procedure[pid],en",
        setup: function(editor) {

            function insertPerson () { insertElem('person'); }
            function insertProc () { insertElem('procedure'); }

            function insertElem(gi) {
                      var s = '&lt;' + gi + '&gt;' 
                            + editor.selection.getContent()
                            + '&lt;/' + gi + '&gt;';
                      editor.insertContent(s); 
            } 
            editor.addButton('insertperson', {
                text: "Person",
                onclick: insertPerson,
                tooltip: "Insert person element"
            });
            editor.addButton('insertprocedure', {
                text: "Procedure",
                onclick: insertProc,
                tooltip: "Insert procedure element"
            });
          }

    } </xsd:appinfo>

XSLTForms 638 的必要修改

[编辑 | 编辑源代码]

剩下的复杂之处是,虽然上面显示的初始化对象将与 TinyMCE 一起使用,但默认情况下 XSLTForms 会用它自己的设置函数覆盖用户提供的 setup 属性。

为了允许 TinyMCE 执行标准 XSLTForms 设置和用户提供的设置,需要对 xsltforms.js 版本 638 的代码进行三个更改(当然,无法保证对其他版本的适用性)。所有三个更改都发生在名为 XsltForms_input.prototype.initInput 的函数的定义中。在 XSLTForms 638 中,很容易通过搜索字符串“setup”来找到它,该字符串只出现在该函数中。

注意:目前预计 r640 版本将包含补丁或等效补丁,因此此处描述的更改将不再需要。

1. 在读取 initinfo.mode = "none"; 的行之后,插入以下行

 initinfo.Xsltforms_usersetup = 
     (initinfo.setup 
     ? initinfo.setup 
     : function (ed) {} );

这定义了一个名为 Xsltforms_usersetup 的属性(该名称是为了尽量减少与未来 TinyMCE 版本中的任何新属性发生冲突的可能性),它的值为用户提供的 setup 属性的值(如果有),否则是一个空函数,什么也不做。

2. 紧随其后的是一个条件,用于测试 TinyMCE 版本 3.*;if/then/else 的每个分支都为 setup 属性分配一个值。

then 分支中,通过插入对 initinfo.Xsltforms_usersetup() 的调用来更改该函数。替换

    initinfo.setup = function(ed) {
            ed.onKeyUp.add(function(ed) {
                  ...
            });
            ed.onChange.add(function(ed) {
                  ...
            });
            ed.onUndo.add(function(ed) {
                  ...
            ed.onRedo.add(function(ed) {
                  ...
            });
    };

    initinfo.setup = function(ed) {
            /* user exit for setup() */
            initinfo.Xsltforms_usersetup(ed);

            ed.onKeyUp.add(function(ed) {
                  ...
            });
            // etc.
            ...
    };

3. 在 else 中,执行相同操作。对于

    initinfo.setup = function(ed) {
        ed.on("KeyUp", function() {
                  ...
        });
        ed.on("Change", function(ed) {
                  ...
        });
        ed.on("Undo", function(ed) {
                  ...
        });
        ed.on("Redo", function(ed) {
                  ...
        });
    };

替换为

    initinfo.setup = function(ed) {
        /* user exit for setup() */
        initinfo.Xsltforms_usersetup(ed);

        ed.on("KeyUp", function() {
                  ...
        });
        ... etc. ...
    };

通过这些更改,可以使用 TinyMCE 为 XSLTForms 混合内容控件定义自定义按钮。

华夏公益教科书