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:
- Implementa un constructor.
- Registra una clave JSON y, luego, implementa
fromJson
. - Controla la inicialización de la IU y el evento en bloque objetos de escucha.
- Controlar la eliminación de los objetos de escucha de eventos (la eliminación de la IU se controla para ti).
- Implementa el manejo del valor.
- Agrega una representación de texto del valor de tu campo para fines de accesibilidad.
- Agrega funciones adicionales, como las siguientes:
- 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:
- 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. - 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.
- 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
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_
ennewValue
. - Establece el
isDirty_
. propiedad atrue
.
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 vs. WidgetDiv
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
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
Código de muestra de 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));
}
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.
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:
- La creación del DOM debe controlarse durante inicialización, ya que es más eficiente.
- 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