Mutator to mixin, który dodaje do bloku dodatkową serializację (dodatkowy stan, który jest zapisywany i wczytywany). Na przykład wbudowane bloki controls_if
i list_create_with
wymagają dodatkowej serializacji, aby można było zapisać liczbę wejść. Może też dodać interfejs, aby użytkownik mógł zmienić kształt bloku.
Pamiętaj, że zmiana kształtu bloku nie musi oznaczać, że potrzebujesz dodatkowej serializacji. Na przykład blok math_number_property
zmienia kształt, ale robi to na podstawie pola menu, którego wartość jest już serializowana. W związku z tym może używać tylko walidatora pola i nie potrzebuje mutatora.
Więcej informacji o tym, kiedy potrzebujesz mutatora, a kiedy nie, znajdziesz na stronie serializacji.
Mutatory udostępniają też wbudowany interfejs, który umożliwia użytkownikom zmianę kształtów bloków, jeśli udostępnisz niektóre metody opcjonalne.
Ciekawostki dotyczące serializacji
Mutatory mają 2 pary punktów zaczepienia serializacji, z którymi współpracują. Jedna para hooków działa z nowym systemem serializacji JSON, a druga z systemem serializacji XML. Musisz podać co najmniej 1 parę.
saveExtraState i loadExtraState
saveExtraState
i loadExtraState
to punkty zaczepienia serializacji, które działają z nowym systemem serializacji JSON. saveExtraState
zwraca wartość, którą można serializować do formatu JSON, reprezentującą dodatkowy stan bloku, a loadExtraState
akceptuje tę samą wartość, którą można serializować do formatu JSON, i stosuje ją do bloku.
// 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_();
},
Wynikowy plik JSON będzie wyglądać tak:
{
"type": "lists_create_with",
"extraState": {
"itemCount": 3 // or whatever the count is
}
}
Brak stanu
Jeśli blok jest w stanie domyślnym podczas serializacji, metoda saveExtraState
może zwrócić wartość null
, aby to wskazać. Jeśli metoda saveExtraState
zwraca wartość null
, do JSON nie jest dodawana żadna właściwość extraState
. Dzięki temu rozmiar pliku zapisu pozostanie mały.
Pełna serializacja i tworzenie kopii zapasowych danych
saveExtraState
otrzymuje też opcjonalny parametr doFullSerialization
. Jest on używany przez bloki, które odwołują się do stanu serializowanego przez inny serializator (np. modele danych zaplecza). Parametr sygnalizuje, że przywoływany stan nie będzie dostępny podczas deserializacji bloku, więc blok powinien sam serializować cały stan zapasowy. Na przykład dzieje się tak, gdy poszczególne bloki są serializowane lub gdy blok jest kopiowany i wklejany.
Oto 2 typowe przypadki użycia:
- Gdy pojedynczy blok jest wczytywany do obszaru roboczego, w którym nie ma modelu danych źródłowych, ma on wystarczającą ilość informacji w swoim stanie, aby utworzyć nowy model danych.
- Gdy blok jest kopiowany i wklejany, zawsze tworzy nowy model danych zamiast odwoływać się do istniejącego.
Niektóre bloki, które z niej korzystają, to bloki @blockly/block-shareable-procedures. Zwykle serializują one odwołanie do modelu danych zaplecza, który przechowuje ich stan.
Jeśli jednak parametr doFullSerialization
ma wartość true, serializują cały swój stan. Bloki procedur, które można udostępniać, używają tego, aby mieć pewność, że po skopiowaniu i wklejeniu utworzą nowy model danych, zamiast odwoływać się do istniejącego modelu.
mutationToDom i domToMutation
mutationToDom
i domToMutation
to punkty zaczepienia serializacji, które działają ze starym systemem serializacji XML. Używaj tych funkcji tylko w razie potrzeby (np.jeśli pracujesz nad starszą bazą kodu, która nie została jeszcze zmigrowana). W innych przypadkach używaj funkcji saveExtraState
i loadExtraState
.
mutationToDom
zwraca węzeł XML, który reprezentuje dodatkowy stan bloku, a domToMutation
akceptuje ten sam węzeł XML i stosuje stan do bloku.
// 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_();
},
Wynikowy plik XML będzie wyglądać tak:
<block type="lists_create_with">
<mutation items="3"></mutation>
</block>
Jeśli funkcja mutationToDom
zwróci wartość null, do pliku XML nie zostanie dodany żaden dodatkowy element.
Punkty zaczepienia interfejsu
Jeśli w ramach mutatora udostępnisz określone funkcje, Blockly doda do bloku domyślny interfejs mutatora.
Jeśli chcesz dodać dodatkową serializację, nie musisz korzystać z tego interfejsu. Możesz użyć niestandardowego interfejsu, takiego jak ten, który zapewnia wtyczka blocks-plus-minus, lub nie używać interfejsu wcale.
składać i rozkładać,
Domyślny interfejs użytkownika korzysta z funkcji compose
i decompose
.
decompose
„rozbija” blok na mniejsze podbloki, które można przenosić, dodawać i usuwać. Ta funkcja powinna zwracać „główny blok”, czyli główny blok w obszarze roboczym mutatora, z którym łączą się podbloki.
compose
interpretuje konfigurację bloków podrzędnych i używa ich do modyfikowania bloku głównego. Ta funkcja powinna przyjmować jako parametr „górny blok” zwrócony przez decompose
.
Pamiętaj, że te funkcje są „mieszane” z blokiem, który jest „mutowany”, więc this
może odnosić się do tego bloku.
// 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
Opcjonalnie możesz też zdefiniować funkcję saveConnections
, która działa z domyślnym interfejsem. Ta funkcja umożliwia powiązanie elementów podrzędnych głównego bloku (który znajduje się w głównym obszarze roboczym) z blokami podrzędnymi znajdującymi się w obszarze roboczym mutatora. Możesz następnie użyć tych danych, aby upewnić się, że funkcja compose
prawidłowo ponownie łączy elementy podrzędne głównego bloku, gdy bloki podrzędne zostaną przeorganizowane.
saveConnections
powinna akceptować jako parametr „górny blok” zwracany przez funkcję decompose
. Jeśli funkcja saveConnections
jest zdefiniowana, Blockly wywoła ją przed wywołaniem funkcji 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();
}
},
Rejestruję…
Mutatory to tylko specjalny rodzaj mixinu, więc przed użyciem ich w definicji JSON typu bloku musisz je zarejestrować.
// 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
: ciąg znaków do powiązania z mutatorem, aby można było go używać w JSON.mixinObj
: obiekt zawierający różne metody mutacji. Np.saveExtraState
iloadExtraState
.opt_helperFn
: opcjonalna funkcja pomocnicza, która będzie uruchamiana w bloku po zmiksowaniu mixinu.opt_blockList
: opcjonalna tablica typów bloków (w postaci ciągów znaków), które zostaną dodane do wysuwanego menu w domyślnym interfejsie mutatora, jeśli zdefiniowane są też metody interfejsu.
Pamiętaj, że w przeciwieństwie do rozszerzeń każdy typ bloku może mieć tylko 1 mutator.
{
//...
"mutator": "controls_if_mutator"
}
Funkcja pomocnicza
Oprócz klasy mixin mutator może zarejestrować funkcję pomocniczą. Ta funkcja jest uruchamiana w przypadku każdego bloku danego typu po jego utworzeniu i dodaniu elementu mixinObj
. Może służyć do dodawania dodatkowych wyzwalaczy lub efektów do mutacji.
Możesz na przykład dodać do bloku przypominającego listę funkcję pomocniczą, która ustawia początkową liczbę elementów:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}