Prima di creare un nuovo tipo di campo, valuta se uno degli altri metodi per personalizzare i campi soddisfa le tue esigenze. Se la tua applicazione deve memorizzare un nuovo tipo di valore o vuoi creare una nuova UI per un tipo di valore esistente, probabilmente dovrai creare un nuovo tipo di campo.
Per creare un nuovo campo:
- Implementa un costruttore.
- Registra una chiave JSON e implementa
fromJson
. - Gestisci l'inizializzazione dell'interfaccia utente on-block e degli ascoltatori di eventi.
- Gestisci lo smaltimento dei listener di eventi (lo smaltimento dell'interfaccia utente viene gestito per te).
- Implementa la gestione dei valori.
- Aggiungi una rappresentazione di testo del valore del campo per l'accessibilità.
- Aggiungere funzionalità aggiuntive, ad esempio:
- Configura aspetti aggiuntivi del campo, ad esempio:
Questa sezione presuppone che tu abbia letto e che tu abbia familiarità con i contenuti di Anatomia di un campo.
Per un esempio di campo personalizzato, consulta la demo dei campi personalizzati.
Implementazione di un costruttore
Il costruttore del campo è responsabile della configurazione del valore iniziale del campo e, facoltativamente, della configurazione di un convalidatore locale. Il costruttore del campo personalizzato viene chiamato durante l'inizializzazione del blocco di origine, indipendentemente dal fatto che il blocco di origine sia definito in JSON o JavaScript. Pertanto, il campo personalizzato non ha accesso al blocco di origine durante la compilazione.
Il seguente esempio di codice crea un campo personalizzato denominato GenericField
:
class GenericField extends Blockly.Field {
constructor(value, validator) {
super(value, validator);
this.SERIALIZABLE = true;
}
}
Firma del metodo
I costruttori di campi in genere accettano un valore e un validatore locale. Il valore è facoltativo e, se non ne specifichi uno (o ne specifichi uno che non supera la convalida della classe), verrà utilizzato il valore predefinito della superclasse. Per la classe Field
predefinita, il valore è null
. Se non vuoi questo valore predefinito, assicurati di passare un valore adatto. Il parametro di convalida è presente solo per i campi modificabili ed è in genere contrassegnato come facoltativo. Scopri di più su
gli autori di convalida nelle documentazioni degli autori di convalida.
Struttura
La logica all'interno del costruttore deve seguire questo flusso:
- Chiama il costruttore super ereditato (tutti i campi personalizzati devono ereditare da
Blockly.Field
o da uno dei suoi sottoclassi) per inizializzare correttamente il valore e impostare il validatore locale per il campo. - Se il campo è serializzabile, imposta la proprietà corrispondente nel constructor. I campi modificabili devono essere serializzabili e sono modificabili per impostazione predefinita, quindi ti consigliamo di impostare questa proprietà su true, a meno che tu non sappia che non deve essere serializzabile.
- (Facoltativo) Applica una personalizzazione aggiuntiva (ad esempio, i campi delle etichette consentono di passare una classe CSS, che viene poi applicata al testo).
JSON e registrazione
Nelle definizioni dei blocchi JSON, i campi sono descritti da una stringa (ad es. field_number
, field_textinput
). Blockly gestisce una mappa da queste stringhe agli oggetti di campo e chiama fromJson
sull'oggetto appropriato durante la costruzione.
Chiama Blockly.fieldRegistry.register
per aggiungere il tipo di campo a questa mappa, passando la classe del campo come secondo argomento:
Blockly.fieldRegistry.register('field_generic', GenericField);
Devi anche definire la funzione fromJson
. L'implementazione deve prima dereferenziare eventuali riferimenti ai token di localizzazione utilizzando replaceMessageReferences e poi passare i valori al costruttore.
GenericField.fromJson = function(options) {
const value = Blockly.utils.parsing.replaceMessageReferences(
options['value']);
return new CustomFields.GenericField(value);
};
Inizializzazione in corso
Quando il campo viene creato, contiene in pratica un solo valore. Durante l'inizializzazione viene creato il DOM, il modello (se il campo possiede un modello) e vengono associati gli eventi.
Display su blocco
Durante l'inizializzazione, è tua responsabilità creare tutto ciò di cui hai bisogno per la visualizzazione in blocco del campo.
Valori predefiniti, sfondo e testo
La funzione initView
predefinita crea un elemento rect
di colore chiaro e un elemento
text
. Se vuoi che il tuo campo abbia entrambe queste funzionalità, oltre ad alcuni extra, chiama la funzione del superclasse initView
prima di aggiungere il resto degli elementi DOM. Se vuoi che il campo contenga uno di questi elementi, ma non entrambi, puoi utilizzare le funzioni createBorderRect_
o createTextElement_
.
Personalizzazione della costruzione del DOM
Se il campo è un campo di testo generico (ad es. Text input), la creazione del DOM verrà gestita automaticamente. In caso contrario, dovrai eseguire l'override della funzione initView
per creare gli elementi DOM di cui avrai bisogno durante il rendering futuro del campo.
Ad esempio, un campo a discesa può contenere sia immagini che testo. In initView
viene creato un singolo elemento immagine e un singolo elemento di testo. Durante render_
viene mostrato l'elemento attivo e nascosto l'altro, in base al tipo di
opzione selezionata.
La creazione di elementi DOM può essere eseguita utilizzando il metodo Blockly.utils.dom.createSvgElement
o i metodi di creazione DOM tradizionali.
I requisiti per la visualizzazione in blocco di un campo sono:
- Tutti gli elementi DOM devono essere elementi secondari del
fieldGroup_
del campo. Il gruppo di campi viene creato automaticamente. - Tutti gli elementi DOM devono rimanere all'interno delle dimensioni riportate del campo.
Per ulteriori dettagli su come personalizzare e aggiornare la visualizzazione on-block, consulta la sezione Rendering.
Aggiunta di simboli di testo
Se vuoi aggiungere simboli al testo di un campo (ad esempio il simbolo di grado del campo Angolo), puoi accodare l'elemento simbolo (di solito contenuto in un <tspan>
) direttamente al textElement_
del campo.
Eventi di input
Per impostazione predefinita, i campi registrano gli eventi tooltip e mousedown (da utilizzare per mostrare gli editor).
Se vuoi ascoltare altri tipi di eventi (ad es. se vuoi gestire il trascinamento in un campo), devi sostituire la funzione 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;
}
);
}
In genere, per eseguire il binding a un evento devi utilizzare la funzione
Blockly.utils.browserEvents.conditionalBind
. Questo metodo di associazione degli eventi esclude i tocchi secondari durante i trascinamenti. Se vuoi che il gestore venga eseguito anche durante un trascinamento in corso, puoi utilizzare la funzione Blockly.browserEvents.bind
.
Smaltimento
Se hai registrato ascoltatori di eventi personalizzati all'interno della funzione bindEvents_
del campo, dovrai annullarne la registrazione all'interno della funzione dispose
.
Se hai inizializzato correttamente la visualizzazione del campo (aggiungendo tutti gli elementi DOM a fieldGroup_
), il DOM del campo verrà eliminato automaticamente.
Gestione del valore
→ Per informazioni sul valore di un campo rispetto al relativo testo, consulta la sezione Anatomia di un campo.
Ordine di convalida
Implementazione di un convalidatore di classi
I campi devono accettare solo determinati valori. Ad esempio, i campi numerici devono accettare solo numeri, i campi di colore devono accettare solo colori e così via. Ciò viene garantito tramite validatori di classe e locali. Il validatore di classi segue le stesse regole dei validatori locali, tranne per il fatto che viene eseguito anche nel costruttore e, di conseguenza, non deve fare riferimento al blocco di origine.
Per implementare il convalidatore di classe del campo, sostituisci la funzione doClassValidation_
.
doClassValidation_(newValue) {
if (typeof newValue != 'string') {
return null;
}
return newValue;
};
Gestione dei valori validi
Se il valore passato a un campo con setValue
è valido, riceverai un callbackdoValueUpdate_
. Per impostazione predefinita, la funzione doValueUpdate_
:
- Imposta la proprietà
value_
sunewValue
. - Imposta la proprietà
isDirty_
sutrue
.
Se devi semplicemente memorizzare il valore e non vuoi eseguire alcuna gestione personalizzata,
non devi eseguire l'override di doValueUpdate_
.
In caso contrario, se vuoi:
- Spazio di archiviazione personalizzato di
newValue
. - Modifica altre proprietà in base a
newValue
. - Salva se il valore corrente è valido o meno.
Dovrai eseguire l'override di doValueUpdate_
:
doValueUpdate_(newValue) {
super.doValueUpdate_(newValue);
this.displayValue_ = newValue;
this.isValueValid_ = true;
}
Gestione dei valori non validi
Se il valore passato al campo con setValue
non è valido, riceverai un callback
doValueInvalid_
. Per impostazione predefinita, la funzione doValueInvalid_
non fa nulla. Ciò significa che per impostazione predefinita i valori non validi non verranno visualizzati. Inoltre, significa che il campo non verrà visualizzato di nuovo perché la proprietà
isDirty_
non verrà impostata.
Se vuoi visualizzare valori non validi, devi sostituire doValueInvalid_
.
Nella maggior parte dei casi, devi impostare una proprietà displayValue_
sul valore invalido, impostare isDirty_
su true
e override
render_ per aggiornare la visualizzazione in blocco in base a displayValue_
anziché a value_
.
doValueInvalid_(newValue) {
this.displayValue_ = newValue;
this.isDirty_ = true;
this.isValueValid_ = false;
}
Valori composti da più parti
Quando il campo contiene un valore composto (ad es. elenchi, vettori, oggetti), potresti voler gestire le parti come singoli valori.
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;
}
Nell'esempio riportato sopra, ogni proprietà di newValue
viene convalidata singolarmente. Poi,
alla fine della funzione doClassValidation_
, se una singola proprietà è
non valida, il valore viene memorizzato nella proprietà cacheValidatedValue_
prima di
restituire null
(non valido). La memorizzazione nella cache dell'oggetto con proprietà convalidate singolarmente consente alla funzione doValueInvalid_
di gestirle separatamente, semplicemente eseguendo un controllo !this.cacheValidatedValue_.property
, anziché convalidare nuovamente ogni proprietà singolarmente.
Questo pattern per la convalida dei valori composti può essere utilizzato anche nei validatori locali, ma al momento non è possibile applicarlo.
isDirty_
isDirty_
è un flag utilizzato nella funzione
setValue
e in altre parti del campo per indicare se il campo deve essere
rigenerato. Se il valore visualizzato del campo è cambiato, in genere isDirty_
deve essere impostato su true
.
Testo
→ Per informazioni su dove viene utilizzato il testo di un campo e su come si differenzia dal valore del campo, consulta Anatomia di un campo.
Se il testo del campo è diverso dal valore del campo, devi eseguire l'override della funzione getText
per fornire il testo corretto.
getText() {
let text = this.value_.turtleName + ' wearing a ' + this.value_.hat;
if (this.value_.hat == 'Stovepipe' || this.value_.hat == 'Propeller') {
text += ' hat';
}
return text;
}
Creazione di un editor
Se definisci la funzione showEditor_
, Blockly ascolterà automaticamente i clic e chiamerà showEditor_
al momento opportuno. Puoi visualizzare qualsiasi codice HTML
nel tuo editor inserendolo in uno dei due elementi div speciali, chiamati DropDownDiv
e WidgetDiv, che galleggiano sopra il resto dell'interfaccia utente di Blockly.
DropDownDiv e WidgetDiv
DropDownDiv
viene utilizzato per fornire editor all'interno di una casella collegata
a un campo. Si posiziona automaticamente vicino al campo rimanendo
nei limiti visibili. Il selettore di angoli e il selettore di colori sono buoni esempi di DropDownDiv
.
WidgetDiv
viene utilizzato per fornire editor che non si trovano all'interno di una casella. I campi numerici utilizzano WidgetDiv per coprire il campo con una casella di immissione di testo HTML. DropDownDiv gestisce il posizionamento per te, mentre WidgetDiv no. Gli elementi dovranno essere posizionati manualmente. Il sistema di coordinate è in pixel rispetto all'angolo in alto a sinistra della finestra. L'editor di input di testo è un buon esempio di
WidgetDiv
.
Codice di esempio di 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));
}
Codice di esempio di 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);
}
Pulizia
Sia DropDownDiv sia WidgetDiv gestiscono l'eliminazione degli elementi HTML del widget, ma devi eliminare manualmente gli ascoltatori di eventi che hai applicato a questi elementi.
widgetDispose_() {
for (let i = this.editorListeners_.length, listener;
listener = this.editorListeners_[i]; i--) {
Blockly.browserEvents.unbind(listener);
this.editorListeners_.pop();
}
}
La funzione dispose
viene chiamata in un contesto null
su DropDownDiv
. Sul
WidgetDiv
viene chiamato nel contesto del WidgetDiv
. In entrambi i casi, è preferibile utilizzare la funzione bind quando passi una funzione di eliminazione, come mostrato negli esempi DropDownDiv
e WidgetDiv
riportati sopra.
→ Per informazioni sullo smaltimento non specifico per gli editor, consulta Smaltimento.
Aggiornamento della visualizzazione sul blocco
La funzione render_
viene utilizzata per aggiornare la visualizzazione in blocco del campo in modo che corrisponda al suo valore interno.
Ecco alcuni esempi comuni:
- Modificare il testo (menu a discesa)
- Modificare il colore (color)
Predefiniti
La funzione render_
predefinita imposta il testo visualizzato sul risultato della funzione
getDisplayText_
. La funzione getDisplayText_
restituisce la proprietà value_
del campo
trasformata in una stringa, dopo che è stata troncata per rispettare la lunghezza massima del testo.
Se utilizzi la visualizzazione predefinita nel blocco e il comportamento predefinito del testo funziona per il tuo campo, non è necessario sostituire render_
.
Se il comportamento predefinito del testo funziona per il campo, ma la visualizzazione nel blocco del campo contiene elementi statici aggiuntivi, puoi chiamare la funzione render_
predefinita, ma dovrai comunque sostituirla per aggiornare le dimensioni del campo.
Se il comportamento predefinito del testo non funziona per il tuo campo o se la visualizzazione in blocco del campo contiene elementi dinamici aggiuntivi, dovrai personalizzare la funzione render_
.
Personalizzare il rendering
Se il comportamento di rendering predefinito non funziona per il tuo campo, dovrai definire un comportamento di rendering personalizzato. Può essere necessario impostare un testo visualizzato personalizzato, modificare gli elementi dell'immagine o aggiornare i colori di sfondo.
Tutte le modifiche agli attributi DOM sono legali, ma ricordati due cose:
- La creazione del DOM deve essere gestita durante la inizializzazione, poiché è più efficiente.
- Devi sempre aggiornare la proprietà
size_
in modo che corrisponda alle dimensioni della visualizzazione in blocco.
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_();
}
Aggiornamento delle dimensioni
L'aggiornamento della proprietà size_
di un campo è molto importante, in quanto indica al codice di rendering del blocco come posizionare il campo. Il modo migliore per capire esattamente che cosa debba essere size_
è sperimentare.
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;
}
Colori dei blocchi abbinati
Se vuoi che gli elementi del campo corrispondano ai colori del blocco a cui sono collegati, devi sostituire il metodo applyColour
. Ti consigliamo di accedere al colore tramite la proprietà style del blocco.
applyColour() {
const sourceBlock = this.sourceBlock_;
if (sourceBlock.isShadow()) {
this.arrow_.style.fill = sourceBlock.style.colourSecondary;
} else {
this.arrow_.style.fill = sourceBlock.style.colourPrimary;
}
}
Aggiornamento della modificabilità
La funzione updateEditable
può essere utilizzata per modificare la visualizzazione del campo in base al fatto che sia modificabile o meno. La funzione predefinita fa in modo che lo sfondo abbia/non abbia una risposta al passaggio del mouse (bordo) se è/non è modificabile.
La visualizzazione in blocco non deve cambiare dimensioni a seconda della sua modificabilità, ma tutte le altre modifiche sono consentite.
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;
}
}
Serializzazione
La serializzazione consiste nel salvare lo stato del campo in modo che possa essere ricaricato nello spazio di lavoro in un secondo momento.
Lo stato dello spazio di lavoro include sempre il valore del campo, ma potrebbe includere anche un altro stato, ad esempio lo stato dell'interfaccia utente del campo. Ad esempio, se il campo era una mappa con zoom che consentiva all'utente di selezionare i paesi, potresti anche serializzare il livello di zoom.
Se il campo è serializzabile, devi impostare la proprietà SERIALIZABLE
su
true
.
Blockly fornisce due insiemi di hook di serializzazione per i campi. Una coppia di hook funziona con il nuovo sistema di serializzazione JSON e l'altra con il vecchio sistema di serializzazione XML.
saveState
e loadState
saveState
e loadState
sono hook di serializzazione che funzionano con il nuovo sistema di serializzazione JSON.
In alcuni casi non è necessario fornire queste informazioni, perché verranno utilizzate le implementazioni predefinite. Se (1) il campo è una sottoclasse diretta della classe baseBlockly.Field
, (2) il valore è un tipo serializzabile in JSON e (3) devi solo eseguire la serializzazione del valore, l'implementazione predefinita funzionerà perfettamente.
In caso contrario, la funzione saveState
deve restituire un valore/oggetto serializzabile JSON che rappresenti lo stato del campo. Inoltre, la funzione loadState
deve accettare lo stesso oggetto/valore JSON serializzabile e applicarlo al campo.
saveState() {
return {
'country': this.getValue(), // Value state
'zoom': this.getZoomLevel(), // UI state
};
}
loadState(state) {
this.setValue(state['country']);
this.setZoomLevel(state['zoom']);
}
Serializzazione completa e dati di backup
saveState
riceve anche un parametro facoltativo doFullSerialization
. Viene utilizzato dai campi che in genere fanno riferimento allo stato serializzato da un serializzatore diverso (ad esempio i modelli di dati di supporto). Il parametro indica che
lo stato a cui si fa riferimento non sarà disponibile quando il blocco viene deserializzato, pertanto il
campo deve eseguire tutta la serializzazione stessa. Ad esempio, questo accade quando un singolo blocco viene serializzato o quando un blocco viene copiato e incollato.
Ecco due casi d'uso comuni:
- Quando un singolo blocco viene caricato in uno spazio di lavoro in cui non esiste il modello di dati di supporto, il campo contiene informazioni sufficienti nel proprio stato per creare un nuovo modello di dati.
- Quando un blocco viene copiato e incollato, il campo crea sempre un nuovo modello di dati di supporto anziché fare riferimento a uno esistente.
Un campo che lo utilizza è il campo della variabile incorporata. Normalmente, viene serializzato
l'ID della variabile a cui fa riferimento, ma se doFullSerialization
è true
viene serializzato tutto il suo stato.
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());
}
Il campo della variabile esegue questa operazione per assicurarsi che, se viene caricato in un'area di lavoro in cui la relativa variabile non esiste, possa creare una nuova variabile a cui fare riferimento.
toXml
e fromXml
toXml
e fromXml
sono hook di serializzazione che funzionano con il vecchio sistema di serializzazione XML. Utilizza questi hook solo se necessario (ad es. se stai lavorando su una vecchia base di codice di cui non è ancora stata eseguita la migrazione), altrimenti utilizza saveState
e
loadState
.
La funzione toXml
deve restituire un nodo XML che rappresenta lo stato del campo. La funzione fromXml
deve accettare lo stesso nodo XML e applicarlo 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'));
}
Proprietà modificabili e serializzabili
La proprietà EDITABLE
determina se il campo deve avere un'interfaccia utente che indichi che è possibile interagire con esso. Il valore predefinito è true
.
La proprietà SERIALIZABLE
determina se il campo deve essere serializzato. Il valore predefinito è false
. Se questa proprietà è true
, potrebbe essere necessario fornire funzioni di serializzazione e deserializzazione (consulta Serializzazione).
Personalizzare il cursore
La proprietà CURSOR
determina il cursore visualizzato dagli utenti quando passano il mouse sopra il campo. Deve essere una stringa del cursore CSS valida. Per impostazione predefinita, viene utilizzato il cursore
definito da .blocklyDraggable
, ovvero il cursore di acquisizione.