Un mutatore è un mixin che aggiunge una serializzazione aggiuntiva (uno stato aggiuntivo che viene salvato
e caricato) a un blocco. Ad esempio, i blocchi controls_if
e
list_create_with
integrati richiedono una serializzazione aggiuntiva per poter salvare il numero di input. Potrebbe anche aggiungere un'interfaccia utente in modo che l'utente possa modificare la forma del blocco.
Tieni presente che la modifica della forma del blocco non significa necessariamente che devi
aggiungere una serializzazione. Ad esempio, il blocco math_number_property
cambia
forma, ma lo fa in base a un campo a discesa, il cui valore viene già
serializzato. Pertanto, può utilizzare solo un validator
di campo e non
ha bisogno di un mutatore.
Per ulteriori informazioni su quando è necessario un mutatore e quando non lo è, consulta la pagina sulla serializzazione.
I mutatori forniscono anche un'interfaccia utente integrata per consentire agli utenti di modificare le forme dei blocchi se fornisci alcuni metodi facoltativi.
Hook di serializzazione
I mutatori hanno due coppie di hook di serializzazione con cui lavorano. Una coppia di hook funziona con il nuovo sistema di serializzazione JSON, mentre l'altra coppia funziona con il vecchio sistema di serializzazione XML. Devi fornire almeno una di queste coppie.
saveExtraState e loadExtraState
saveExtraState
e loadExtraState
sono hook di serializzazione che funzionano con il
nuovo sistema di serializzazione JSON. saveExtraState
restituisce un valore serializzabile JSON
che rappresenta lo stato aggiuntivo del blocco, mentre loadExtraState
accetta lo stesso valore serializzabile JSON e lo applica al blocco.
// 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_();
},
Il JSON risultante sarà simile a questo:
{
"type": "lists_create_with",
"extraState": {
"itemCount": 3 // or whatever the count is
}
}
Nessuno stato
Se il blocco è nel suo stato predefinito quando viene serializzato, il metodo
saveExtraState
può restituire null
per indicarlo. Se il tuo metodo
saveExtraState
restituisce null
, non viene aggiunta alcuna proprietà extraState
al
JSON. In questo modo, le dimensioni del file di salvataggio rimangono ridotte.
Serializzazione completa e dati di backup
saveExtraState
riceve anche un parametro doFullSerialization
facoltativo. Questo
viene utilizzato dai blocchi che fanno riferimento allo stato serializzato da un serializzatore diverso (come i modelli di dati di supporto). Il parametro indica che
lo stato a cui viene fatto riferimento non sarà disponibile quando il blocco viene deserializzato, quindi il
blocco deve serializzare tutto lo stato di backing. Ad esempio, questo è
vero quando un blocco individuale viene serializzato o quando un blocco viene copiato e incollato.
Due casi d'uso comuni sono:
- Quando un singolo blocco viene caricato in uno spazio di lavoro in cui non esiste il modello di dati sottostante, contiene informazioni sufficienti nel proprio stato per creare un nuovo modello di dati.
- Quando un blocco viene copiato e incollato, viene sempre creato un nuovo modello di dati di supporto anziché fare riferimento a uno esistente.
Alcuni blocchi che lo utilizzano sono i blocchi
@blockly/block-shareable-procedures. Normalmente
serializzano un riferimento a un modello di dati di supporto, che memorizza il loro stato.
Tuttavia, se il parametro doFullSerialization
è true, serializzano tutto il loro stato. I blocchi di procedure condivisibili lo utilizzano per assicurarsi che, quando vengono copiati e incollati, creino un nuovo modello di dati di supporto anziché fare riferimento a un modello esistente.
mutationToDom e domToMutation
mutationToDom
e domToMutation
sono hook di serializzazione che funzionano con il vecchio sistema di serializzazione XML. Utilizza questi hook solo se necessario (ad es. se
stai lavorando su una base di codice precedente che non è ancora stata migrata), altrimenti utilizza
saveExtraState
e loadExtraState
.
mutationToDom
restituisce un nodo XML che rappresenta lo stato aggiuntivo del
blocco, mentre domToMutation
accetta lo stesso nodo XML e applica lo stato al
blocco.
// 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_();
},
L'XML risultante avrà il seguente aspetto:
<block type="lists_create_with">
<mutation items="3"></mutation>
</block>
Se la funzione mutationToDom
restituisce null, non verrà aggiunto alcun elemento
extra all'XML.
UI Hooks
Se fornisci determinate funzioni come parte del mutatore, Blockly aggiungerà un'interfaccia utente "mutatore" predefinita al blocco.
Non devi utilizzare questa UI se vuoi aggiungere una serializzazione aggiuntiva. Puoi utilizzare un'interfaccia utente personalizzata, come quella fornita dal plug-in blocks-plus-minus, oppure non utilizzare alcuna interfaccia utente.
comporre e scomporre
La UI predefinita si basa sulle funzioni compose
e decompose
.
decompose
"esplode" il blocco in sottoblocchi più piccoli che possono essere spostati, aggiunti ed eliminati. Questa funzione deve restituire un "blocco superiore", ovvero
il blocco principale nello spazio di lavoro del mutatore a cui si collegano i blocchi secondari.
compose
interpreta quindi la configurazione dei sottoblocchi e li utilizza per modificare il blocco principale. Questa funzione deve accettare come parametro il "blocco superiore" restituito da decompose
.
Tieni presente che queste funzioni vengono "mixate" nel blocco che viene "modificato", quindi this
può essere utilizzato per fare riferimento a quel blocco.
// 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
Se vuoi, puoi anche definire una funzione saveConnections
che funzioni con
la UI predefinita. Questa funzione ti offre la possibilità di associare i blocchi secondari del blocco principale (che si trova nello spazio di lavoro principale) ai blocchi secondari che si trovano nello spazio di lavoro del mutatore. Puoi quindi utilizzare questi dati per assicurarti che la funzione compose
ricolleghi correttamente i figli del blocco principale quando i
blocchi secondari vengono riorganizzati.
saveConnections
deve accettare come parametro il "blocco superiore" restituito dalla funzione decompose
. Se la funzione saveConnections
è definita, Blockly
la chiamerà prima di chiamare 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();
}
},
In fase di registrazione
I mutatori sono solo un tipo speciale di mixin, quindi devono essere registrati prima di poter essere utilizzati nella definizione JSON del tipo di blocco.
// 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
: una stringa da associare al mutatore in modo da poterlo utilizzare in JSON.mixinObj
: un oggetto contenente i vari metodi di mutazione. Ad es.saveExtraState
eloadExtraState
.opt_helperFn
: una funzione helper facoltativa che verrà eseguita sul blocco dopo l'inclusione del mixin.opt_blockList
: Un array facoltativo di tipi di blocchi (come stringhe) che verranno aggiunti al riquadro a comparsa nell'interfaccia utente del mutatore predefinita, se sono definiti anche i metodi dell'interfaccia utente.
Tieni presente che, a differenza delle estensioni, ogni tipo di blocco può avere un solo mutatore.
{
//...
"mutator": "controls_if_mutator"
}
Funzione helper
Oltre al mixin, un mutatore può registrare una funzione helper. Questa funzione viene
eseguita su ogni blocco del tipo specificato dopo la creazione e l'aggiunta di mixinObj
. Può essere utilizzato per aggiungere trigger o effetti aggiuntivi a una mutazione.
Ad esempio, potresti aggiungere un helper al blocco di tipo elenco che imposta il numero iniziale di elementi:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}