Um modificador é um mixin que adiciona uma serialização extra (estado extra que é salvo
e carregado) a um bloco. Por exemplo, os blocos controls_if
e
list_create_with
integrados precisam de serialização extra para salvar quantas
entradas eles têm. Ele também pode adicionar uma interface para que o usuário possa mudar a
forma do bloco.
Mudar a forma do bloco não necessariamente significa que você precisa
de uma serialização extra. Por exemplo, o bloco math_number_property
muda
de forma, mas faz isso com base em um campo de lista suspensa, cujo valor já é
serializado. Assim, ele pode usar um validador
de campo e não
precisa de um modificador.
Consulte a página de serialização para mais informações sobre quando você precisa de um modificador e quando não precisa.
Os modificadores também oferecem uma interface integrada para que os usuários mudem as formas dos blocos se você fornecer alguns métodos opcionais.
Hooks de serialização
Os modificadores trabalham com dois pares de ganchos de serialização. Um par de hooks funciona com o novo sistema de serialização JSON, e o outro par funciona com o antigo sistema de serialização XML. É necessário fornecer pelo menos um desses pares.
saveExtraState e loadExtraState
saveExtraState
e loadExtraState
são ganchos de serialização que funcionam com o
novo sistema de serialização JSON. saveExtraState
retorna um valor serializável
em JSON que representa o estado extra do bloco. loadExtraState
aceita esse mesmo valor serializável em JSON e o aplica ao bloco.
// 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_();
},
O JSON resultante vai ficar assim:
{
"type": "lists_create_with",
"extraState": {
"itemCount": 3 // or whatever the count is
}
}
Sem estado
Se o bloco estiver no estado padrão quando for serializado, o método
saveExtraState
poderá retornar null
para indicar isso. Se o método
saveExtraState
retornar null
, nenhuma propriedade extraState
será adicionada ao
JSON. Isso mantém o tamanho do arquivo salvo pequeno.
Serialização completa e dados de backup
saveExtraState
também recebe um parâmetro doFullSerialization
opcional. Ele
é usado por blocos que referenciam o estado serializado por um
serializador diferente (como modelos de dados de suporte). O parâmetro indica que
o estado referenciado não estará disponível quando o bloco for desserializado. Portanto, o
bloco precisa serializar todo o estado de suporte. Por exemplo, isso é
verdade quando um bloco individual é serializado ou quando um bloco é copiado e colado.
Dois casos de uso comuns para isso são:
- Quando um bloco individual é carregado em um espaço de trabalho em que o modelo de dados de suporte não existe, ele tem informações suficientes no próprio estado para criar um novo modelo de dados.
- Quando um bloco é copiado e colado, ele sempre cria um novo modelo de dados de suporte em vez de referenciar um existente.
Alguns blocos que usam isso são os blocos
@blockly/block-shareable-procedures. Normalmente,
eles serializam uma referência a um modelo de dados de suporte, que armazena o estado deles.
No entanto, se o parâmetro doFullSerialization
for verdadeiro, ele vai serializar todo
o estado. Os blocos de procedimento compartilháveis usam isso para garantir que, quando
forem copiados e colados, eles criem um novo modelo de dados de suporte, em vez de referenciar um
modelo existente.
mutationToDom e domToMutation
mutationToDom
e domToMutation
são ganchos de serialização que funcionam com o
antigo sistema de serialização XML. Use esses hooks apenas quando necessário (por exemplo, se você
estiver trabalhando em uma base de código antiga que ainda não foi migrada). Caso contrário, use
saveExtraState
e loadExtraState
.
mutationToDom
retorna um nó XML que representa o estado extra do
bloco, e domToMutation
aceita esse mesmo nó XML e aplica o estado ao
bloco.
// 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_();
},
O XML resultante vai ficar assim:
<block type="lists_create_with">
<mutation items="3"></mutation>
</block>
Se a função mutationToDom
retornar nulo, nenhum elemento extra será
adicionado ao XML.
Ganchos da interface
Se você fornecer determinadas funções como parte do mutator, o Blockly vai adicionar uma interface de "mutator" padrão ao bloco.
Não é necessário usar essa interface se você quiser adicionar mais serialização. Você pode usar uma interface personalizada, como a fornecida pelo plug-in blocks-plus-minus, ou não usar nenhuma interface.
compor e decompor
A interface padrão depende das funções compose
e decompose
.
decompose
"explode" o bloco em subblocos menores que podem ser movidos, adicionados e excluídos. Essa função precisa retornar um "bloco superior", que é
o bloco principal no espaço de trabalho do modificador ao qual os subblocos se conectam.
O compose
interpreta a configuração dos subblocos e os usa para
modificar o bloco principal. Essa função precisa aceitar o "bloco superior", que foi
retornado por decompose
como um parâmetro.
Essas funções são "misturadas" ao bloco que está sendo "mutado" para que this
possa ser usado para se referir a esse bloco.
// 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
Também é possível definir uma função saveConnections
que funcione com
a interface padrão. Essa função permite associar filhos do
bloco principal (que existe no espaço de trabalho principal) a subblocos que existem no
espaço de trabalho do modificador. Em seguida, use esses dados para garantir que a função compose
reconecte corretamente os filhos do bloco principal quando os
subblocos forem reorganizados.
saveConnections
precisa aceitar o "bloco superior" retornado pela função decompose
como parâmetro. Se a função saveConnections
for definida, o Blockly
a chamará antes de chamar 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();
}
},
Registrando
Os modificadores são apenas um tipo especial de mixin. Portanto, eles também precisam ser registrados antes de serem usados na definição JSON do tipo de bloco.
// 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
: uma string para associar ao modificador para que possa ser usada em JSON.mixinObj
: um objeto que contém os vários métodos de mutação. Por exemplo,saveExtraState
eloadExtraState
.opt_helperFn
: uma função auxiliar opcional que será executada no bloco depois que o mixin for misturado.opt_blockList
: uma matriz opcional de tipos de bloco (como strings) que será adicionada ao menu suspenso na interface do modificador padrão, se os métodos da interface também forem definidos.
Ao contrário das extensões, cada tipo de bloco pode ter apenas um modificador.
{
//...
"mutator": "controls_if_mutator"
}
Função auxiliar
Além do mixin, um mutador pode registrar uma função auxiliar. Essa função é executada em cada bloco do tipo especificado depois que ele é criado e o mixinObj é adicionado. Ele pode ser usado para adicionar gatilhos ou efeitos adicionais a uma mutação.
Por exemplo, é possível adicionar um auxiliar ao bloco semelhante a uma lista que define o número inicial de itens:
var helper = function() {
this.itemCount_ = 5;
this.updateShape();
}