Como criar um novo tipo de campo

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:

  1. Implementar um construtor.
  2. Registre uma chave JSON e implemente fromJson.
  3. Processar a inicialização da interface e do evento no bloco listeners.
  4. Processar o descarte de listeners de eventos (o descarte de interface é tratado para para você).
  5. Implementar o tratamento de valores.
  6. Adicione uma representação em texto do valor do campo para fins de acessibilidade.
  7. Adicione outras funcionalidades, como:
  8. 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:

  1. 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.
  2. 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.
  3. 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

Fluxograma que descreve a ordem em que os validadores são executados

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_ como newValue.
  • Define o isDirty_. como true.

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.

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.

Imagem do seletor de ângulo

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:

Imagem do editor de entrada de texto

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.

Fluxograma descrevendo como decidir se deve substituir render_

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:

  1. A criação do DOM deve ser realizada durante inicialização por ser mais eficiente.
  2. 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.