Мутатор — это миксин, который добавляет дополнительную сериализацию (дополнительное состояние, которое сохраняется и загружается) к блоку. Например, встроенным блокам controls_if
и list_create_with
нужна дополнительная сериализация, чтобы они могли сохранять количество имеющихся у них входов. Он также может добавлять пользовательский интерфейс, чтобы пользователь мог изменять форму блока.
Обратите внимание, что изменение формы вашего блока не обязательно означает, что вам нужна дополнительная сериализация. Например, блок math_number_property
меняет форму, но он делает это на основе раскрывающегося поля, значение которого уже сериализовано. Таким образом, он может просто использовать валидатор поля и не нуждается в мутаторе.
Дополнительную информацию о том, когда вам нужен мутатор, а когда нет, смотрите на странице сериализации .
Мутаторы также предоставляют встроенный пользовательский интерфейс, позволяющий пользователям изменять формы блоков, если вы предоставите некоторые дополнительные методы.
Хуки сериализации
У мутаторов есть две пары хуков сериализации, с которыми они работают. Одна пара хуков работает с новой системой сериализации JSON, а другая пара работает со старой системой сериализации XML. Вам необходимо предоставить хотя бы одну из этих пар.
сохранитьExtraState и загрузитьExtraState
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
. Он используется блоками, которые ссылаются на состояние, сериализованное другим сериализатором (например, резервные модели данных). Параметр сигнализирует, что ссылаемое состояние не будет доступно при десериализации блока, поэтому блок должен сериализовать все резервное состояние самостоятельно. Например, это верно, когда сериализуется отдельный блок или когда блок копируется и вставляется.
Два распространенных варианта использования этого:
- Когда отдельный блок загружается в рабочую область, где не существует базовой модели данных, в его собственном состоянии содержится достаточно информации для создания новой модели данных.
- При копировании и вставке блока всегда создается новая базовая модель данных, а не ссылка на существующую.
Некоторые блоки, которые используют это, — это блоки @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 добавит в ваш блок пользовательский интерфейс «мутатора» по умолчанию.
Вам не обязательно использовать этот UI, если вы хотите добавить дополнительную сериализацию. Вы можете использовать пользовательский UI, например, плагин blocks-plus-minus , или вы можете вообще не использовать 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
, которая работает с пользовательским интерфейсом по умолчанию. Эта функция дает вам возможность связать дочерние элементы вашего основного блока (который существует в основной рабочей области) с подблоками, которые существуют в рабочей области мутатора. Затем вы можете использовать эти данные, чтобы убедиться, что ваша функция 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
: необязательный массив типов блоков (в виде строк), которые будут добавлены во всплывающее окно в пользовательском интерфейсе мутатора по умолчанию, если также определены методы пользовательского интерфейса.
Обратите внимание, что в отличие от расширений, каждый тип блока может иметь только один мутатор.
{
//...
"mutator": "controls_if_mutator"
}
Вспомогательная функция
Наряду с миксином мутатор может регистрировать вспомогательную функцию. Эта функция запускается для каждого блока данного типа после его создания и добавления mixinObj. Она может использоваться для добавления дополнительных триггеров или эффектов к мутации.
Например, вы можете добавить в свой блок-список помощника, который задает начальное количество элементов:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}