突變

變異器是混入,可將額外的序列化 (儲存及載入的額外狀態) 新增至區塊。舉例來說,內建的 controls_iflist_create_with 區塊需要額外序列化,才能儲存輸入內容的數量。此外,它也可能會新增 UI,讓使用者變更方塊形狀。

建立清單區塊的三種突變:沒有輸入內容、三個輸入內容和五個輸入內容。

if/do 區塊的兩種變異:if-do 和 if-do-else-if-do-else。

請注意,變更區塊形狀不一定代表需要額外序列化。舉例來說,math_number_property 區塊會變更形狀,但這是根據下拉式選單欄位進行,而該欄位的值已序列化。因此,它只能使用欄位驗證器,不需要變異子。

`math_number_property` 區塊,下拉式選單設為 `even`。這個區塊有一個單一值輸入。 `math_number_property` 區塊,下拉式選單設為「可整除」。這個區塊有兩個值輸入。

如要進一步瞭解何時需要變動函式,請參閱序列化頁面

如果您提供一些選用方法,變動器也會提供內建 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 序列化系統使用。只有在必要時 (例如您正在處理尚未遷移的舊程式碼),才使用這些 Hook,否則請使用 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 Hook

如果您在變動器中提供特定函式,Blockly 會在方塊中加入預設的「變動器」UI。

開啟變異器泡泡的 if-do 區塊。使用者可以將 else-if 和 else 子句新增至 if-do 區塊。

如要新增額外的序列化作業,不必使用這個 UI。您可以選擇使用自訂 UI,例如 blocks-plus-minus 外掛程式提供的 UI,也可以完全不使用 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

您也可以選擇定義 saveConnections 函式,與預設 UI 搭配使用。這個函式可讓您將主要區塊 (位於主要工作區) 的子項與變動器工作區中的子區塊建立關聯。然後使用這項資料,確保 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();
  }
},

註冊中

變數是特殊類型的混合,因此也必須先註冊,才能在區塊類型的 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();
}