XSLTForms/TinyMCE
与其他 XForms 实现一样,XSLTForms 支持一个非标准控件来编辑混合内容(包含元素和字符数据的 XML)。
XSLTForms 可以(至少在理论上)与 CKEditor 或 TinyMCE 一起使用。本页面描述了将 TinyMCE 与 XSLTForms 一起使用。它基于 XSLTForms 的 638 版本;其他版本的行为可能有所不同。
要在 XForm 中使用 TinyMCE 作为控件,需要做几件事。一个 使用 TinyMCE 的简单最小示例 可在 AgenceXML 网站上获得;它说明了以下所有要点。
- 一个
script
元素应该指向 TinyMCE 库并指定 TinyMCE 版本号。 - 应该提供一个 XSD 模式,它定义了使用 TinyMCE 编辑元素的适当简单类型;此类型定义中的
xsd:appinfo
元素包含 TinyMCE 用来自定义编辑器的初始化对象。 - 使用 TinyMCE 编辑的元素应绑定到 XSD 模式中声明的简单类型。
包含具有以下属性的 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 与 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 形式,如下所示。
使用 TinyMCE 和 CKEditor 等混合内容编辑器需要表单设计人员仔细区分称为 XML 文档或片段的正常 XML 形式 和相同材料的 序列化形式。一些其他构造也会带来相同的挑战,例如 transform()
和 serialize()
函数以及 setnode
操作。(这个主题有广泛的混淆可能性,因为 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>
<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>
或者作为另一种选择(使用 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 文档中的示例可能看起来像下面的示例;这显示了一个简单的初始化对象,其属性为 selector
和 toolbar
。
<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。
为了定义编辑器的自定义按钮,TinyMCE 文档建议在 TinyMCE 编辑器对象上调用 addButton()
方法;该方法的参数是一个对象,用于指定按钮标识符、按钮标签或图标、按钮点击时要运行的函数等。
TinyMCE 文档给出了一个例子
editor.addButton('mybutton', {
text: "My Button",
onclick: function () {
alert("My Button clicked!");
}
});
第一个明显的复杂之处是,所讨论的编辑器对象只有在调用 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>
在实践中,可能会出现其他问题。如果按钮的目的是为自定义短语级元素做内置 bold
和 italic
按钮对 HTML strong
和 em
元素所做的事情,那么首先需要使用上面描述的 custom_elements
和 valid_elements
属性来声明并使自定义元素有效。
其次,必须有函数来执行按钮点击时所需的动作。如上所示,该函数不应该接收任何参数。因此,为了与编辑器交互,我们需要在 TinyMCE 编辑器对象可用的上下文中声明该函数:即在 setup()
函数内部,其中编辑器对象作为参数传入。
假设我们想要用一个自定义元素标记当前选中的文本,即在当前选择之前插入一个开始标签,在当前选择之后插入一个结束标签。为了减少代码中的冗余,我们可以使用一个通用的 Javascript 函数来定义它,该函数接收元素类型名称作为参数,以及任意数量的元素特定函数,这些函数调用通用函数
function insertElem(gi) {
var s = '<' + gi + '>'
+ editor.selection.getContent()
+ '</' + gi + '>';
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 = '<' + gi + '>'
+ editor.selection.getContent()
+ '</' + gi + '>';
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>
剩下的复杂之处是,虽然上面显示的初始化对象将与 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 混合内容控件定义自定义按钮。