Antes de criar um novo tipo de campo, considere se um dos outros métodos para personalizar campos de acordo com suas necessidades. Caso seu aplicativo precise armazenar novo tipo de valor ou se quiser criar uma nova interface para um tipo de valor existente, provavelmente precisará criar um novo tipo de campo.
Para criar um novo campo, faça o seguinte:
- Implementar um construtor.
- Registre uma chave JSON e implemente
fromJson
. - Processar a inicialização da interface e do evento no bloco listeners.
- Processar o descarte de listeners de eventos (o descarte de interface é tratado para para você).
- Implementar o tratamento de valores.
- Adicione uma representação em texto do valor do campo para fins de acessibilidade.
- Adicione outras funcionalidades, como:
- Configure outros aspectos do campo, como:
Esta seção pressupõe que você leu e está familiarizado com o conteúdo do Anatomia de um Campo.
Para obter um exemplo de um campo personalizado, consulte a página Campos personalizados demonstração ,
Implementação de um construtor
O construtor do campo é responsável por configurar o valor inicial dele e, opcionalmente, configurar uma configuração validador. A página construtor do campo é chamado durante a inicialização do bloco de origem, independentemente de o bloco de origem estar definido em JSON ou JavaScript. Então, o tamanho não tem acesso ao bloco de origem durante a construção.
O exemplo de código a seguir cria um campo personalizado chamado GenericField
:
class GenericField extends Blockly.Field {
constructor(value, validator) {
super(value, validator);
this.SERIALIZABLE = true;
}
}
Assinatura do método
Os construtores de campo geralmente recebem um valor e um validador local. O valor é
opcional, e se você não passar um valor (ou se passar um valor que falhe na classe
validação), o valor padrão da superclasse será usado. Para o
classe Field
padrão, esse valor será null
. Se você não quiser que o padrão
, transmita um valor adequado. O parâmetro de validação é apenas
presente nos campos editáveis e normalmente é marcada como opcional. Saiba mais
sobre validadores na página Validadores
docs.
Estrutura
A lógica dentro do construtor precisa seguir este fluxo:
- Chamar o superconstrutor herdado (todos os campos personalizados devem herdar de
Blockly.Field
ou uma das subclasses) para inicializar corretamente o valor e configure o validador local do campo. - Se seu campo for serializável, defina a propriedade correspondente no construtor. Os campos editáveis precisam ser serializáveis, e os campos editáveis por padrão, então você deve definir essa propriedade como verdadeira, a menos que saiba ele não deve ser serializável.
- Opcional: aplique outras personalizações (por exemplo, Campos de rótulo) permite que uma classe CSS seja transmitida, que é aplicada ao texto).
JSON e registro
No bloco JSON
definições,
campos são descritos por uma string (por exemplo, field_number
, field_textinput
).
O Blockly mantém um mapa dessas strings para objetos de campo e chama
fromJson
no objeto apropriado durante a construção.
Chame Blockly.fieldRegistry.register
para adicionar seu tipo de campo a este mapa.
transmitindo a classe do campo como o segundo argumento:
Blockly.fieldRegistry.register('field_generic', GenericField);
Também é necessário definir a função fromJson
. Sua implementação precisa
primeiro, desfaça referência a qualquer string
tabela
referências usando
replaceMessageReferences,
e, em seguida, passar os valores para o construtor.
GenericField.fromJson = function(options) {
const value = Blockly.utils.parsing.replaceMessageReferences(
options['value']);
return new CustomFields.GenericField(value);
};
Inicializando
Quando o campo é construído, ele basicamente contém apenas um valor. A inicialização é onde o DOM e o modelo são criados (se o campo possui um modelo) e os eventos são vinculados.
Tela no bloco
Durante a inicialização, você é responsável por criar tudo de que precisa para a exibição do campo no bloco.
Padrões, plano de fundo e texto
A função initView
padrão cria um elemento rect
de cor clara e um
Elemento text
. Se você quiser que o campo tenha ambos, além de alguns recursos
guloseimas, chame a função initView
da superclasse antes de adicionar o restante dos
Elementos DOM. Se você quiser que seu campo tenha um, mas não ambos, estes
elementos, você pode usar as funções createBorderRect_
ou createTextElement_
.
Personalizar a construção do DOM
Se o campo for um campo de texto genérico (por exemplo, Texto
entrada),
A construção do DOM será feita para você. Caso contrário, você precisará substituir
a função initView
para criar os elementos DOM necessários durante
a renderização futura do seu campo.
Por exemplo, um campo suspenso pode conter imagens e texto. Em initView
cria um único elemento de imagem e um único elemento de texto. Depois, durante render_
ela mostra o elemento ativo e oculta o outro, com base no tipo do
opção selecionada.
A criação de elementos DOM pode ser feita usando o
Blockly.utils.dom.createSvgElement
ou usando a criação tradicional do DOM
métodos.
Os requisitos da exibição no bloco de um campo são:
- Todos os elementos do DOM precisam ser filhos do
fieldGroup_
do campo. O campo grupo é criado automaticamente. - Todos os elementos DOM devem ficar dentro das dimensões informadas do campo.
Consulte a Renderização para mais detalhes sobre como personalizar e atualizar a tela no bloco.
Como adicionar símbolos de texto
Se quiser adicionar símbolos ao texto de um campo (como o
Ângulo
grau do campo), você pode acrescentar o elemento de símbolo (normalmente contido em um
<tspan>
) diretamente ao textElement_
do campo.
Eventos de entrada
Por padrão, os campos registram eventos de dica e eventos de mousedown (para serem usados para
mostrando
editores).
Se você quiser detectar outros tipos de eventos (por exemplo, se você deseja processar
arrastando um campo), modifique a função bindEvents_
do campo.
bindEvents_() {
// Call the superclass function to preserve the default behavior as well.
super.bindEvents_();
// Then register your own additional event listeners.
this.mouseDownWrapper_ =
Blockly.browserEvents.conditionalBind(this.getClickTarget_(), 'mousedown', this,
function(event) {
this.originalMouseX_ = event.clientX;
this.isMouseDown_ = true;
this.originalValue_ = this.getValue();
event.stopPropagation();
}
);
this.mouseMoveWrapper_ =
Blockly.browserEvents.conditionalBind(document, 'mousemove', this,
function(event) {
if (!this.isMouseDown_) {
return;
}
var delta = event.clientX - this.originalMouseX_;
this.setValue(this.originalValue_ + delta);
}
);
this.mouseUpWrapper_ =
Blockly.browserEvents.conditionalBind(document, 'mouseup', this,
function(_event) {
this.isMouseDown_ = false;
}
);
}
Para se vincular a um evento, em geral, use o método
Blockly.utils.browserEvents.conditionalBind
função. Esse método de vinculação de eventos filtra toques secundários durante
se arrasta. Se você quiser que o gerenciador seja executado mesmo durante uma ação de arrastar em andamento
use o
Blockly.browserEvents.bind
função.
Descarte
Se você registrou listeners de eventos personalizados dentro do bindEvents_
do campo
elas precisarão ser canceladas na função dispose
.
Se você inicializar corretamente
visualização
do campo (anexando todos os elementos DOM ao fieldGroup_
), o
do DOM desse campo será descartado automaticamente.
Tratamento de valor
→ Para informações sobre o valor de um campo em relação ao texto dele, consulte Anatomia de um .
Ordem de validação
Como implementar um validador de classes
Os campos devem aceitar apenas determinados valores. Por exemplo, os campos numéricos só devem aceitam números, os campos de cor devem aceitar apenas cores etc. Isso é garantido via classe e locais validadores. A classe validador segue as mesmas regras dos validadores locais, exceto por ser executado no construtor Por isso, ele não deve fazer referência ao bloco de origem.
Para implementar o validador de classes do campo, substitua doClassValidation_
função.
doClassValidation_(newValue) {
if (typeof newValue != 'string') {
return null;
}
return newValue;
};
Como processar valores válidos
Se o valor passado para um campo com setValue
for válido, você receberá uma
doValueUpdate_
. Por padrão, a função doValueUpdate_
:
- Define a propriedade
value_
comonewValue
. - Define o
isDirty_
. comotrue
.
Se você só precisa armazenar o valor e não quer fazer nenhum tratamento personalizado,
não é necessário substituir doValueUpdate_
.
Caso contrário, se você quiser fazer coisas como:
- Armazenamento personalizado de
newValue
. - Mude outras propriedades com base em
newValue
. - Salve se o valor atual é válido ou não.
Será necessário substituir doValueUpdate_
:
doValueUpdate_(newValue) {
super.doValueUpdate_(newValue);
this.displayValue_ = newValue;
this.isValueValid_ = true;
}
Como tratar valores inválidos
Se o valor passado ao campo com setValue
for inválido, você receberá uma
doValueInvalid_
. Por padrão, a função doValueInvalid_
faz
nada. Isso significa que, por padrão, os valores inválidos não serão mostrados. Ela também
significa que o campo não será renderizado novamente, porque o
isDirty_
não será definida.
Se você quiser mostrar valores inválidos, modifique doValueInvalid_
.
Na maioria das circunstâncias, você precisa definir uma propriedade displayValue_
como o
valor inválido, definido
isDirty_
para true
e substituir
render_
para que a tela no bloco seja atualizada com base no displayValue_
, e não no
value_
.
doValueInvalid_(newValue) {
this.displayValue_ = newValue;
this.isDirty_ = true;
this.isValueValid_ = false;
}
Valores de várias partes
Quando seu campo contém um valor de várias partes (por exemplo, listas, vetores, objetos), você pode querer que as partes sejam tratadas como valores individuais.
doClassValidation_(newValue) {
if (FieldTurtle.PATTERNS.indexOf(newValue.pattern) == -1) {
newValue.pattern = null;
}
if (FieldTurtle.HATS.indexOf(newValue.hat) == -1) {
newValue.hat = null;
}
if (FieldTurtle.NAMES.indexOf(newValue.turtleName) == -1) {
newValue.turtleName = null;
}
if (!newValue.pattern || !newValue.hat || !newValue.turtleName) {
this.cachedValidatedValue_ = newValue;
return null;
}
return newValue;
}
No exemplo acima, cada propriedade de newValue
é validada individualmente. Depois,
ao final da função doClassValidation_
, se alguma propriedade individual for
inválido, o valor é armazenado em cache na propriedade cacheValidatedValue_
antes
retornando null
(inválido). Armazenar o objeto em cache com validações individuais
permite que as
doValueInvalid_
para processá-las separadamente, simplesmente fazendo uma
!this.cacheValidatedValue_.property
em vez de revalidar cada
a propriedade individualmente.
Este padrão de validação de valores de várias partes também pode ser usado em validadores, mas não há como aplicar esse padrão.
isDirty_
isDirty_
é uma sinalização usada
setValue
bem como outras partes do campo, para dizer se ele precisa ser
renderizado novamente. Se o valor de exibição do campo tiver mudado, isDirty_
normalmente
ser definido como true
.
Texto
→ Para informações sobre onde o texto de um campo é usado e como ele é diferente do valor do campo, consulte Anatomia de um .
Se o texto de seu campo for diferente do valor de seu campo, você deve
substituir o
Função getText
para fornecer o texto correto.
getText() {
let text = this.value_.turtleName + ' wearing a ' + this.value_.hat;
if (this.value_.hat == 'Stovepipe' || this.value_.hat == 'Propeller') {
text += ' hat';
}
return text;
}
Como criar um editor
Se você definir a função showEditor_
, o Blockly detectará automaticamente os
cliques e ligar para showEditor_
no momento apropriado. Você pode exibir qualquer HTML
em seu editor, envolvendo-o em um dos dois divs especiais, chamados DropDownDiv,
e o WidgetDiv, que flutuam sobre o resto da interface do Blockly.
DropDownDiv e WidgetDiv
O DropDownDiv
é usado para fornecer editores que estão em uma caixa conectada
a um campo. Ele se posiciona automaticamente para ficar perto do campo enquanto permanece
dentro dos limites visíveis. O seletor de ângulo e o seletor de cores são bons exemplos de
o DropDownDiv
.
O WidgetDiv
é usado para
que não ficam dentro de uma caixa. Os campos numéricos usam o
WidgetDiv para cobrir o campo com uma caixa de entrada de texto HTML. Enquanto o DropDownDiv
o posicionamento para você, o WidgetDiv não. Os elementos deverão ser
posicionado manualmente. O sistema de coordenadas está em coordenadas de pixel em relação a
no canto superior esquerdo da janela. O editor de entrada de texto é um bom exemplo
WidgetDiv
:
Exemplo de código DropDownDiv
showEditor_() {
// Create the widget HTML
this.editor_ = this.dropdownCreate_();
Blockly.DropDownDiv.getContentDiv().appendChild(this.editor_);
// Set the dropdown's background colour.
// This can be used to make it match the colour of the field.
Blockly.DropDownDiv.setColour('white', 'silver');
// Show it next to the field. Always pass a dispose function.
Blockly.DropDownDiv.showPositionedByField(
this, this.disposeWidget_.bind(this));
}
Exemplo de código do WidgetDiv
showEditor_() {
// Show the div. This automatically closes the dropdown if it is open.
// Always pass a dispose function.
Blockly.WidgetDiv.show(
this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
// Create the widget HTML.
var widget = this.createWidget_();
Blockly.WidgetDiv.getDiv().appendChild(widget);
}
Limpar
Tanto o DropDownDiv quanto o WidgetDiv destroem o HTML do widget mas você precisa descartar manualmente todos os listeners de eventos que tiver aplicadas a esses elementos.
widgetDispose_() {
for (let i = this.editorListeners_.length, listener;
listener = this.editorListeners_[i]; i--) {
Blockly.browserEvents.unbind(listener);
this.editorListeners_.pop();
}
}
A função dispose
é chamada em um contexto null
no DropDownDiv
. Ativado
o WidgetDiv
que é chamado no contexto da WidgetDiv
. Em ambos os casos
é melhor usar
vínculo
ao transmitir uma função de descarte, conforme mostrado no DropDownDiv
acima
e WidgetDiv
.
→ Para mais informações sobre o descarte, não especificamente sobre o descarte de editores, consulte Descarte.
Atualizando a tela no bloco
A função render_
é usada para atualizar a tela no bloco do campo de acordo com
seu valor interno.
São exemplos comuns:
- Alterar o texto (menu suspenso)
- Mudar a cor
Padrões
A função render_
padrão define o texto de exibição como o resultado do
getDisplayText_
função. A função getDisplayText_
retorna a propriedade value_
do campo
transmitir para uma string, depois de ter sido truncada para respeitar o máximo de texto
comprimento.
Se você estiver usando a tela padrão no bloco e o comportamento de texto padrão
funcione para seu campo, não será necessário substituir render_
.
Se o comportamento de texto padrão funcionar para seu campo, mas ele estiver bloqueado
display tiver outros elementos estáticos, será possível chamar o método render_
padrão
, mas você ainda precisará substituí-la para atualizar a função
tamanho.
Se o comportamento de texto padrão não funcionar para seu campo ou
a tela no bloco tem elementos dinâmicos adicionais, você precisará personalizar
render_
função.
Como personalizar a renderização
Caso o comportamento de renderização padrão não funcione para seu campo, será preciso definem o comportamento de renderização personalizada. Isso pode envolver desde a criação de configurações exibir texto, alterar elementos da imagem e atualizar as cores de fundo.
Todas as alterações de atributos do DOM são legais. Lembre-se apenas de duas coisas:
- A criação do DOM deve ser realizada durante inicialização por ser mais eficiente.
- Sempre atualize o
size_
. para corresponder ao tamanho da tela no bloco.
render_() {
switch(this.value_.hat) {
case 'Stovepipe':
this.stovepipe_.style.display = '';
break;
case 'Crown':
this.crown_.style.display = '';
break;
case 'Mask':
this.mask_.style.display = '';
break;
case 'Propeller':
this.propeller_.style.display = '';
break;
case 'Fedora':
this.fedora_.style.display = '';
break;
}
switch(this.value_.pattern) {
case 'Dots':
this.shellPattern_.setAttribute('fill', 'url(#polkadots)');
break;
case 'Stripes':
this.shellPattern_.setAttribute('fill', 'url(#stripes)');
break;
case 'Hexagons':
this.shellPattern_.setAttribute('fill', 'url(#hexagons)');
break;
}
this.textContent_.nodeValue = this.value_.turtleName;
this.updateSize_();
}
Atualizando tamanho
Atualizar a propriedade size_
de um campo é muito importante, porque informa ao
bloquear o código de renderização sobre como posicionar o campo. A melhor maneira de descobrir
exatamente o que esse size_
precisa ser com o experimento.
updateSize_() {
const bbox = this.movableGroup_.getBBox();
let width = bbox.width;
let height = bbox.height;
if (this.borderRect_) {
width += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
height += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.borderRect_.setAttribute('width', width);
this.borderRect_.setAttribute('height', height);
}
// Note how both the width and the height can be dynamic.
this.size_.width = width;
this.size_.height = height;
}
Cores de bloco correspondentes
Se você quer que os elementos de seu campo combinem com as cores do bloco, eles são
anexado, modifique o método applyColour
. Você vai querer
acessar a cor por meio da propriedade de estilo do bloco.
applyColour() {
const sourceBlock = this.sourceBlock_;
if (sourceBlock.isShadow()) {
this.arrow_.style.fill = sourceBlock.style.colourSecondary;
} else {
this.arrow_.style.fill = sourceBlock.style.colourPrimary;
}
}
Atualizando a capacidade de edição
A função updateEditable
pode ser usada para mudar a aparência do campo
dependendo se ele é editável ou não. A função padrão faz com que
o plano de fundo tem ou não uma resposta ao passar o cursor (borda) se for ou não editável.
A tela no bloco não deve mudar de tamanho dependendo da capacidade de edição, mas
todas as outras alterações serão permitidas.
updateEditable() {
if (!this.fieldGroup_) {
// Not initialized yet.
return;
}
super.updateEditable();
const group = this.getClickTarget_();
if (!this.isCurrentlyEditable()) {
group.style.cursor = 'not-allowed';
} else {
group.style.cursor = this.CURSOR;
}
}
Serialização
A serialização consiste em salvar estado de seu campo para que ele possa ser recarregado no espaço de trabalho mais tarde.
O estado do seu espaço de trabalho sempre inclui o valor do campo, mas também pode incluir outro estado, como o estado da interface do seu campo. Por exemplo, se sua era um mapa com zoom que permitia ao usuário selecionar países, você poderia também serializará o nível de zoom.
Se o campo for serializável, defina a propriedade SERIALIZABLE
como
true
.
O Blockly fornece dois conjuntos de ganchos de serialização para campos. Um par de ganchos funciona com o novo sistema de serialização JSON, e o outro par funciona com a antigo sistema de serialização XML.
saveState
e loadState
saveState
e loadState
são hooks de serialização que funcionam com o novo JSON
em um sistema de serialização.
Em alguns casos, você não precisa fornecê-las, porque o padrão
vão funcionar. Se (1) seu campo for uma subclasse direta da base
Blockly.Field
, (2) seu valor for uma classe JSON serializável
externo e (3) você só precisa
serializar o valor, a implementação padrão funcionará sem problemas.
Caso contrário, a função saveState
retornará um JSON serializável
objeto/valor que representa o estado do campo. E seu loadState
deve aceitar o mesmo objeto/valor serializável JSON e aplicá-lo à
campo.
saveState() {
return {
'country': this.getValue(), // Value state
'zoom': this.getZoomLevel(), // UI state
};
}
loadState(state) {
this.setValue(state['country']);
this.setZoomLevel(state['zoom']);
}
Serialização completa e dados de backup
saveState
também recebe um parâmetro opcional doFullSerialization
. Isso é
usada por campos que normalmente fazem referência ao estado serializado por uma
serializer (como modelos de dados de apoio). O parâmetro indica que
o estado referenciado não estará disponível quando o bloco for desserializado, então o
deve executar toda a serialização. Por exemplo, isso ocorre 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 os dados de apoio não existir, o campo terá informações suficientes em seu próprio estado para criar um novo modelo de dados.
- Quando um bloco é copiado e colado, o campo sempre cria um novo apoio modelo de dados em vez de referenciar um que já existe.
Um campo que usa isso é o campo de variável incorporada. Normalmente ele serializa
o ID da variável que está sendo referenciada, mas se doFullSerialization
for verdadeiro
ele serializa todo o estado.
saveState(doFullSerialization) {
const state = {'id': this.variable_.getId()};
if (doFullSerialization) {
state['name'] = this.variable_.name;
state['type'] = this.variable_.type;
}
return state;
}
loadState(state) {
const variable = Blockly.Variables.getOrCreateVariablePackage(
this.getSourceBlock().workspace,
state['id'],
state['name'], // May not exist.
state['type']); // May not exist.
this.setValue(variable.getId());
}
O campo de variável faz isso para garantir que, se for carregado em um espaço de trabalho onde sua variável não existe, ele pode criar uma nova variável para referenciar.
toXml
e fromXml
toXml
e fromXml
são hooks de serialização que funcionam com o XML antigo
em um sistema de serialização. Use esses ganchos apenas se precisar (por exemplo, se estiver trabalhando
em uma base de código antiga que ainda não foi migrada), use saveState
e
loadState
.
A função toXml
precisa retornar um nó XML que representa o estado do
campo. E a função fromXml
precisa aceitar o mesmo nó XML e aplicar
para o campo.
toXml(fieldElement) {
fieldElement.textContent = this.getValue();
fieldElement.setAttribute('zoom', this.getZoomLevel());
return fieldElement;
}
fromXml(fieldElement) {
this.setValue(fieldElement.textContent);
this.setZoomLevel(fieldElement.getAttribute('zoom'));
}
Propriedades editáveis e serializáveis
A propriedade EDITABLE
determina se o campo precisa ter a interface para indicar que
com os quais é possível interagir. O padrão é true
.
A propriedade SERIALIZABLE
determina se o campo precisa ser serializado. Ela
o padrão é false
. Se a propriedade for true
, será necessário fornecer
funções de serialização e desserialização (consulte
Serialização).
Como personalizar o cursor
A propriedade CURSOR
determina o cursor que os usuários veem quando passam o cursor sobre
em seu campo. Precisa ser uma string de cursor CSS válida. O padrão é o cursor
definido por .blocklyDraggable
, que é o cursor de captura.