Un mutador es un mixin que agrega serialización adicional (estado adicional que se guarda y carga) a un bloque. Por ejemplo, los bloques controls_if
y list_create_with
integrados necesitan serialización adicional para poder guardar cuántas entradas tienen. También puede agregar una IU para que el usuario pueda cambiar la forma del bloque.
Ten en cuenta que cambiar la forma de tu bloque no significa necesariamente que necesites serialización adicional. Por ejemplo, el bloque math_number_property
cambia de forma, pero lo hace en función de un campo de menú desplegable, cuyo valor ya se serializa. Por lo tanto, puede usar un validador de campos y no necesita un mutador.
Consulta la página de serialización para obtener más información sobre cuándo necesitas un mutador y cuándo no.
Los mutadores también proporcionan una IU integrada para que los usuarios cambien las formas de los bloques si proporcionas algunos métodos opcionales.
Hooks de serialización
Los mutadores tienen dos pares de hooks de serialización con los que trabajan. Un par de hooks funciona con el nuevo sistema de serialización JSON, y el otro par funciona con el sistema de serialización XML anterior. Debes proporcionar al menos uno de estos pares.
saveExtraState y loadExtraState
saveExtraState
y loadExtraState
son hooks de serialización que funcionan con el nuevo sistema de serialización de JSON. saveExtraState
devuelve un valor serializable en JSON que representa el estado adicional del bloque, y loadExtraState
acepta ese mismo valor serializable en JSON y lo aplica al bloque.
// 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_();
},
El JSON resultante se verá de la siguiente manera:
{
"type": "lists_create_with",
"extraState": {
"itemCount": 3 // or whatever the count is
}
}
Sin estado
Si el bloque está en su estado predeterminado cuando se serializa, tu método saveExtraState
puede devolver null
para indicarlo. Si tu método saveExtraState
devuelve null
, no se agrega ninguna propiedad extraState
al JSON. Esto mantiene el tamaño del archivo de guardado pequeño.
Datos de respaldo y serialización completos
saveExtraState
también recibe un parámetro doFullSerialization
opcional. Los bloques que hacen referencia a un estado serializado por un serializador diferente (como los modelos de datos de respaldo) usan este campo. El parámetro indica que el estado al que se hace referencia no estará disponible cuando se deserialice el bloque, por lo que el bloque debe serializar todo el estado de respaldo por sí mismo. Por ejemplo, esto sucede cuando se serializa un bloque individual o cuando se copia y pega un bloque.
Estos son dos casos de uso comunes:
- Cuando un bloque individual se carga en un espacio de trabajo en el que no existe el modelo de datos de respaldo, tiene suficiente información en su propio estado para crear un nuevo modelo de datos.
- Cuando se corta y pega un bloque, siempre se crea un nuevo modelo de datos de respaldo en lugar de hacer referencia a uno existente.
Algunos bloques que usan esto son los bloques de @blockly/block-shareable-procedures. Normalmente, serializan una referencia a un modelo de datos de respaldo, que almacena su estado.
Sin embargo, si el parámetro doFullSerialization
es verdadero, serializan todo su estado. Los bloques de procedimientos compartibles usan esto para asegurarse de que, cuando se copian y pegan, creen un nuevo modelo de datos de respaldo, en lugar de hacer referencia a un modelo existente.
mutationToDom y domToMutation
mutationToDom
y domToMutation
son hooks de serialización que funcionan con el sistema de serialización XML anterior. Solo usa estos hooks si es necesario (p. ej., si trabajas en una base de código antigua que aún no se migró). De lo contrario, usa saveExtraState
y loadExtraState
.
mutationToDom
devuelve un nodo XML que representa el estado adicional del bloque, y domToMutation
acepta ese mismo nodo XML y aplica el estado al bloque.
// 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_();
},
El XML resultante se verá de la siguiente manera:
<block type="lists_create_with">
<mutation items="3"></mutation>
</block>
Si tu función mutationToDom
devuelve nulo, no se agregará ningún elemento adicional al XML.
UI Hooks
Si proporcionas ciertas funciones como parte de tu mutador, Blockly agregará una IU de "mutador" predeterminada a tu bloque.
No es necesario que uses esta IU si deseas agregar serialización adicional. Puedes usar una IU personalizada, como la que proporciona el complemento blocks-plus-minus, o no usar ninguna IU.
componer y descomponer
La IU predeterminada se basa en las funciones compose
y decompose
.
decompose
"explota" el bloque en subbloques más pequeños que se pueden mover, agregar y borrar. Esta función debe devolver un "bloque superior", que es el bloque principal en el espacio de trabajo del mutador al que se conectan los sub-bloques.
Luego, compose
interpreta la configuración de los subbloques y los usa para modificar el bloque principal. Esta función debe aceptar el "bloque superior" que devolvió decompose
como parámetro.
Ten en cuenta que estas funciones se "mezclan" en el bloque que se "muta", por lo que se puede usar this
para hacer referencia a ese bloque.
// 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
De manera opcional, también puedes definir una función saveConnections
que funcione con la IU predeterminada. Esta función te permite asociar los bloques secundarios de tu bloque principal (que existe en el espacio de trabajo principal) con los bloques secundarios que existen en tu espacio de trabajo del mutador. Luego, puedes usar estos datos para asegurarte de que tu función compose
vuelva a conectar correctamente los elementos secundarios de tu bloque principal cuando se reorganizan tus subbloques.
saveConnections
debería aceptar el "bloque superior" que muestra tu función decompose
como parámetro. Si se define la función saveConnections
, Blockly la llamará antes de llamar a 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();
}
},
Registrándose
Los mutadores son solo un tipo especial de mixin, por lo que también deben registrarse antes de que puedas usarlos en la definición JSON de tu tipo de bloque.
// 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
: Es una cadena para asociar con el mutador, de modo que puedas usarlo en JSON.mixinObj
: Es un objeto que contiene los distintos métodos de mutación. p. ej.,saveExtraState
yloadExtraState
opt_helperFn
: Una función de ayuda opcional que se ejecutará en el bloque después de que se incluya el mixin.opt_blockList
: Es un array opcional de tipos de bloques (como cadenas) que se agregarán al menú desplegable en la IU del mutador predeterminado, si también se definen los métodos de la IU.
Ten en cuenta que, a diferencia de las extensiones, cada tipo de bloque solo puede tener un mutador.
{
//...
"mutator": "controls_if_mutator"
}
Función auxiliar
Junto con el mixin, un mutador puede registrar una función auxiliar. Esta función se ejecuta en cada bloque del tipo determinado después de que se crea y se agrega el mixinObj
. Se puede usar para agregar activadores o efectos adicionales a una mutación.
Por ejemplo, podrías agregar un asistente a tu bloque similar a una lista que establezca la cantidad inicial de elementos:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}