Les extensions sont des fonctions qui s'exécutent sur chaque bloc d'un type donné à mesure que le bloc est créé. Celles-ci ajoutent souvent une configuration ou un comportement personnalisés à un bloc.
Un mutateur est un type particulier d'extension qui ajoute une sérialisation personnalisée, et parfois une UI, à un bloc.
Extensions
Les extensions sont des fonctions qui s'exécutent sur chaque bloc d'un type donné à mesure que le bloc est créé. Ils peuvent ajouter une configuration personnalisée (par exemple, l'info-bulle du bloc) ou un comportement personnalisé (par exemple, ajouter un écouteur d'événements au bloc).
// This extension sets the block's tooltip to be a function which displays
// the parent block's tooltip (if it exists).
Blockly.Extensions.register(
'parent_tooltip_extension',
function() { // this refers to the block that the extension is being run on
var thisBlock = this;
this.setTooltip(function() {
var parent = thisBlock.getParent();
return (parent && parent.getInputsInline() && parent.tooltip) ||
Blockly.Msg.MATH_NUMBER_TOOLTIP;
});
});
Les extensions doivent être "enregistrées" pour pouvoir être associées à une clé de chaîne. Vous pouvez ensuite attribuer cette clé de chaîne à la propriété extensions
de la définition JSON de votre type de bloc pour appliquer l'extension au bloc.
{
//...,
"extensions": ["parent_tooltip_extension",]
}
Vous pouvez également ajouter plusieurs extensions à la fois. Notez que la propriété extensions
doit être un tableau, même si vous n'appliquez qu'une seule extension.
{
//...,
"extensions": ["parent_tooltip_extension", "break_warning_extension"],
}
Mix
Blockly fournit également une méthode pratique pour les cas où vous souhaitez ajouter des propriétés/fonctions d'aide à un bloc, mais pas les exécuter immédiatement. Cela vous permet d'enregistrer un objet mixin contenant l'ensemble de vos propriétés/méthodes supplémentaires. L'objet mixin est ensuite encapsulé dans une fonction qui applique le mixin chaque fois qu'une instance du type de bloc donné est créée.
Blockly.Extensions.registerMixin('my_mixin', {
someProperty: 'a cool value',
someMethod: function() {
// Do something cool!
}
))`
Les clés de chaîne associées à des mixins peuvent être référencées en JSON comme n'importe quelle autre extension.
{
//...,
"extensions": ["my_mixin"],
}
Mutateurs
Un mutateur est un type particulier d'extension qui ajoute une sérialisation supplémentaire (un é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.
Notez que la modification de la forme de votre bloc ne signifie pas nécessairement que vous aurez besoin d'une sérialisation supplémentaire. Par exemple, le bloc math_number_property
change de forme, mais il le fait en fonction d'un champ déroulant, dont la valeur est déjà sérialisée. Par conséquent, il peut simplement utiliser un valideur de champ et n'a pas besoin d'un mutateur.
Consultez la page de sérialisation pour savoir dans quels cas vous avez besoin d'un mutateur ou non.
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 utilisent 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 JSON sérialisable qui représente l'état supplémentaire du bloc. 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 ainsi 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 réduire la taille de votre fichier d'enregistrement.
Sérialisation complète et sauvegarde des données
saveExtraState
reçoit également un paramètre doFullSerialization
facultatif. Elle est utilisée par les blocs qui font référence à un état sérialisé par un autre sérialiseur (comme des modèles de données de sauvegarde). Le paramètre indique que l'état référencé ne sera pas disponible lorsque le bloc est désérialisé. Le bloc doit donc sérialiser l'ensemble de l'état de sauvegarde lui-même. C'est le cas lorsqu'un bloc individuel est sérialisé ou lorsqu'un bloc est copié-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 de sauvegarde 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é-collé, il crée toujours un modèle de données de sauvegarde au lieu de référencer un modèle existant.
Certains blocs qui l'utilisent sont les blocs @blockly/block-shareable-procedures. Normalement, ils sérialisent une référence à un modèle de données de sauvegarde, qui stocke leur état.
Toutefois, si le paramètre doFullSerialization
est "true", ils sérialisent tous leurs états. Les blocs de procédure partageable 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 de sauvegarde 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 le devez (par exemple, si vous travaillez sur un ancien codebase qui n'a pas encore été migré), 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 une valeur nulle, aucun élément supplémentaire ne sera ajouté au code XML.
Crochets d'interface utilisateur
Si vous fournissez certaines fonctions dans votre mutateur, Blockly ajoute une UI de "mutateur" par défaut à votre bloc.
Vous n'avez pas besoin d'utiliser cette interface utilisateur si vous souhaitez ajouter une sérialisation supplémentaire. Vous pouvez utiliser une interface utilisateur personnalisée, comme le fournit le plug-in blocks-plus-moins, ou n'utiliser aucune interface utilisateur.
composer et décomposer
L'interface utilisateur par défaut repose sur les fonctions compose
et decompose
.
decompose
"explose" 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" qui a été renvoyé par decompose
en tant que paramètre.
Notez que ces fonctions sont "mélangées" au bloc en cours de "mutation". this
peut donc ê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'interface utilisateur par défaut. Cette fonction vous permet d'associer les enfants de votre bloc principal (qui existe dans l'espace de travail principal) à des sous-blocs qui existent dans votre espace de travail de mutation. 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
en tant que paramètre. Si la fonction saveConnections
est définie, Blockly l'appellera avant 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 particulier d'extension. Ils doivent donc également être enregistrés avant que vous puissiez les utiliser 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 afin de pouvoir l'utiliser en 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 une fois le mixin mélangé.opt_blockList
: tableau facultatif de types de blocs (sous forme de chaînes) qui sera ajouté 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 blocage ne peut avoir 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 du mixinObj. Elle permet d'ajouter des déclencheurs ou des effets supplémentaires à une mutation.
Par exemple, vous pouvez ajouter à votre bloc de type liste un assistant qui définit le nombre initial d'éléments:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}