Un mutateur est un mixin qui ajoute une sérialisation supplémentaire (état supplémentaire enregistré et chargé) à un bloc. Par exemple, les blocs intégrés controls_if
et list_create_with
nécessitent une sérialisation supplémentaire pour pouvoir enregistrer le nombre d'entrées dont ils disposent. Il peut également ajouter une UI pour que l'utilisateur puisse modifier la forme du bloc.
Notez que le fait de modifier la forme de votre bloc ne signifie pas nécessairement que vous avez besoin d'une sérialisation supplémentaire. Par exemple, la forme du bloc math_number_property
change, mais elle le fait en fonction d'un champ de menu déroulant dont la valeur est déjà sérialisée. Il peut donc simplement utiliser un validateur de champ et n'a pas besoin de mutateur.
Pour savoir quand vous avez besoin d'un mutateur et quand vous n'en avez pas besoin, consultez la page sur la sérialisation.
Les mutateurs fournissent également une UI intégrée permettant aux utilisateurs de modifier la forme des blocs si vous fournissez des méthodes facultatives.
Hooks de sérialisation
Les mutateurs fonctionnent avec deux paires de hooks de sérialisation. Une paire de hooks fonctionne avec le nouveau système de sérialisation JSON, et l'autre paire fonctionne avec l'ancien système de sérialisation XML. Vous devez fournir au moins l'une de ces paires.
saveExtraState et loadExtraState
saveExtraState
et loadExtraState
sont des hooks de sérialisation qui fonctionnent avec le nouveau système de sérialisation JSON. saveExtraState
renvoie une valeur sérialisable JSON qui représente l'état supplémentaire du bloc, et loadExtraState
accepte cette même valeur sérialisable JSON et l'applique au bloc.
// 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_();
},
Le fichier JSON obtenu se présentera comme suit :
{
"type": "lists_create_with",
"extraState": {
"itemCount": 3 // or whatever the count is
}
}
Aucun état
Si votre bloc est dans son état par défaut lorsqu'il est sérialisé, votre méthode saveExtraState
peut renvoyer null
pour l'indiquer. Si votre méthode saveExtraState
renvoie null
, aucune propriété extraState
n'est ajoutée au fichier JSON. Cela permet de limiter la taille de votre fichier de sauvegarde.
Sérialisation complète et données de sauvegarde
saveExtraState
reçoit également un paramètre doFullSerialization
facultatif. Cette méthode est utilisée par les blocs qui font référence à un état sérialisé par un sérialiseur différent (comme les modèles de données de sauvegarde). Le paramètre indique que l'état référencé ne sera pas disponible lorsque le bloc sera désérialisé. Le bloc doit donc sérialiser lui-même l'intégralité de l'état de sauvegarde. Par exemple, cela est vrai lorsqu'un bloc individuel est sérialisé ou lorsqu'un bloc est copié et collé.
Voici deux cas d'utilisation courants :
- Lorsqu'un bloc individuel est chargé dans un espace de travail où le modèle de données sous-jacent n'existe pas, il dispose de suffisamment d'informations dans son propre état pour créer un modèle de données.
- Lorsqu'un bloc est copié et collé, il crée toujours un nouveau modèle de données sous-jacent au lieu de faire référence à un modèle existant.
Les blocs @blockly/block-shareable-procedures en sont un exemple. Normalement, ils sérialisent une référence à un modèle de données sous-jacent qui stocke leur état.
Toutefois, si le paramètre doFullSerialization
est défini sur "true", ils sérialisent l'intégralité de leur état. Les blocs de procédure partageables utilisent cette méthode pour s'assurer que, lorsqu'ils sont copiés et collés, ils créent un nouveau modèle de données sous-jacent au lieu de faire référence à un modèle existant.
mutationToDom et domToMutation
mutationToDom
et domToMutation
sont des hooks de sérialisation qui fonctionnent avec l'ancien système de sérialisation XML. N'utilisez ces hooks que si vous y êtes obligé (par exemple, si vous travaillez sur une ancienne base de code qui n'a pas encore été migrée). Sinon, utilisez saveExtraState
et loadExtraState
.
mutationToDom
renvoie un nœud XML qui représente l'état supplémentaire du bloc, et domToMutation
accepte ce même nœud XML et applique l'état au bloc.
// 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_();
},
Le code XML obtenu se présente comme suit :
<block type="lists_create_with">
<mutation items="3"></mutation>
</block>
Si votre fonction mutationToDom
renvoie la valeur "null", aucun élément supplémentaire ne sera ajouté au fichier XML.
Hooks d'UI
Si vous fournissez certaines fonctions dans votre mutateur, Blockly ajoutera une UI de "mutateur" par défaut à votre bloc.
Vous n'êtes pas obligé d'utiliser cette UI si vous souhaitez ajouter une sérialisation supplémentaire. Vous pouvez utiliser une UI personnalisée, comme celle fournie par le plug-in blocks-plus-minus, ou n'utiliser aucune UI.
composer et décomposer
L'UI par défaut s'appuie sur les fonctions compose
et decompose
.
decompose
"éclate" le bloc en sous-blocs plus petits qui peuvent être déplacés, ajoutés et supprimés. Cette fonction doit renvoyer un "bloc supérieur", qui est le bloc principal de l'espace de travail du mutateur auquel les sous-blocs se connectent.
compose
interprète ensuite la configuration des sous-blocs et les utilise pour modifier le bloc principal. Cette fonction doit accepter le "bloc supérieur" renvoyé par decompose
comme paramètre.
Notez que ces fonctions sont "mélangées" au bloc en cours de "mutation", de sorte que this
peut être utilisé pour faire référence à ce bloc.
// 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
Vous pouvez également définir une fonction saveConnections
qui fonctionne avec l'UI par défaut. Cette fonction vous permet d'associer les enfants de votre bloc principal (qui existe dans l'espace de travail principal) aux sous-blocs qui existent dans votre espace de travail de mutateur. Vous pouvez ensuite utiliser ces données pour vous assurer que votre fonction compose
reconnecte correctement les enfants de votre bloc principal lorsque vos sous-blocs sont réorganisés.
saveConnections
doit accepter le "bloc supérieur" renvoyé par votre fonction decompose
comme paramètre. Si la fonction saveConnections
est définie, Blockly l'appellera avant d'appeler 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();
}
},
Enregistrement…
Les mutateurs ne sont qu'un type spécial de mixin. Ils doivent donc également être enregistrés avant de pouvoir être utilisés dans la définition JSON de votre type de bloc.
// 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
: chaîne à associer au mutateur pour pouvoir l'utiliser dans JSON.mixinObj
: objet contenant les différentes méthodes de mutation. Par exemple,saveExtraState
etloadExtraState
.opt_helperFn
: fonction d'assistance facultative qui s'exécutera sur le bloc après l'inclusion du mixin.opt_blockList
: tableau facultatif de types de blocs (sous forme de chaînes) qui seront ajoutés au menu déroulant dans l'UI du mutateur par défaut, si les méthodes d'UI sont également définies.
Notez que, contrairement aux extensions, chaque type de bloc ne peut comporter qu'un seul mutateur.
{
//...
"mutator": "controls_if_mutator"
}
Fonction d'assistance
En plus du mixin, un mutateur peut enregistrer une fonction d'assistance. Cette fonction est exécutée sur chaque bloc du type donné après sa création et l'ajout de mixinObj
. Il peut être utilisé pour ajouter des déclencheurs ou des effets supplémentaires à une mutation.
Par exemple, vous pouvez ajouter un helper à votre bloc de type liste qui définit le nombre initial d'éléments :
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}