Crea un nuevo tipo de campo

Antes de crear un nuevo tipo de campo, analiza si existe otro métodos para personalizar los campos según tus necesidades. Si tu aplicación necesita almacenar un nuevo tipo de valor o deseas crear una nueva IU para un tipo de valor existente, un nuevo tipo de campo.

Para crear un campo nuevo, haz lo siguiente:

  1. Implementa un constructor.
  2. Registra una clave JSON y, luego, implementa fromJson.
  3. Controla la inicialización de la IU y el evento en bloque objetos de escucha.
  4. Controlar la eliminación de los objetos de escucha de eventos (la eliminación de la IU se controla para ti).
  5. Implementa el manejo del valor.
  6. Agrega una representación de texto del valor de tu campo para fines de accesibilidad.
  7. Agrega funciones adicionales, como las siguientes:
  8. Configura aspectos adicionales de tu campo, como los siguientes:

En esta sección, se da por sentado que leíste y conoces el contenido de Anatomía de un Campo.

Para ver un ejemplo de un campo personalizado, consulta la sección Campos personalizados demostración de Google Cloud.

Cómo implementar un constructor

El constructor del campo es responsable de configurar el valor inicial del campo y, opcionalmente, la configuración de una configuración local validador. En la tabla se llama al constructor del campo durante la inicialización del bloque de origen, independientemente de si el bloque de origen se define en JSON o JavaScript. Por lo tanto, el flujo de trabajo no tiene acceso al bloque de origen durante la construcción.

En la siguiente muestra de código, se crea un campo personalizado llamado GenericField:

class GenericField extends Blockly.Field {
  constructor(value, validator) {
    super(value, validator);

    this.SERIALIZABLE = true;
  }
}

Firma del método

Los constructores de campos suelen recibir un valor y un validador local. El valor es opcional, y si no pasas un valor (o pasas un valor que falla la clase validación), se usará el valor predeterminado de la superclase. Para el la clase Field predeterminada, ese valor es null. Si no quieres el modo y asegúrate de pasar un valor adecuado. El parámetro del validador solo es Está presente en los campos que se pueden editar y suele marcarse como opcional. Más información sobre los validadores en la pestaña Validadores documentación.

Estructura

La lógica dentro de tu constructor debería seguir este flujo:

  1. Llama al superconstructor heredado (todos los campos personalizados deben heredar de Blockly.Field o una de sus subclases) para inicializar de manera correcta el valor. y configura el validador local para tu campo.
  2. Si tu campo se puede serializar, configura la propiedad correspondiente en la . Los campos editables deben ser serializables, y los campos se pueden editar de forma predeterminada, por lo que probablemente deberías establecer esta propiedad en true, a menos que sepas no debería poder serializarse.
  3. Opcional: Aplica personalización adicional (por ejemplo, Campos de etiquetas) permitir que se pase una clase CSS, que luego se aplica al texto).

JSON y registro

En el bloque de JSON definiciones, los campos se describen mediante una cadena (p.ej., field_number, field_textinput). Blockly mantiene un mapa de estas cadenas hasta objetos de campo y llama fromJson en el objeto correspondiente durante la construcción.

Llama a Blockly.fieldRegistry.register para agregar tu tipo de campo a este mapa. y pasar la clase de campo como segundo argumento:

Blockly.fieldRegistry.register('field_generic', GenericField);

También debes definir la función fromJson. Tu implementación debe primero desmenciona cualquier cadena tabla referencias con replaceMessageReferences, y, luego, pasa los valores al constructor.

GenericField.fromJson = function(options) {
  const value = Blockly.utils.parsing.replaceMessageReferences(
      options['value']);
  return new CustomFields.GenericField(value);
};

Inicializando

Cuando se construye tu campo, básicamente contiene un solo valor. La inicialización es donde se crea el DOM, se crea el modelo (si el campo posee un modelo) y los eventos están vinculados.

Pantalla en bloque

Durante la inicialización, eres responsable de crear todo lo que necesites. para la visualización en bloque del campo.

Valores predeterminados, fondo y texto

La función initView predeterminada crea un elemento rect de color claro y una elemento text. Si quieres que tu campo tenga estas dos opciones adicionales, llama a la función initView de la superclase antes de agregar el resto de tus elementos del DOM. Si quieres que tu campo tenga uno, pero no ambos, elementos, puedes usar las funciones createBorderRect_ o createTextElement_.

Cómo personalizar la construcción del DOM

Si el campo es un campo de texto genérico (p.ej., Texto Entrada) La construcción del DOM se encargará por ti. De lo contrario, deberás anular la función initView para crear los elementos del DOM que necesitarás durante la renderización futura de tu campo.

Por ejemplo, un campo desplegable puede contener imágenes y texto. En initView crea un solo elemento de imagen y uno de texto. Luego, durante render_ muestra el elemento activo y oculta el otro según el tipo de elemento la opción seleccionada.

Para crear elementos DOM, es posible usar el Blockly.utils.dom.createSvgElement o mediante la creación de DOM tradicional .

Los requisitos de la visualización en bloque de un campo son los siguientes:

  • Todos los elementos del DOM deben ser secundarios del fieldGroup_ del campo. El campo grupo se crea automáticamente.
  • Todos los elementos del DOM deben permanecer dentro de las dimensiones informadas del campo.

Consulta la Renderización para obtener más detalles sobre cómo personalizar y actualizar la pantalla en bloque.

Cómo agregar símbolos de texto

Si deseas agregar símbolos al texto de un campo (como el Ángulo grado de un campo), puedes agregar el elemento de símbolo (por lo general, ubicado en un <tspan>) directamente al textElement_ del campo.

Eventos de entrada

De forma predeterminada, los campos registran eventos de información sobre herramientas y eventos de desplazamiento del mouse (que se usarán para mostrando editores). Si deseas escuchar otros tipos de eventos (p.ej., si quieres controlar arrastrar en un campo), debes anular la función bindEvents_ del 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 establecer un enlace con un evento, generalmente debes usar el Blockly.utils.browserEvents.conditionalBind . Este método de vinculación de eventos filtra los toques secundarios durante arrastra. Si deseas que tu controlador se ejecute incluso en medio de un arrastre en curso puedes usar el Blockly.browserEvents.bind .

Desecho

Si registraste algún objeto de escucha de eventos personalizados dentro del bindEvents_ del campo se deberá cancelar su registro dentro de la función dispose.

Si inicializaste correctamente la vista de tu campo (anexando todos los elementos del DOM al fieldGroup_), luego el el DOM de este campo se eliminará automáticamente.

Manejo del valor

→ Para obtener información acerca del valor de un campo en comparación con su texto, consulta Anatomía de un .

Pedido de validación

Diagrama de flujo que describe el orden en el que se ejecutan los validadores

Cómo implementar un validador de clases

Los campos solo deben aceptar ciertos valores. Por ejemplo, los campos numéricos solo deben aceptar números, los campos de color solo deben aceptar colores, etc. Esto garantiza a través de clases validadores. La clase el validador sigue las mismas reglas que los validadores locales, excepto que también se ejecuta en la constructor y, por lo tanto, no debe hacer referencia al bloque source.

Para implementar el validador de clases de tu campo, anula doClassValidation_. .

doClassValidation_(newValue) {
  if (typeof newValue != 'string') {
    return null;
  }
  return newValue;
};

Controla valores válidos

Si el valor que se pasó a un campo con setValue es válido, recibirás un Devolución de llamada doValueUpdate_. De forma predeterminada, la función doValueUpdate_ hace lo siguiente:

  • Establece la propiedad value_ en newValue.
  • Establece el isDirty_. propiedad a true.

Si solo necesita almacenar el valor y no quiere hacer ningún manejo personalizado no es necesario que anules doValueUpdate_.

De lo contrario, puedes hacer lo siguiente:

  • Almacenamiento personalizado de newValue.
  • Cambia otras propiedades según newValue.
  • Guarda si el valor actual es válido o no.

Deberás anular doValueUpdate_:

doValueUpdate_(newValue) {
  super.doValueUpdate_(newValue);
  this.displayValue_ = newValue;
  this.isValueValid_ = true;
}

Controla los valores no válidos

Si el valor que se pasó al campo con setValue no es válido, recibirás un Devolución de llamada doValueInvalid_. De forma predeterminada, la función doValueInvalid_ hace lo siguiente: nada. Esto significa que, de forma predeterminada, no se mostrarán los valores no válidos. También significa que el campo no se volverá a renderizar, ya que isDirty_ no se establecerá ninguna propiedad.

Si deseas mostrar valores no válidos, debes anular doValueInvalid_. En la mayoría de los casos, debes establecer una propiedad displayValue_ en el valor no válido, establecido isDirty_ a true y anular render_ para que la pantalla en bloque se actualice en función del displayValue_ en lugar del value_

doValueInvalid_(newValue) {
  this.displayValue_ = newValue;
  this.isDirty_ = true;
  this.isValueValid_ = false;
}

Valores de varias partes

Cuando tu campo contiene un valor multiparte (p.ej., listas, vectores, objetos) que es posible que desee que las partes se manejen como valores individuales.

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;
}

En el ejemplo anterior, cada propiedad de newValue se valida de forma individual. Después al final de la función doClassValidation_, si alguna propiedad individual se no válido, el valor se almacena en caché en la propiedad cacheValidatedValue_ antes Se muestra null (no válido). Almacenar el objeto en caché con validaciones individuales permite que el doValueInvalid_ para manejarlos por separado, con tan solo hacer un !this.cacheValidatedValue_.property, en lugar de volver a validar cada propiedad de forma individual.

Este patrón para validar valores de varias partes también se puede usar en validadores, pero por el momento, no hay forma de aplicar este patrón.

isDirty_

isDirty_ es una marca que se usa en el setValue función, así como en otras partes del campo, para saber si el campo debe se volvió a renderizar. Si el valor de visualización del campo cambió, por lo general, isDirty_ debe establecerse en true.

Texto

→ Para obtener información sobre dónde se usa el texto de un campo y en qué se diferencia a partir del valor del campo, consulta Anatomía de un .

Si el texto de tu campo es diferente del valor de tu campo, deberías anular el Función getText para proporcionar el texto correcto.

getText() {
  let text = this.value_.turtleName + ' wearing a ' + this.value_.hat;
  if (this.value_.hat == 'Stovepipe' || this.value_.hat == 'Propeller') {
    text += ' hat';
  }
  return text;
}

Cómo crear un editor

Si defines la función showEditor_, Blockly detectará automáticamente hace clic y llama a showEditor_ en el momento adecuado. Puedes mostrar cualquier código HTML en el editor uniéndolo a uno de dos divs especiales, llamado y WidgetDiv, que flotan por encima del resto de la IU de Blockly.

DropDownDiv se usa para proporcionar editores que se encuentran dentro de un decodificador conectado. a un campo. Se posiciona automáticamente para estar cerca del campo de juego sin perder la capacidad dentro de los límites visibles. El selector de ángulo y el selector de color son buenos ejemplos de DropDownDiv

Imagen del selector de ángulo

WidgetDiv se usa para lo siguiente: proporcionan editores que no están dentro de un cuadro. Los campos numéricos usan el WidgetDiv para cubrir el campo con una casilla de entrada de texto HTML Mientras que DropDownDiv controla el posicionamiento por ti, WidgetDiv no lo hace. Los elementos deberán estar posicionarse manualmente. El sistema de coordenadas se expresa en coordenadas de píxeles con relación a la parte superior izquierda de la ventana. El editor de entrada de texto es un buen ejemplo de la WidgetDiv

Imagen del 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));
}

Código de muestra de 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);
}

Realice una limpieza

Tanto el controlador DropDownDiv como WidgetDiv destruye el HTML del widget de eventos, pero debes eliminar manualmente todos los objetos de escucha de eventos que tengas se aplican a esos elementos.

widgetDispose_() {
  for (let i = this.editorListeners_.length, listener;
      listener = this.editorListeners_[i]; i--) {
    Blockly.browserEvents.unbind(listener);
    this.editorListeners_.pop();
  }
}

Se llama a la función dispose en un contexto null en DropDownDiv. Activada el WidgetDiv al que se llama en el contexto de WidgetDiv. En cualquier caso se recomienda usar vincular cuando se pasa una función de eliminación, como se muestra en el ejemplo anterior de DropDownDiv y WidgetDiv.

→ Para obtener información sobre la eliminación no específica de editores, consulta Desechar.

Actualizando la pantalla en bloque

La función render_ se usa para actualizar la pantalla en bloque del campo para que coincida. su valor interno.

A continuación, se muestran algunos ejemplos comunes:

  • Cómo cambiar el texto (desplegable)
  • Cambiar el color (color)

Valores predeterminados

La función render_ predeterminada establece el texto en pantalla como el resultado de la getDisplayText_ . La función getDisplayText_ muestra la propiedad value_ del campo. convertir a una cadena, después de que se ha truncado para respetar el máximo de texto. del conjunto de datos.

Si usas la pantalla de bloqueo predeterminada y el comportamiento de texto predeterminado funciona para tu campo, no es necesario que anules render_.

Si el comportamiento de texto predeterminado funciona para tu campo, pero este está bloqueado pantalla tiene elementos estáticos adicionales, puedes llamar al render_ predeterminado pero aún deberás anularla para actualizar la configuración tamaño.

Si el comportamiento predeterminado del texto no funciona para tu campo o la configuración la pantalla en bloque tiene elementos dinámicos adicionales, deberás personalizar render_ la función.

Diagrama de flujo en el que se describe cómo tomar la decisión de anular la renderización_

Cómo personalizar la renderización

Si el comportamiento de renderización predeterminado no funciona para tu campo, deberás definen el comportamiento de la renderización personalizada. Esto puede implicar cualquier cosa, desde establecer mostrar texto, cambiar elementos de imagen o actualizar colores de fondo.

Todos los cambios de atributos del DOM son legales. Los únicos dos puntos que debes recordar son los siguientes:

  1. La creación del DOM debe controlarse durante inicialización, ya que es más eficiente.
  2. Siempre debes actualizar size_. para que coincida con el tamaño de la pantalla en bloque.
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_();
}

Actualizando tamaño

Es muy importante actualizar la propiedad size_ de un campo, ya que informa al código de renderización de bloques cómo posicionar el campo. La mejor manera de averiguar exactamente lo que debería ser size_ es experimentando.

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;
}

Colores de bloques coincidentes

Si quieres que los elementos de tu campo coincidan con los colores del bloque, debes anular el método applyColour. Deberías acceder al color a través de la propiedad de estilo del bloque.

applyColour() {
  const sourceBlock = this.sourceBlock_;
  if (sourceBlock.isShadow()) {
    this.arrow_.style.fill = sourceBlock.style.colourSecondary;
  } else {
    this.arrow_.style.fill = sourceBlock.style.colourPrimary;
  }
}

Actualizando la edición

La función updateEditable se puede usar para cambiar la forma en que aparece tu campo dependiendo de si se puede editar o no. La función predeterminada hace que el fondo tiene o no una respuesta de desplazamiento (borde) si se puede o no editar. La pantalla en bloque no debe cambiar de tamaño en función de su edición, pero todos los demás cambios están permitidos.

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;
  }
}

Serialización

La serialización consiste en guardar estado del campo para que pueda volver a cargarse en el lugar de trabajo más tarde.

El estado de tu lugar de trabajo siempre incluye el valor del campo, pero también podría Incluye otro estado, como el de la IU de tu campo. Por ejemplo, si tu era un mapa con zoom que permitía al usuario seleccionar países, podías también serializar el nivel de zoom.

Si tu campo se puede serializar, debes configurar la propiedad SERIALIZABLE de la siguiente manera: true

Blockly proporciona dos conjuntos de hooks de serialización para los campos. Un par de ganchos funciona con el nuevo sistema de serialización JSON, y el otro par trabaja con el el antiguo sistema de serialización XML.

saveState y loadState

saveState y loadState son hooks de serialización que funcionan con el JSON nuevo. serializado.

En algunos casos, no es necesario que proporciones estos datos, ya que la configuración implementaciones nuevas. Si (1) tu campo es una subclase directa de la base Clase Blockly.Field, (2) tu valor es un mensaje de tipo JSON serializable el tipo de contenido y (3) solo serializar el valor, la implementación predeterminada funcionará bien.

De lo contrario, tu función saveState debería mostrar un archivo JSON serializable objeto/valor que representa el estado del campo. Y tu loadState debe aceptar el mismo objeto/valor serializable JSON y aplicarlo al en el campo.

saveState() {
  return {
    'country': this.getValue(),  // Value state
    'zoom': this.getZoomLevel(), // UI state
  };
}

loadState(state) {
  this.setValue(state['country']);
  this.setZoomLevel(state['zoom']);
}

Datos de serialización y copia de seguridad completos

saveState también recibe un parámetro opcional doFullSerialization. Este es usado por campos que normalmente hacen referencia al estado serializado por una dirección serializador (como modelos de datos de copia de seguridad). El parámetro indica que el estado al que se hace referencia no estará disponible cuando se deserialice el bloque. de Terraform debe realizar la serialización por sí misma. Por ejemplo, esto es así cuando cuando un bloque individual se serializa o cuando un bloque se copia y pega.

Dos casos de uso comunes para esto son los siguientes:

  • Cuando se carga un bloque individual en un espacio de trabajo en el que los datos de copia de seguridad no existe, el campo tiene suficiente información en su propio estado para para crear un modelo de datos nuevo.
  • Cuando se copia y pega un bloque, el campo siempre crea una copia de seguridad nueva en lugar de hacer referencia a uno existente.

Un campo que usa esto es el campo de la variable integrada. Por lo general, se serializan El ID de la variable a la que hace referencia, pero si doFullSerialization es verdadero serializa todo su 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());
}

El campo de variable hace esto para garantizar que, si se carga en un lugar de trabajo donde su variable no existe, se puede crear una nueva para hacer referencia.

toXml y fromXml

toXml y fromXml son hooks de serialización que funcionan con el XML anterior serializado. Utiliza estos ganchos solo si es necesario (p.ej., estás trabajando en una base de código antigua que aún no se haya migrado), de lo contrario, usa saveState y loadState

Tu función toXml debe mostrar un nodo XML que represente el estado de en el campo. Tu función fromXml debe aceptar el mismo nodo XML y aplicar al 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'));
}

Propiedades editables y serializables

La propiedad EDITABLE determina si el campo debe tener una IU para indicar lo siguiente: con los que se puede interactuar. El valor predeterminado es true.

La propiedad SERIALIZABLE determina si el campo debe serializarse. Integra La configuración predeterminada es false. Si esta propiedad es true, es posible que debas proporcionar funciones de serialización y deserialización (consulta Serialización).

Personaliza el cursor

La propiedad CURSOR determina el cursor que verán los usuarios cuando coloquen el cursor sobre ellos. tu campo. Debe ser una cadena de cursor CSS válida. El valor predeterminado es el cursor. definido por .blocklyDraggable, que es el cursor de agarre