Мутатор — это миксин, добавляющий блоку дополнительную сериализацию (дополнительное состояние, которое сохраняется и загружается). Например, встроенным блокам 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 не будет добавлен никакой дополнительный элемент.
UI-хуки
Если вы предоставите определенные функции как часть своего мутатора, 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
, которая работает с пользовательским интерфейсом по умолчанию. Эта функция позволяет связать дочерние элементы основного блока (который находится в основной рабочей области) с подблоками, которые находятся в рабочей области мутатора. Затем вы можете использовать эти данные, чтобы убедиться, что функция 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();
}