變異器是一種混合器,可在區塊中新增額外的序列化 (可儲存和載入的額外狀態)。舉例來說,內建的 controls_if
和 list_create_with
區塊需要額外的序列化,才能儲存輸入內容的數量。您也可以新增 UI,讓使用者變更區塊的形狀。
請注意,變更區塊的形狀不一定表示您需要額外的序列化。舉例來說,math_number_property
區塊會變更形狀,但會根據下拉式選單欄位執行,而該欄位的值已完成序列化。因此,它只會使用欄位驗證器,不需要修飾器。
如要進一步瞭解何時需要變異器,何時不需要,請參閱序列化頁面。
如果您提供一些選用方法,變化器也會提供內建 UI,讓使用者變更區塊的形狀。
序列化掛鉤
轉換器有兩組序列化鉤子。一組鉤子可搭配新的 JSON 序列化系統,另一組則可搭配舊的 XML 序列化系統。您必須提供至少一組。
saveExtraState 和 loadExtraState
saveExtraState
和 loadExtraState
是序列化鉤子,可與新的 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
,則不會將 extraState
屬性新增至 JSON。這樣可縮減儲存檔案的大小。
完整序列化和備份資料
saveExtraState
也會接收選用的 doFullSerialization
參數。此類型會由參照由不同序列化器 (例如支援資料模型) 序列化的狀態的區塊使用。這個參數會指出在區塊反序化時,所參照的狀態將無法使用,因此區塊應自行序列化所有備援狀態。舉例來說,當個別區塊序列化或區塊複製貼上時,就會為 True。
這項功能有兩種常見用途:
- 當個別區塊載入至沒有基礎資料模型的工作區時,其本身狀態就含有足夠的資訊,可用來建立新的資料模型。
- 當您複製貼上區塊時,系統一律會建立新的備援資料模型,而非參照現有模型。
使用此功能的部分區塊包括 @blockly/block-shareable-procedures 區塊。通常會將參照序列化為備援資料模型,以便儲存狀態。但如果 doFullSerialization
參數為 true,則會將所有狀態序列化。可共用的程序區塊會使用這項功能,確保在複製貼上時,會建立新的備援資料模型,而非參照現有模型。
mutationToDom 和 domToMutation
mutationToDom
和 domToMutation
是序列化鉤子,可與舊版 XML 序列化系統搭配使用。請僅在必要時使用這些掛鉤 (例如,您正在處理尚未遷移的舊程式碼集),否則請使用 saveExtraState
和 loadExtraState
。
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。您可以使用自訂 UI (例如 blocks-plus-minus 外掛程式提供的),也可以完全不使用 UI!
組合和分解
預設 UI 會依賴 compose
和 decompose
函式。
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();
}
},
註冊中
變異器只是一種特殊的混合器,因此您必須先註冊變異器,才能在區塊類型的 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
:包含各種變異方法的物件。例如saveExtraState
和loadExtraState
。opt_helperFn
:選用的輔助函式,會在混入混合後在區塊上執行。opt_blockList
:如果也定義了 UI 方法,則會將選用的區塊類型陣列 (以字串表示) 新增至預設變更器 UI 中的彈出式視窗。
請注意,與擴充功能不同,每個區塊類型可能只有一個修飾符。
{
//...
"mutator": "controls_if_mutator"
}
輔助函式
除了 mixin,變換器也可能會註冊輔助函式。這個函式會在建立指定類型的每個區塊並新增 mixinObj 後執行。可用於在變異中新增其他觸發事件或效果。
舉例來說,您可以將輔助程式新增至清單式區塊,設定項目的初始數量:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}