跳轉到內容

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 混合內容控制元件定義自定義按鈕。

華夏公益教科書