擴充功能和更改器

擴充功能是在建立區塊時,於特定類型的每個區塊上執行的函式。這些工具通常會將一些自訂設定或行為加到區塊中。

變更器是一種特殊的擴充功能,可將自訂序列化 (有時是 UI) 新增至區塊。

擴充功能

擴充功能是在建立區塊時,於特定類型的每個區塊上執行的函式。他們可以新增自訂設定 (例如設定區塊的工具提示) 或自訂行為 (例如將事件監聽器加入區塊)。

// This extension sets the block's tooltip to be a function which displays
// the parent block's tooltip (if it exists).
Blockly.Extensions.register(
    'parent_tooltip_extension',
    function() { // this refers to the block that the extension is being run on
      var thisBlock = this;
      this.setTooltip(function() {
        var parent = thisBlock.getParent();
        return (parent && parent.getInputsInline() && parent.tooltip) ||
            Blockly.Msg.MATH_NUMBER_TOOLTIP;
      });
    });

擴充功能必須「註冊」,才能與字串鍵建立關聯。接著,您可以將這個字串鍵指派給區塊類型 JSON 定義extensions 屬性,以將擴充功能套用至區塊。

{
 //...,
 "extensions": ["parent_tooltip_extension",]
}

此外,您也可以一次新增多個額外資訊。請注意,即使您只套用一個擴充功能,extensions 屬性也必須是陣列。

{
  //...,
  "extensions": ["parent_tooltip_extension", "break_warning_extension"],
}

合輯

Blockly 也提供便利方法,讓您可在想將部分屬性/協助函式加入區塊中,但並未立即執行。您可以註冊包含所有其他屬性/方法的 mixin 物件。接著,混合物件會納入函式中,每次建立指定區塊類型的執行個體時,都會套用混合。

Blockly.Extensions.registerMixin('my_mixin', {
  someProperty: 'a cool value',

  someMethod: function() {
    // Do something cool!
  }
))`

與 Mixins 相關聯的字串索引鍵可以在 JSON 中參照,就像任何其他副檔名一樣。

{
 //...,
 "extensions": ["my_mixin"],
}

動盪器

變更器是一種特殊類型的擴充功能,可將額外的序列化作業 (儲存和載入的額外狀態) 加入區塊。舉例來說,內建的 controls_iflist_create_with 區塊需要額外的序列化作業,以便儲存具有的輸入內容數量。

請注意,變更區塊形狀「不一定」需要額外的序列化作業。舉例來說,math_number_property 區塊會變更形狀,但會根據下拉式選單欄位 (其值已序列化) 執行。因此,只要使用欄位驗證工具,不需要異動器。

如要進一步瞭解何時需要修改器以及何時不需要,請參閱序列化頁面

如果您提供一些選用方法,Mutators 也提供內建的 UI,讓使用者可以變更區塊的形狀。

序列化掛鉤

更動子會使用兩對序列化掛鉤,一組掛鉤可與新的 JSON 序列化系統搭配使用,另一個掛鉤則能與舊 XML 序列化系統搭配運作。至少須提供其中一個組合。

saveExtraState 和 loadExtraState

saveExtraStateloadExtraState 是可與新的 JSON 序列化系統搭配使用的序列化掛鉤。saveExtraState 會傳回 JSON 序列化值,代表區塊的額外狀態,而 loadExtraState 接受相同的 JSON 序列化值,並將其套用至區塊。

// These are the serialization hooks for the lists_create_with block.
saveExtraState: function() {
  return {
    'itemCount': this.itemCount_,
  };
},

loadExtraState: function(state) {
  this.itemCount_ = state['itemCount'];
  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},

產生的 JSON 應如下所示:

{
  "type": "lists_create_with",
  "extraState": {
    "itemCount": 3 // or whatever the count is
  }
}
沒有狀態

如果區塊在序列化時處於預設狀態,則 saveExtraState 方法可以傳回 null 來表示。如果 saveExtraState 方法傳回 null,則 JSON 中不會新增任何 extraState 屬性。藉此縮減儲存檔案的大小。

完整序列化與備份資料

saveExtraState 也會收到選用的 doFullSerialization 參數。區塊會參照其他序列化程式 (例如備份資料模型) 序列化的狀態,藉此使用此功能。參數表示在區塊反序列化時將無法使用參照狀態,因此該區塊應將所有備份狀態本身序列化。例如,當個別區塊序列化或複製區塊時,情況會是如此。

有兩種常見用途:

  • 如果將個別區塊載入的工作區不存在備份資料模型,則該區塊本身狀態的資訊足以建立新的資料模型。
  • 當您複製貼上區塊時,系統一律會建立新的備份資料模型,而非參照現有的區塊。

使用這種區塊的部分區塊為 @blockly/block-shareable-procedures 區塊。一般來說,他們會序列化儲存備份資料模型的參照。不過,如果 doFullSerialization 參數為 true,則會將所有狀態序列化。可共用程序區塊使用此功能,確保在複製貼上內容時,會建立新的備份資料模型,而非參照現有的模型。

mutationToDom 和 domToMutation

mutationToDomdomToMutation 是適用於舊版 XML 序列化系統的序列化掛鉤。請只在有必要時使用這些掛鉤 (例如您正在使用尚未遷移的舊程式碼集),否則請使用 saveExtraStateloadExtraState

mutationToDom 會傳回代表區塊額外狀態的 XML 節點,而 domToMutation 接受相同的 XML 節點,並將狀態套用至該區塊。

// These are the old XML serialization hooks for the lists_create_with block.
mutationToDom: function() {
  // You *must* create a <mutation></mutation> element.
  // This element can have children.
  var container = Blockly.utils.xml.createElement('mutation');
  container.setAttribute('items', this.itemCount_);
  return container;
},

domToMutation: function(xmlElement) {
  this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},

產生的 XML 應如下所示:

<block type="lists_create_with">
  <mutation items="3"></mutation>
</block>

如果 mutationToDom 函式傳回空值,系統就不會在 XML 中加入任何額外元素。

UI 掛鉤

如果您提供特定函式做為變更器的一部分,Blockly 會將預設的「差異工具」使用者介面新增至您的區塊。

如要新增額外的序列化作業,您不需要使用這個 UI。您可以使用自訂 UI (例如 blocks-plus-minus 外掛程式),也可以完全不使用 UI!

撰寫和分解

預設 UI 依賴 composedecompose 函式。

decompose 會將區塊「分解」為較小的子區塊,可以四處移動、新增和刪除。這個函式應會傳回「頂層區塊」,這是子區塊所連線的變異器工作區中的主要區塊。

compose 接著會解讀子區塊的設定,並使用這些區塊修改主要區塊。此函式應接受 decompose 傳回的「頂層區塊」做為參數。

請注意,這些函式會「混合」至被「變化」的區塊,因此 this 可以用來參照該區塊。

// These are the decompose and compose functions for the lists_create_with block.
decompose: function(workspace) {
  // This is a special sub-block that only gets created in the mutator UI.
  // It acts as our "top block"
  var topBlock = workspace.newBlock('lists_create_with_container');
  topBlock.initSvg();

  // Then we add one sub-block for each item in the list.
  var connection = topBlock.getInput('STACK').connection;
  for (var i = 0; i < this.itemCount_; i++) {
    var itemBlock = workspace.newBlock('lists_create_with_item');
    itemBlock.initSvg();
    connection.connect(itemBlock.previousConnection);
    connection = itemBlock.nextConnection;
  }

  // And finally we have to return the top-block.
  return topBlock;
},

// The container block is the top-block returned by decompose.
compose: function(topBlock) {
  // First we get the first sub-block (which represents an input on our main block).
  var itemBlock = topBlock.getInputTargetBlock('STACK');

  // Then we collect up all of the connections of on our main block that are
  // referenced by our sub-blocks.
  // This relates to the saveConnections hook (explained below).
  var connections = [];
  while (itemBlock && !itemBlock.isInsertionMarker()) {  // Ignore insertion markers!
    connections.push(itemBlock.valueConnection_);
    itemBlock = itemBlock.nextConnection &&
        itemBlock.nextConnection.targetBlock();
  }

  // Then we disconnect any children where the sub-block associated with that
  // child has been deleted/removed from the stack.
  for (var i = 0; i < this.itemCount_; i++) {
    var connection = this.getInput('ADD' + i).connection.targetConnection;
    if (connection && connections.indexOf(connection) == -1) {
      connection.disconnect();
    }
  }

  // Then we update the shape of our block (removing or adding iputs as necessary).
  // `this` refers to the main block.
  this.itemCount_ = connections.length;
  this.updateShape_();

  // And finally we reconnect any child blocks.
  for (var i = 0; i < this.itemCount_; i++) {
    connections[i].reconnect(this, 'ADD' + i);
  }
},

saveConnections

或者,您也可以定義適用於預設 UI 的 saveConnections 函式。此函式讓您有機會將主要區塊 (主要工作區中) 的子項與變異器工作區中的子區塊建立關聯。在重新編排子區塊時,您可以使用這項資料確保 compose 函式正確重新連結主要區塊的子項。

saveConnections 應接受 decompose 函式傳回的「頂層區塊」做為參數。如果已定義 saveConnections 函式,Blockly 會在呼叫 compose 之前呼叫該函式。

saveConnections: function(topBlock) {
  // First we get the first sub-block (which represents an input on our main block).
  var itemBlock = topBlock.getInputTargetBlock('STACK');

  // Then we go through and assign references to connections on our main block
  // (input.connection.targetConnection) to properties on our sub blocks
  // (itemBlock.valueConnection_).
  var i = 0;
  while (itemBlock) {
    // `this` refers to the main block (which is being "mutated").
    var input = this.getInput('ADD' + i);
    // This is the important line of this function!
    itemBlock.valueConnection_ = input && input.connection.targetConnection;
    i++;
    itemBlock = itemBlock.nextConnection &&
        itemBlock.nextConnection.targetBlock();
  }
},

註冊中

Mutator 是一種特殊的擴充功能,因此必須先註冊,才能在區塊類型的 JSON 定義中使用。

// Function signature.
Blockly.Extensions.registerMutator(name, mixinObj, opt_helperFn, opt_blockList);

// Example call.
Blockly.Extensions.registerMutator(
    'controls_if_mutator',
    { /* mutator methods */ },
    undefined,
    ['controls_if_elseif', 'controls_if_else']);
  • name:用來與變異器建立關聯的字串,可讓您在 JSON 中使用該字串。
  • mixinObj:包含各種變異方法的物件。例如 saveExtraStateloadExtraState
  • opt_helperFn:選用的輔助函式,會在混合混合加入後於區塊上執行。
  • opt_blockList:如果也定義了 UI 方法,系統會將選用的區塊類型陣列 (以字串形式) 新增至預設變異器 UI 中的飛出。

請注意,與擴充功能不同,每種封鎖類型只能有一個異動器。

{
  //...
  "mutator": "controls_if_mutator"
}

輔助函式

除了混合以外,變體也可以註冊輔助函式。這個函式建立後,會在指定類型的每個區塊上執行,並新增 MixinObj。可用於為變異新增額外的觸發條件或效果。

舉例來說,您可以將輔助程式新增至清單類區塊中,以設定項目的初始數量:

var helper = function() {
  this.itemCount_ = 5;
  this.updateShape();
}