Ein Mutator ist ein Mixin, das einem Block zusätzliche Serialisierung (zusätzlicher Zustand, der gespeichert und geladen wird) hinzufügt. Für die integrierten Blöcke controls_if
und list_create_with
ist beispielsweise eine zusätzliche Serialisierung erforderlich, damit gespeichert werden kann, wie viele Eingaben sie haben. Möglicherweise wird auch eine Benutzeroberfläche hinzugefügt, damit der Nutzer die Form des Blocks ändern kann.
Wenn Sie die Form Ihres Blocks ändern, ist nicht unbedingt eine zusätzliche Serialisierung erforderlich. Der math_number_property
-Block ändert beispielsweise seine Form, aber das geschieht auf Grundlage eines Drop-down-Felds, dessen Wert bereits serialisiert wird. Daher kann einfach ein Feldvalidator verwendet werden und es ist kein Mutator erforderlich.
Weitere Informationen dazu, wann Sie einen Mutator benötigen und wann nicht, finden Sie auf der Seite Serialisierung.
Mutatoren bieten auch eine integrierte Benutzeroberfläche, über die Nutzer die Formen von Blöcken ändern können, wenn Sie einige optionale Methoden bereitstellen.
Serialisierungshooks
Mutatoren verwenden zwei Paare von Serialisierungshooks. Ein Hook-Paar funktioniert mit dem neuen JSON-Serialisierungssystem und das andere mit dem alten XML-Serialisierungssystem. Sie müssen mindestens eines dieser Paare angeben.
saveExtraState und loadExtraState
saveExtraState
und loadExtraState
sind Serialisierungshooks, die mit dem neuen JSON-Serialisierungssystem funktionieren. saveExtraState
gibt einen JSON-serialisierbaren Wert zurück, der den zusätzlichen Status des Blocks darstellt. loadExtraState
akzeptiert denselben JSON-serialisierbaren Wert und wendet ihn auf den Block an.
// 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_();
},
Der resultierende JSON-Code sieht so aus:
{
"type": "lists_create_with",
"extraState": {
"itemCount": 3 // or whatever the count is
}
}
Kein Status
Wenn sich Ihr Block beim Serialisieren im Standardzustand befindet, kann Ihre saveExtraState
-Methode null
zurückgeben, um dies anzugeben. Wenn Ihre saveExtraState
-Methode null
zurückgibt, wird dem JSON keine extraState
-Eigenschaft hinzugefügt. Dadurch bleibt die Größe der Speicherdatei gering.
Vollständige Serialisierung und Sicherungsdaten
saveExtraState
empfängt auch einen optionalen doFullSerialization
-Parameter. Dies wird von Blöcken verwendet, die auf den Zustand verweisen, der von einem anderen Serializer (z. B. Sicherungsdatenmodelle) serialisiert wurde. Der Parameter signalisiert, dass der referenzierte Status beim Deserialisieren des Blocks nicht verfügbar ist. Daher sollte der Block den gesamten zugrunde liegenden Status selbst serialisieren. Das ist beispielsweise der Fall, wenn ein einzelner Block serialisiert oder ein Block kopiert und eingefügt wird.
Zwei häufige Anwendungsfälle dafür sind:
- Wenn ein einzelner Block in einen Arbeitsbereich geladen wird, in dem das zugrunde liegende Datenmodell nicht vorhanden ist, enthält er genügend Informationen in seinem eigenen Status, um ein neues Datenmodell zu erstellen.
- Wenn ein Block kopiert und eingefügt wird, wird immer ein neues zugrunde liegendes Datenmodell erstellt, anstatt auf ein vorhandenes zu verweisen.
Einige Blöcke, die dies verwenden, sind die Blöcke @blockly/block-shareable-procedures. Normalerweise serialisieren sie eine Referenz auf ein zugrunde liegendes Datenmodell, in dem ihr Status gespeichert wird.
Wenn der Parameter doFullSerialization
jedoch „true“ ist, wird der gesamte Status serialisiert. Die freigabefähigen Verfahrensblöcke verwenden dies, um sicherzustellen, dass beim Kopieren und Einfügen ein neues zugrunde liegendes Datenmodell erstellt wird, anstatt auf ein vorhandenes Modell zu verweisen.
mutationToDom und domToMutation
mutationToDom
und domToMutation
sind Serialisierungshooks, die mit dem alten XML-Serialisierungssystem funktionieren. Verwenden Sie diese Hooks nur, wenn es unbedingt erforderlich ist (z. B. wenn Sie an einer alten Codebasis arbeiten, die noch nicht migriert wurde). Andernfalls verwenden Sie saveExtraState
und loadExtraState
.
mutationToDom
gibt einen XML-Knoten zurück, der den zusätzlichen Status des Blocks darstellt, und domToMutation
akzeptiert denselben XML-Knoten und wendet den Status auf den Block an.
// 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_();
},
Die resultierende XML sieht so aus:
<block type="lists_create_with">
<mutation items="3"></mutation>
</block>
Wenn Ihre mutationToDom
-Funktion „null“ zurückgibt, wird dem XML kein zusätzliches Element hinzugefügt.
UI-Hooks
Wenn Sie bestimmte Funktionen als Teil Ihres Mutators bereitstellen, fügt Blockly dem Block eine Standard-Mutator-Benutzeroberfläche hinzu.
Sie müssen diese Benutzeroberfläche nicht verwenden, wenn Sie zusätzliche Serialisierung hinzufügen möchten. Sie können eine benutzerdefinierte Benutzeroberfläche verwenden, wie sie beispielsweise vom Plugin „blocks-plus-minus“ bereitgestellt wird, oder Sie können auch ganz auf eine Benutzeroberfläche verzichten.
Zusammenfassen und aufschlüsseln
Die Standard-Benutzeroberfläche basiert auf den Funktionen compose
und decompose
.
decompose
zerlegt den Block in kleinere Unterblöcke, die verschoben, hinzugefügt und gelöscht werden können. Diese Funktion sollte einen „Top-Block“ zurückgeben, der der Hauptblock im Mutator-Arbeitsbereich ist, mit dem Unterblöcke verbunden werden.
compose
interpretiert dann die Konfiguration der Unterblöcke und verwendet sie, um den Hauptblock zu ändern. Diese Funktion sollte den „obersten Block“, der von decompose
zurückgegeben wurde, als Parameter akzeptieren.
Beachten Sie, dass diese Funktionen in den Block „eingemischt“ werden, der „mutiert“ wird. Daher kann this
verwendet werden, um auf diesen Block zu verweisen.
// 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
Optional können Sie auch eine saveConnections
-Funktion definieren, die mit der Standard-Benutzeroberfläche funktioniert. Mit dieser Funktion können Sie untergeordnete Blöcke Ihres Hauptblocks (der im Hauptarbeitsbereich vorhanden ist) mit untergeordneten Blöcken verknüpfen, die in Ihrem Mutator-Arbeitsbereich vorhanden sind. Anhand dieser Daten können Sie dann dafür sorgen, dass die compose
-Funktion die untergeordneten Elemente Ihres Hauptblocks richtig neu verbindet, wenn Ihre untergeordneten Blöcke neu organisiert werden.
saveConnections
sollte den von Ihrer decompose
-Funktion zurückgegebenen „Top-Block“ als Parameter akzeptieren. Wenn die Funktion saveConnections
definiert ist, ruft Blockly sie vor dem Aufrufen von compose
auf.
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();
}
},
Wird registriert
Mutatoren sind nur eine spezielle Art von Mixin und müssen daher registriert werden, bevor Sie sie in der JSON-Definition Ihres Blocktyps verwenden können.
// 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
: Ein String, der dem Mutator zugeordnet wird, damit Sie ihn in JSON verwenden können.mixinObj
: Ein Objekt mit den verschiedenen Mutationsmethoden. Beispiel:saveExtraState
undloadExtraState
.opt_helperFn
: Eine optionale Hilfsfunktion, die für den Block ausgeführt wird, nachdem das Mixin eingebunden wurde.opt_blockList
: Ein optionales Array von Blocktypen (als Strings), die dem Flyout in der Standard-Mutator-Benutzeroberfläche hinzugefügt werden, wenn die UI-Methoden ebenfalls definiert sind.
Im Gegensatz zu Erweiterungen kann jeder Blocktyp nur einen Mutator haben.
{
//...
"mutator": "controls_if_mutator"
}
Hilfsfunktion
Zusammen mit dem Mixin kann ein Mutator eine Hilfsfunktion registrieren. Diese Funktion wird für jeden Block des angegebenen Typs ausgeführt, nachdem er erstellt und mixinObj
hinzugefügt wurde. Damit können einer Mutation zusätzliche Trigger oder Effekte hinzugefügt werden.
Sie können Ihrem listenähnlichen Block beispielsweise einen Helfer hinzufügen, der die anfängliche Anzahl von Elementen festlegt:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}