Extensões são funções executadas em cada bloco de um determinado tipo à medida que o bloco é criado. Eles geralmente adicionam configuração ou comportamento personalizado a um bloco.
Um mutador é um tipo especial de extensão que adiciona serialização personalizada e, às vezes, interface do usuário a um bloco.
Extensões
Extensões são funções executadas em cada bloco de um determinado tipo à medida que o bloco é criado. Eles podem adicionar uma configuração personalizada (por exemplo, definir a dica do bloco) ou comportamento personalizado (por exemplo, adicionar um listener de eventos ao bloco).
// 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;
});
});
As extensões precisam ser "registradas" para que possam ser associadas a uma chave
de string. Em seguida, é possível atribuir essa chave de string à propriedade extensions
da
definição JSON do
tipo de bloco para aplicar a
extensão ao bloco.
{
//...,
"extensions": ["parent_tooltip_extension",]
}
Você também pode adicionar várias extensões de uma só vez. Observe que a propriedade extensions
precisa ser uma matriz, mesmo que você esteja aplicando apenas uma extensão.
{
//...,
"extensions": ["parent_tooltip_extension", "break_warning_extension"],
}
Mixes
O Blockly também oferece um método de conveniência para situações em que você quer adicionar algumas propriedades/funções auxiliares a um bloco, mas não executá-las imediatamente. Isso funciona, permitindo que você registre um objeto mixin que contém todas as propriedades/métodos extras. O objeto mixin é então envolvido em uma função que aplica o mixin sempre que uma instância do tipo de bloco especificado é criada.
Blockly.Extensions.registerMixin('my_mixin', {
someProperty: 'a cool value',
someMethod: function() {
// Do something cool!
}
))`
As chaves de string associadas a mixins podem ser referenciadas em JSON, assim como qualquer outra extensão.
{
//...,
"extensions": ["my_mixin"],
}
Mutadores
Um mutador é um tipo especial de extensão que adiciona serialização extra (estado extra
que é salvo e carregado) a um bloco. Por exemplo, os blocos integrados controls_if
e list_create_with
precisam de serialização extra para que possam salvar quantas entradas eles têm.
Mudar a forma do bloco não necessariamente significa que você vai precisar de
serialização extra. Por exemplo, o bloco math_number_property
muda
de forma, mas faz isso com base em um campo de menu suspenso, cujo valor já é
serializado. Dessa forma, ele pode usar apenas um validador de campo e não precisa de um mutador.
Consulte a página de serialização para mais informações sobre quando e quando não precisa de um mutador.
Os mutadores também fornecem uma interface integrada para que os usuários mudem as formas dos blocos, caso você forneça alguns métodos opcionais.
Ganchos de serialização
Os mutadores 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 funciona com o antigo sistema de serialização XML. É necessário informar pelo menos um desses pares.
saveExtraState e loadExtraState
saveExtraState
e loadExtraState
são hooks de serialização que funcionam com o novo sistema de serialização JSON. saveExtraState
retorna um valor serializável
JSON que representa o estado extra do bloco, e loadExtraState
aceita esse mesmo valor serializável 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 será semelhante a este:
{
"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 reduz o tamanho do arquivo.
Serialização completa e dados de backup
saveExtraState
também recebe um parâmetro doFullSerialization
opcional. Isso
é usado por blocos que fazem referência ao estado serializado por um
serializador diferente (como modelos de dados de apoio). O parâmetro sinaliza que
o estado referenciado não estará disponível quando o bloco for desserializado, portanto, o
bloco precisa serializar todo o próprio estado de apoio. Por exemplo, isso é
verdadeiro 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 apoio 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 apoio 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 apoio, que armazena o estado delas.
No entanto, se o parâmetro doFullSerialization
for verdadeiro, eles serializarão todo o
estado. Os blocos de procedimentos compartilháveis usam isso para garantir que, quando
forem copiados e colados, criem um novo modelo de dados de apoio, em vez de referenciar um
modelo existente.
mutateToDom e domToMutation
mutationToDom
e domToMutation
são hooks de serialização que funcionam com o
sistema de serialização XML antigo. Use esses hooks apenas se for 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 será semelhante a este:
<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 de interface
Se você fornecer determinadas funções como parte do seu mutador, o Blockly adicionará uma interface de "mutador" padrão ao seu bloco.
Não é necessário usar essa interface se você quiser adicionar mais serialização. Você pode usar uma interface personalizada, como o plug-in blocks-plus-menos (link em inglês), ou você pode 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 mutador ao qual os sub-blocos se conectam.
Em seguida, compose
interpreta a configuração dos sub-blocos 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.
Observe que essas funções são "combinadas" com o bloco que está sendo modificado 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
Se quiser, também é possível definir uma função saveConnections
que funcione com a interface padrão. Essa função oferece a chance de associar filhos do
bloco principal (que existe no espaço de trabalho principal) com sub-blocos que existem no
espaço de trabalho do mutador. Você pode usar esses dados para garantir que a função compose
reconecte corretamente os filhos do bloco principal quando os
sub-blocos forem reorganizados.
saveConnections
precisa aceitar o "top block" retornado pela função decompose
como um parâmetro. Se a função saveConnections
estiver definida, o Blockly
a chamará antes de 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 mutadores são apenas um tipo especial de extensão. Portanto, eles também precisam ser registrados antes que você possa usá-los 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 a ser associada ao mutador para que você possa usá-lo 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 mutador padrão, se os métodos da interface também forem definidos.
Ao contrário das extensões, cada tipo de bloqueio só pode ter um mutador.
{
//...
"mutator": "controls_if_mutator"
}
Função auxiliar
Junto com o 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. Pode ser usado para adicionar outros gatilhos ou efeitos a uma mutação.
Por exemplo, você pode 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();
}