mutator 是一种 mixin,可向 block 添加额外的序列化(要保存和加载的额外状态)。例如,内置的 controls_if
和 list_create_with
代码块需要额外的序列化,以便保存它们拥有的输入数量。它还可能会添加界面,以便用户更改块的形状。
请注意,更改块的形状并不一定意味着您需要额外的序列化。例如,math_number_property
块会更改形状,但它是根据下拉字段(其值已序列化)来更改形状的。因此,它只需使用字段验证器,而不需要 mutator。
如需详细了解何时需要 mutator 以及何时不需要,请参阅序列化页面。
如果您提供一些可选方法,mutator 还会提供一个内置界面,供用户更改块的形状。
序列化钩子
Mutator 有两对序列化钩子,它们可以与这些钩子搭配使用。其中一对钩子适用于新的 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
,则不会向 JSON 添加 extraState
属性。这样可确保保存文件的大小较小。
完整序列化和后备数据
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
函数返回 null,则不会向 XML 添加任何额外的元素。
界面钩子
如果您在突变器中提供某些函数,Blockly 会向您的块添加默认的“突变器”界面。
如果您想添加额外的序列化,则不必使用此界面。您可以使用自定义界面(例如 blocks-plus-minus 插件提供的界面),也可以完全不使用界面!
组合和分解
默认界面依赖于 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
或者,您还可以定义一个与默认界面搭配使用的 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 只是一个特殊的 mixin,因此也必须先注册,然后才能在块类型的 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
:要与 mutator 关联的字符串,以便您可以在 JSON 中使用它。mixinObj
:包含各种突变方法的对象。例如,saveExtraState
和loadExtraState
。opt_helperFn
:一个可选的辅助函数,将在混入 mixin 后在块上运行。opt_blockList
:一个可选的块类型(以字符串形式)数组,如果还定义了界面方法,则会添加到默认 mutator 界面中的弹出式菜单。
请注意,与扩展程序不同,每种块类型只能有一个 mutator。
{
//...
"mutator": "controls_if_mutator"
}
辅助函数
除了混入之外,变异器还可以注册辅助函数。此函数在创建给定类型的每个块并添加 mixinObj
后运行。它可用于向突变添加其他触发器或效果。
例如,您可以向类似列表的块添加一个辅助函数,用于设置初始商品数量:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}