Мутаторы

Мутатор — это миксин, добавляющий блоку дополнительную сериализацию (дополнительное состояние, которое сохраняется и загружается). Например, встроенным блокам controls_if и list_create_with требуется дополнительная сериализация, чтобы сохранять количество имеющихся входных данных. Он также может добавлять пользовательский интерфейс, позволяющий пользователю изменять форму блока.

Три мутации блока создания списка: без входов, с тремя входами и с пятью входами.

Две мутации блока if/do: if-do и if-do-else-if-do-else.

Обратите внимание, что изменение формы блока не обязательно означает необходимость дополнительной сериализации. Например, блок math_number_property меняет форму, но делает это на основе раскрывающегося поля, значение которого уже сериализовано. Таким образом, он может использовать только валидатор поля и не нуждается в мутаторе.

Блок `math_number_property` с раскрывающимся списком, в котором установлено значение `even`. Он имеет одно поле ввода.Блок `math_number_property` с раскрывающимся списком, в котором установлено значение `divisible on`. Он имеет два входа для значений.

Дополнительную информацию о том, когда вам нужен мутатор, а когда нет, смотрите на странице сериализации .

Мутаторы также предоставляют встроенный пользовательский интерфейс, позволяющий пользователям изменять формы блоков, если вы предоставите некоторые дополнительные методы.

Хуки сериализации

Мутаторы работают с двумя парами хуков сериализации. Одна пара хуков работает с новой системой сериализации 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 добавит в ваш блок пользовательский интерфейс «мутатора» по умолчанию.

Блок if-do с открытым мутаторным пузырём. Это позволяет пользователям добавлять операторы else-if и else в блок if-do.

Вам не обязательно использовать этот интерфейс, если вы хотите добавить дополнительную сериализацию. Вы можете использовать собственный интерфейс, например, плагин «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();
}