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/javascriptsrc提供對 TinyMCE 庫的引用(相對於 XForm)data-uri指定正在使用的哪個富文字編輯器;對於 TinyMCE,使用值http://www.tinymce.comdata-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 混合內容控制元件定義自定義按鈕。