Creazione di un nuovo tipo di campo

Prima di creare un nuovo tipo di campo, valuta se uno degli altri per personalizzare i campi in base alle tue esigenze. Se la tua applicazione deve archiviare un nuovo tipo di valore o vuoi creare una nuova UI per un tipo di valore esistente, creare un nuovo tipo di campo.

Per creare un nuovo campo:

  1. Implementa un costruttore.
  2. Registra una chiave JSON e implementa fromJson.
  3. Gestisci l'inizializzazione della UI e dell'evento sul blocco ascoltatori.
  4. Gestisci l'eliminazione dei listener di eventi (lo smaltimento dell'UI viene gestito per te).
  5. Implementa la gestione del valore.
  6. Aggiungi una rappresentazione testuale del valore del campo per l'accessibilità.
  7. Aggiungi ulteriori funzionalità, ad esempio:
  8. Configura aspetti aggiuntivi del campo, ad esempio:

In questa sezione si presuppone che tu abbia letto e abbia familiarità con i contenuti in Anatomia di un Campo.

Per un esempio di campo personalizzato, consulta la sezione Campi personalizzati demo di Google.

Implementazione di un costruttore

Il costruttore del campo è responsabile della configurazione del valore iniziale del campo e, facoltativamente, di configurare un strumento di convalida. L'opzione viene richiamato durante l'inizializzazione del blocco di origine, o meno che il blocco di origine sia definito in JSON o JavaScript. Quindi, il modello 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 dei campi di solito includono 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 classe , verrà utilizzato il valore predefinito della superclasse. Per predefinita Field, il valore è null. Se non vuoi quella predefinita un valore appropriato, quindi assicurati di trasmettere un valore adatto. Il parametro di convalida è solo presenti per i campi modificabili ed è in genere contrassegnata come facoltativa. Scopri di più sugli strumenti di convalida nella sezione Validators documenti.

Struttura

La logica all'interno del costruttore deve seguire questo flusso:

  1. Chiama il super costruttore ereditato (tutti i campi personalizzati devono ereditare Blockly.Field o una delle relative sottoclassi) per inizializzare correttamente il valore e impostare lo strumento di convalida locale per il campo.
  2. Se il campo è serializzabile, imposta la proprietà corrispondente nel campo come costruttore. I campi modificabili devono essere serializzabili e devono essere modificabili per impostazione predefinita, quindi probabilmente dovresti impostare questa proprietà su true se non sai non deve essere serializzabile.
  3. (Facoltativo) Applica una personalizzazione aggiuntiva (ad esempio Campi etichetta) consente di passare una classe CSS, che viene poi applicata al testo).

JSON e registrazione

Nel blocco JSON definizioni, sono descritti da una stringa (ad es. field_number, field_textinput). Blockly mantiene una mappa da queste stringhe agli oggetti dei campi e richiama fromJson sull'oggetto appropriato durante la costruzione.

Chiama Blockly.fieldRegistry.register per aggiungere il tipo di campo a questa mappa passando nella classe campo come secondo argomento:

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

Devi anche definire la funzione fromJson. L'implementazione dovrebbe prima dereferenzia qualsiasi stringa tavola i riferimenti utilizzando replaceMessageReferences, e poi passiamo 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 viene creato, il campo contiene fondamentalmente solo un valore. L'inizializzazione è il luogo in cui viene creato il DOM, viene creato il modello (se il campo possiede un modello) e gli eventi sono legati.

Display on-block

Durante l'inizializzazione sei responsabile di creare tutto ciò di cui hai bisogno per la visualizzazione nel blocco del campo.

Valori predefiniti, sfondo e testo

La funzione initView predefinita crea un elemento rect di colore chiaro e un Elemento text. Se il tuo campo deve avere entrambi questi elementi, più alcune componenti aggiuntivi, chiama la funzione initView della superclasse prima di aggiungere il resto Elementi DOM. Se vuoi che nel campo ne sia uno, ma non entrambi, elementi puoi utilizzare le funzioni createBorderRect_ o createTextElement_.

Personalizzazione creazione DOM

Se il campo è un campo di testo generico (ad es. Testo di input), La creazione del DOM verrà gestita per te. In caso contrario dovrai eseguire l'override la 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 sia testo. Tra initView crea un singolo elemento immagine e un singolo elemento di testo. Poi nel corso di render_ mostra l'elemento attivo e nasconde l'altro, in base al tipo di l'opzione selezionata.

La creazione di elementi DOM può essere eseguita utilizzando Blockly.utils.dom.createSvgElement o utilizzando la creazione DOM tradizionale di machine learning.

I requisiti per la visualizzazione nel blocco di un campo sono:

  • Tutti gli elementi DOM devono essere secondari dell'elemento fieldGroup_ del campo. Il campo viene creato automaticamente.
  • Tutti gli elementi DOM devono rimanere all'interno delle dimensioni riportate del campo.

Consulta le Rendering per maggiori dettagli su come personalizzare e aggiornare la visualizzazione a blocchi.

Aggiunta di simboli di testo

Se vuoi aggiungere simboli al testo di un campo (ad esempio Angolo simbolo dei gradi di un campo) puoi aggiungere l'elemento simbolo (di solito contenuto in un <tspan>) direttamente nel campo textElement_ del campo.

Eventi di input

Per impostazione predefinita, i campi registrano eventi di descrizione comando ed eventi mousedown (da utilizzare per visualizzazione editor). Se vuoi rimanere in ascolto di altri tipi di eventi (ad esempio, se vuoi gestire trascinato 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;
      }
  );
}

Per eseguire l'associazione a un evento in genere devi usare la classe Blockly.utils.browserEvents.conditionalBind personalizzata. Questo metodo di associazione degli eventi filtra i tocchi secondari durante trascinamenti. Se desideri che il gestore venga eseguito anche nel mezzo di un trascinamento in corso puoi utilizzare Blockly.browserEvents.bind personalizzata.

Smaltimento

Se hai registrato un listener di eventi personalizzato all'interno dell'elemento bindEvents_ del campo Occorre annullare la registrazione all'interno della funzione dispose.

Se hai inizializzato correttamente visualizza del campo (aggiungendo tutti gli elementi DOM a fieldGroup_), quindi il DOM del campo verrà eliminato automaticamente.

Gestione dei valori

→ Per informazioni sul valore di un campo rispetto al suo testo, vedi Struttura di un campo.

Ordine di convalida

Diagramma di flusso che descrive l&#39;ordine di esecuzione degli strumenti di convalida

Implementazione di uno strumento di convalida delle classi

I campi devono accettare solo determinati valori. Ad esempio, i campi numerici devono accettare numeri, campi colore devono accettare solo colori ecc. Ciò è garantito tramite classi e strumenti di convalida. Il corso segue le stesse regole degli strumenti di convalida locali, ad eccezione del fatto che viene eseguito nel di costruttore e, di conseguenza, non deve fare riferimento al blocco source.

Per implementare lo strumento di convalida della classe del campo, sostituisci doClassValidation_ personalizzata.

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 Chiamata doValueUpdate_. Per impostazione predefinita, la funzione doValueUpdate_:

  • Imposta la proprietà value_ su newValue.
  • Imposta la isDirty_ a true.

Se devi semplicemente archiviare il valore e non vuoi eseguire alcuna gestione personalizzata, non è necessario eseguire l'override di doValueUpdate_.

Altrimenti, se vuoi eseguire operazioni quali:

  • Spazio di archiviazione personalizzato di newValue.
  • Modifica le 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 di valori non validi

Se il valore trasmesso al campo con setValue non è valido, riceverai un Chiamata doValueInvalid_. Per impostazione predefinita, la funzione doValueInvalid_ niente. Ciò significa che per impostazione predefinita i valori non validi non vengono mostrati. Inoltre, significa che il campo non verrà sottoposto nuovamente a rendering, perché isDirty_ non verrà impostata.

Se vuoi visualizzare valori non validi, devi sostituire doValueInvalid_. Nella maggior parte dei casi, devi impostare una proprietà displayValue_ su valore non valido, impostato isDirty_ a true e sostituisci render_ per l'aggiornamento del display a blocchi in base a displayValue_ anziché value_.

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

Valori a più parti

Se il campo contiene un valore multiparte (ad es. elenchi, vettori, oggetti), potrebbe volere che le parti vengano gestite 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 precedente, ciascuna proprietà di newValue viene convalidata singolarmente. Poi alla fine della funzione doClassValidation_, se una singola proprietà è non valido, il valore viene memorizzato nella cache per la proprietà cacheValidatedValue_ prima restituisce null (non valido). Memorizzazione nella cache dell'oggetto mediante proprietà consente doValueInvalid_ per gestirli separatamente, semplicemente eseguendo una !this.cacheValidatedValue_.property, invece di riconvalidare ogni singolarmente.

Questo pattern per la convalida dei valori multiparte può essere utilizzato anche in di convalida ma al momento non c'è modo di applicare questo pattern.

isDirty_

isDirty_ è un flag utilizzato nel setValue e altre parti del campo, per capire se il campo deve essere di nuovo sottoposti a rendering. Se il valore visualizzato del campo è cambiato, solitamente isDirty_ dovrebbe essere impostato su true.

Testo

→ Per informazioni su dove viene utilizzato il testo di un campo e sulle sue differenze dal valore del campo, vedi Struttura di un campo.

Se il testo del campo è diverso dal valore del campo stesso, devi sostituisci 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 fa clic e chiama showEditor_ al momento opportuno. Puoi visualizzare qualsiasi codice HTML nell'editor racchiudendolo in uno dei due div speciali, denominati DropdownDiv e WidgetDiv, che fluttuano sopra il resto dell'interfaccia utente di Blockly.

DropDownDiv viene utilizzato per fornire editor che risiedono all'interno di un box collegato in un campo. Si posiziona automaticamente vicino al campo e rimane entro i limiti visibili. Il selettore angolare e il selettore colori sono ottimi esempi di DropDownDiv.

Immagine del selettore di angoli

WidgetDiv è utilizzato per forniscono 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. Mentre il comando DropdownDiv WidgetDiv gestisce il posizionamento al posto tuo. Gli elementi devono essere posizionato manualmente. Il sistema di coordinate è espresso in coordinate pixel rispetto a in alto a sinistra della finestra. L'editor di input di testo è un buon esempio WidgetDiv.

Immagine dell&#39;editor di input di testo

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

esegui la pulizia

Sia DropdownDiv che WidgetDiv gestiscono l'eliminazione dell'HTML del widget ma devi eliminare manualmente tutti 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 chiamata in un contesto null su DropDownDiv. Attivato WidgetDiv viene chiamato nel contesto di WidgetDiv. In entrambi i casi è meglio utilizzare associare quando passi una funzione di eliminazione, come mostrato in DropDownDiv sopra e WidgetDiv esempi.

→ Per informazioni non specifiche sullo smaltimento degli editor, vedi Smaltimento.

Aggiornamento del display su blocco

La funzione render_ viene utilizzata per aggiornare la visualizzazione sul blocco del campo in modo che corrisponda il suo valore interno.

Ecco alcuni esempi comuni:

  • Modificare il testo (menu a discesa)
  • Cambiare il colore (colore)
di Gemini Advanced.

Predefiniti

La funzione predefinita render_ imposta il testo visualizzato in base al risultato getDisplayText_ personalizzata. La funzione getDisplayText_ restituisce la proprietà value_ del campo viene trasmesso a una stringa, dopo che è stato troncato per rispettare i limiti di lunghezza.

Se utilizzi la visualizzazione predefinita sul blocco e il comportamento predefinito del testo funziona per il tuo campo, non devi eseguire l'override di render_.

Se il comportamento predefinito del testo funziona per il campo, ma quest'ultimo è bloccato La pubblicità display presenta altri elementi statici, puoi chiamare il valore predefinito render_ ma dovrai comunque eseguirne l'override per aggiornare la funzione dimensioni.

Se il comportamento predefinito del testo non funziona per il tuo campo o il testo la visualizzazione a blocchi presenta altri elementi dinamici, dovrai personalizzare render_ personalizzata.

Diagramma di flusso che descrive come decidere se sostituire il rendering_

Personalizzazione del rendering

Se il comportamento di rendering predefinito non funziona per il tuo campo, devi per definire un comportamento di rendering personalizzato. Ciò può includere qualsiasi cosa, ad esempio l'impostazione testo visualizzato, modifica degli elementi dell'immagine o aggiornamento dei colori di sfondo.

Tutte le modifiche agli attributi DOM sono legali; le uniche due cose da ricordare sono:

  1. La creazione del DOM deve essere gestita durante inizializzazione, in quanto è più efficiente.
  2. Devi sempre aggiornare size_ in modo che corrisponda alle dimensioni della visualizzazione nel 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é informa la bloccare il codice di rendering come posizionare il campo. Il modo migliore per capire esattamente quello che dovrebbe essere size_ mediante esperimenti.

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 corrispondenti

Se desideri che gli elementi del campo corrispondano ai colori del blocco, devi sostituire il metodo applyColour. Ti consigliamo di accedere al colore attraverso 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...

È possibile utilizzare la funzione updateEditable per modificare l'aspetto del campo a seconda che siano modificabili o meno. La funzione predefinita fa sì che lo sfondo ha o non ha una risposta al passaggio del mouse (bordo) se è o non è modificabile. La visualizzazione sul blocco non deve cambiare di dimensione a seconda della possibilità di modificare, 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 serie riguarda il salvataggio lo stato desiderato 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 anche includere un altro stato, ad esempio lo stato dell'interfaccia utente del campo. Ad esempio, se era una mappa zoomabile che consentiva all'utente di selezionare i paesi, potevi serializzare anche 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. Un paio di ganci funziona con il nuovo sistema di serializzazione JSON, mentre l'altra coppia funziona con un vecchio sistema di serializzazione XML.

saveState e loadState

saveState e loadState sono hook di serializzazione che funzionano con il nuovo JSON di serializzazione.

In alcuni casi non è necessario fornirli perché il valore predefinito le implementazioni funzionano. Se (1) il campo è una sottoclasse diretta della classe di base Blockly.Field classe, (2) il valore è serie serializzabile in JSON type e (3) devi solo serializzare il valore, l'implementazione predefinita andrà bene.

In caso contrario, la funzione saveState dovrebbe restituire un codice JSON serializzabile che rappresenta lo stato del campo. e loadState deve accettare lo stesso oggetto/valore serializzabile JSON e applicarlo 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. Questo è usata dai campi che normalmente fanno riferimento allo stato serializzato da un serializzatore (come i modelli dei dati di backup). Il parametro segnala che lo stato di riferimento non sarà disponibile quando il blocco viene deserializzato, quindi dovrebbe eseguire tutte le operazioni di serializzazione. Ad esempio, è vero quando un singolo blocco viene serializzato o quando un blocco viene copiato e incollato.

Due casi d'uso comuni sono:

  • Quando un singolo blocco viene caricato in un'area di lavoro in cui i dati di supporto modello inesistente, il campo ha informazioni sufficienti nel proprio stato per per creare un nuovo modello dei dati.
  • Quando un blocco viene copiato e incollato, il campo crea sempre un nuovo supporto del modello dei dati, invece di farvi riferimento a uno esistente.

Un campo che lo utilizza è il campo 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());
}

Il campo della variabile fa in modo che, se è stato caricato in un'area di lavoro, se la sua variabile non esiste, può crearne una nuova a cui fare riferimento.

toXml e fromXml

toXml e fromXml sono hook di serializzazione che funzionano con il vecchio XML di serializzazione. Usa questi ganci solo se necessario (ad es. 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 di campo. La funzione fromXml deve accettare lo stesso nodo XML e essere applicata 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 per indicare che con cui è possibile interagire. Il valore predefinito è true.

La proprietà SERIALIZABLE determina se il campo deve essere serializzato. it 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 tuo campo. Deve essere una stringa del cursore CSS valida. Per impostazione predefinita, il cursore definito da .blocklyDraggable, ovvero il cursore di trascinamento.