Prima di creare un nuovo tipo di campo, valuta se uno degli altri metodi per personalizzare i campi è adatto alle tue esigenze. Se la tua applicazione deve archiviare un nuovo tipo di valore o se vuoi creare una nuova UI per un tipo di valore esistente, probabilmente devi creare un nuovo tipo di campo.
Per creare un nuovo campo:
- Implementa un costruttore.
- Registrare una chiave JSON e implementare
fromJson
. - Gestisci l'inizializzazione dell'interfaccia utente e degli ascoltatori di eventi sul blocco.
- Gestisci lo smaltimento dei listener di eventi (lo smaltimento dell'UI viene gestito automaticamente).
- Implementa la gestione del valore.
- Aggiungi una rappresentazione testuale del valore del campo per l'accessibilità.
- Aggiungi ulteriori funzionalità, ad esempio:
- Configura aspetti aggiuntivi del tuo campo, ad esempio:
Questa sezione presuppone che tu abbia letto e abbia familiarità con i contenuti della sezione Struttura 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, di uno strumento di convalida 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. Quindi, il campo personalizzato non ha accesso al blocco di origine durante la creazione.
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 uno strumento di convalida locale. Il valore è facoltativo e se non passi un valore (o passi un valore che non supera la convalida della classe), verrà utilizzato il valore predefinito della superclasse. Per la classe Field
predefinita, questo valore è null
. Se non vuoi questo valore predefinito, assicurati di passare un valore adatto. Il parametro dello strumento di convalida è presente solo per i campi modificabili e in genere è contrassegnato come facoltativo. Scopri di più sugli strumenti di convalida nella documentazione relativa ai convalidatori.
Struttura
La logica all'interno del costruttore deve seguire questo flusso:
- Chiama il super costruttore ereditato (tutti i campi personalizzati devono ereditare da
Blockly.Field
o da una delle sue sottoclassi) per inizializzare correttamente il valore e impostare lo strumento di convalida locale per il tuo campo. - Se il campo è serializzabile, imposta la proprietà corrispondente nel costruttore. I campi modificabili devono essere serializzabili e i campi possono essere modificabili per impostazione predefinita, quindi probabilmente dovresti impostare questa proprietà su true a meno che tu non sappia che non deve essere serializzabile.
- (Facoltativo) Applica un'ulteriore personalizzazione (ad esempio, i campi etichetta consentono il trasferimento di 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 dei campi e chiama fromJson
sull'oggetto appropriato durante la costruzione.
Richiama Blockly.fieldRegistry.register
per aggiungere il tipo di campo a questa mappa, passando nella classe del campo come secondo argomento:
Blockly.fieldRegistry.register('field_generic', GenericField);
Devi inoltre definire la funzione fromJson
. L'implementazione deve
innanzitutto rimuovere i riferimenti alle tabelle
di stringhe
utilizzando
replaceMessageReferences,
quindi passare i valori al costruttore.
GenericField.fromJson = function(options) {
const value = Blockly.utils.parsing.replaceMessageReferences(
options['value']);
return new CustomFields.GenericField(value);
};
In fase di inizializzazione
Una volta creato, il campo contiene fondamentalmente solo un valore. L'inizializzazione è il luogo in cui viene creato il DOM, il modello (se il campo possiede un modello) e gli eventi sono associati.
Display On-Block
Durante l'inizializzazione, sei responsabile della creazione di tutto ciò di cui hai bisogno per la visualizzazione on-block 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 campo abbia entrambi questi elementi, oltre ad altri extra, chiama la funzione initView
della superclasse prima di aggiungere il resto degli elementi DOM. Se vuoi che il campo abbia uno di questi elementi, ma non entrambi, puoi utilizzare le funzioni createBorderRect_
o createTextElement_
.
Personalizzazione della creazione del DOM
Se il campo è un campo di testo generico (ad es. Input
di testo),
la creazione del DOM verrà gestita per te. In caso contrario, dovrai eseguire l'override della funzione initView
per creare gli elementi DOM necessari durante il rendering futuro del campo.
Ad esempio, un campo a discesa può contenere sia immagini che testo. In initView
crea un singolo elemento immagine e un singolo elemento di testo. Durante render_
, mostra l'elemento attivo e nasconde l'altro, in base al tipo di opzione selezionata.
La creazione di elementi DOM può essere eseguita utilizzando il metodo Blockly.utils.dom.createSvgElement
oppure i metodi tradizionali di creazione DOM.
I requisiti per la visualizzazione sul blocco di un campo sono:
- Tutti gli elementi DOM devono essere secondari del valore
fieldGroup_
del campo. Il gruppo di campi viene creato automaticamente. - Tutti gli elementi DOM devono rimanere all'interno delle dimensioni del campo segnalate.
Consulta la sezione Rendering per ulteriori dettagli sulla personalizzazione e sull'aggiornamento del display on-block.
Aggiunta di simboli di testo
Se vuoi aggiungere simboli al testo di un campo (ad esempio il simbolo dei gradi del campo Angolo), puoi aggiungere l'elemento simbolo (solitamente contenuto in un <tspan>
) direttamente al valore textElement_
del campo.
Eventi di input
Per impostazione predefinita, i campi registrano gli eventi della descrizione comando e gli eventi mousedown (da utilizzare per mostrare gli editor).
Se vuoi rimanere in ascolto di altri tipi di eventi (ad esempio, se vuoi gestire il trascinamento su 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 l'associazione a un evento devi utilizzare la funzione Blockly.utils.browserEvents.conditionalBind
. Questo metodo di associazione degli eventi filtra i tocchi secondari durante i trascinamenti. Se vuoi che il gestore venga eseguito anche nel bel mezzo di un trascinamento in corso, puoi utilizzare la funzione Blockly.browserEvents.bind
.
Smaltimento
Se hai registrato listener di eventi personalizzati all'interno della funzione bindEvents_
del campo, sarà necessario 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 dei valori
→ Per informazioni sul valore di un campo e sul relativo testo, consulta Struttura di un campo.
Ordine di convalida
Implementare uno strumento di convalida dei corsi
I campi devono accettare solo determinati valori. Ad esempio, i campi numerici devono accettare solo numeri, i campi colore devono accettare solo colori e così via. Ciò viene garantito tramite convalide di classe e locali. Lo strumento di convalida delle classi segue le stesse regole degli strumenti di convalida locali, tranne per il fatto che viene eseguito anche nel constructor e, di conseguenza, non deve fare riferimento al blocco di origine.
Per implementare lo strumento di convalida delle classi del campo, esegui l'override della funzione doClassValidation_
.
doClassValidation_(newValue) {
if (typeof newValue != 'string') {
return null;
}
return newValue;
};
Gestione di valori validi
Se il valore trasmesso a un campo con setValue
è valido, riceverai un callback di doValueUpdate_
. Per impostazione predefinita, la funzione doValueUpdate_
:
- Imposta la proprietà
value_
sunewValue
. - Imposta la proprietà
isDirty_
sutrue
.
Se devi semplicemente archiviare il valore e non vuoi eseguire una gestione personalizzata,
non è necessario sostituire doValueUpdate_
.
In caso contrario, se vuoi, ad esempio:
- Spazio di archiviazione personalizzato di
newValue
. - Modifica altre proprietà in base a
newValue
. - Indica 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 trasmesso al campo con setValue
non è valido, riceverai un callback di doValueInvalid_
. Per impostazione predefinita, la funzione doValueInvalid_
non esegue alcuna operazione. Ciò significa che, per impostazione predefinita, i valori non validi non verranno mostrati. Significa inoltre che il campo non verrà sottoposto nuovamente a rendering, perché la proprietà isDirty_
non verrà impostata.
Per visualizzare valori non validi, devi sostituire doValueInvalid_
.
Nella maggior parte dei casi, devi impostare una proprietà displayValue_
sul
valore non valido, impostare
isDirty_
su true
e eseguire l'override
del rendering_
affinché la visualizzazione su blocco venga aggiornata in base a displayValue_
anziché a
value_
.
doValueInvalid_(newValue) {
this.displayValue_ = newValue;
this.isDirty_ = true;
this.isValueValid_ = false;
}
Valori in più parti
Quando il campo contiene un valore con più parti (ad es. elenchi, vettori, oggetti), è consigliabile che le parti vengano gestite come valori singoli.
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 precedente, ogni proprietà di newValue
viene convalidata singolarmente. Al termine della funzione doClassValidation_
, se una singola proprietà non è valida, il valore viene memorizzato nella cache 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
, invece di convalidare nuovamente ogni proprietà singolarmente.
Questo pattern per la convalida di valori multiparte può essere utilizzato anche negli strumenti di convalida locali, ma al momento non è possibile applicare questo pattern.
isDirty_
isDirty_
è un flag utilizzato nella funzione setValue
e in altre parti del campo per indicare se il campo deve essere sottoposto nuovamente a rendering. 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 è diverso dal valore del campo, vedi Struttura di un campo.
Se il testo del campo è diverso dal valore del campo, devi sostituire la 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 rimane in ascolto dei clic e chiama showEditor_
al momento opportuno. Puoi visualizzare qualsiasi codice HTML
nell'editor inserendo uno dei due tag div speciali, denominati DropDownDiv,
e WidgetDiv, che appaiono sopra il resto dell'interfaccia utente di Blockly.
DropDownDiv e WidgetDiv
DropDownDiv
viene utilizzato per fornire editor che risiedono all'interno di una casella collegata
a un campo. Si posiziona automaticamente vicino al campo senza superare i limiti visibili. Il selettore dell'angolo e il selettore colori sono buoni esempi di DropDownDiv
.
La WidgetDiv
viene utilizzata per
fornire editor che non risiedono all'interno di una casella. I campi numerici utilizzano WidgetDiv per coprire il campo con una casella di immissione di testo HTML. Al contrario di DropDownDiv
gestisce il posizionamento per te, mentre WidgetDiv no. Gli elementi dovranno essere
posizionati manualmente. Il sistema di coordinate è espresso in pixel
rispetto alla parte superiore sinistra della finestra. L'editor di input di testo è un buon esempio di WidgetDiv
.
Codice di esempio 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 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);
}
Eseguire la pulizia
Sia DropDownDiv sia WidgetDiv gestiscono l'eliminazione degli elementi HTML del widget, ma devi eliminare manualmente i listener di eventi applicati 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 richiamata in un contesto null
sul DropDownDiv
. Su WidgetDiv
viene chiamata nel contesto di WidgetDiv
. In entrambi i casi, è meglio utilizzare la funzione bind quando passi una funzione di eliminazione, come mostrato negli esempi DropDownDiv
e WidgetDiv
precedenti.
→ Per informazioni sull'eliminazione non specifica di quella degli editor, consulta la sezione Smaltimento.
Aggiornamento del display nel blocco
La funzione render_
viene utilizzata per aggiornare la visualizzazione sul blocco del campo in modo che corrisponda al suo valore interno.
Ecco alcuni esempi comuni:
- Modificare il testo (elenco a discesa)
- Modifica il colore (colore)
Valori predefiniti
La funzione render_
predefinita imposta il testo visualizzato sul risultato della funzione
getDisplayText_
. La funzione getDisplayText_
restituisce il cast della proprietà value_
del campo a una stringa, dopo che è stata troncata per rispettare la lunghezza massima del testo.
Se utilizzi la visualizzazione on-block predefinita e il comportamento del testo predefinito funziona per il tuo campo, non è necessario eseguire l'override di render_
.
Se il comportamento del testo predefinito funziona per il tuo campo, ma la visualizzazione sul blocco
del campo contiene altri elementi statici, puoi chiamare la funzione render_
predefinita, ma dovrai comunque eseguirne l'override per aggiornare la dimensione
del campo.
Se il comportamento del testo predefinito non funziona per il tuo campo o se la visualizzazione sul blocco del campo contiene elementi dinamici aggiuntivi, devi personalizzare la funzione render_
.
Personalizzazione del rendering
Se il comportamento di rendering predefinito non funziona per il tuo campo, dovrai definire un comportamento di rendering personalizzato. Ciò può comportare qualsiasi cosa, dall'impostazione di un testo visualizzato personalizzato, alla modifica degli elementi dell'immagine, all'aggiornamento dei colori dello sfondo.
Tutte le modifiche agli attributi DOM sono legali, le uniche due cose da ricordare sono:
- Poiché è più efficiente, la creazione del DOM deve essere gestita durante l'inizializzazione.
- Devi sempre aggiornare la proprietà
size_
in modo che corrisponda alle dimensioni del display sul 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 in corso...
L'aggiornamento della proprietà size_
di un campo è molto importante perché indica al codice di rendering del blocco come posizionare il campo. Il modo migliore per capire
esattamente come dovrebbe essere size_
è fare delle prove.
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 del blocco corrispondenti
Se vuoi che gli elementi del campo corrispondano ai colori del blocco a cui sono associati, devi sostituire il metodo applyColour
. Dovrai accedere al colore tramite la proprietà di stile 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à in corso...
La funzione updateEditable
può essere utilizzata per modificare l'aspetto del campo a seconda 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 è modificabile o non è modificabile.
Le dimensioni del display sul blocco non devono cambiare in base alla possibilità di modifica, ma sono consentite tutte le altre modifiche.
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 comporta il salvataggio dello stato del campo in modo che possa essere ricaricato nell'area di lavoro in un secondo momento.
Lo stato dell'area di lavoro include sempre il valore del campo, ma potrebbe includere anche altri stati, come lo stato dell'interfaccia utente del campo. Ad esempio, se il campo era una mappa zoomabile che consentiva all'utente di selezionare i paesi, potresti serializzare il livello di zoom.
Se il campo è serializzabile, devi impostare la proprietà SERIALIZABLE
su true
.
Blockly fornisce due set di hook di serializzazione per i campi. Una coppia di hook funziona con il nuovo sistema di serializzazione JSON, mentre l'altra coppia funziona con il precedente 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 fornirli, perché le implementazioni predefinite funzioneranno. Se (1) il tuo campo è una sottoclasse diretta della classe Blockly.Field
di base, (2) il tuo valore è un tipo serializzabile JSON e (3) devi solo serializzare il valore, l'implementazione predefinita andrà bene.
In caso contrario, la funzione saveState
dovrebbe restituire un valore/oggetto serializzabile JSON che rappresenta 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 e backup completi dei dati
saveState
riceve anche un parametro facoltativo doFullSerialization
. Viene utilizzato dai campi che normalmente fanno riferimento a uno stato serializzato da un diverso serializzatore (come modelli di dati di supporto). Il parametro indica che
lo stato a cui viene fatto riferimento non sarà disponibile quando il blocco viene deserializzato, quindi
il campo dovrebbe eseguire tutte le operazioni di serializzazione. Ciò vale, ad esempio, 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 un'area di lavoro in cui il modello dei dati di supporto non esiste, il campo dispone di informazioni sufficienti nel proprio stato per creare un nuovo modello dei dati.
- Quando un blocco viene copiato e incollato, il campo crea sempre un nuovo modello di dati di supporto invece di fare riferimento a uno esistente.
Un campo che lo utilizza è quello della variabile integrata. Normalmente serializza l'ID della variabile a cui fa riferimento, ma se doFullSerialization
è true, serializza 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());
}
In questo modo, il campo Variabile garantisce 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 esempio se stai lavorando su un vecchio codebase di cui non è stata ancora eseguita la migrazione), altrimenti usa saveState
e loadState
.
La funzione toXml
deve restituire un nodo XML che rappresenta lo stato del campo. Inoltre, 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 una UI per indicare che è possibile interagire. Il valore predefinito è true
.
La proprietà SERIALIZABLE
determina se il campo deve essere serializzato. Il valore predefinito è false
. Se questa proprietà è true
, potresti dover fornire le funzioni di serializzazione e deserializzazione (vedi Serializzazione).
Personalizzazione del cursore
La proprietà CURSOR
determina il cursore visualizzato dagli utenti quando passano il mouse sopra
il campo. Deve essere una stringa di cursore CSS valida. Per impostazione predefinita, viene utilizzato il cursore definito da .blocklyDraggable
, ovvero il cursore di trascinamento.