Creare un campo personalizzato

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:

  1. Implementa un costruttore.
  2. Registra una chiave JSON e implementa fromJson.
  3. Gestisci l'inizializzazione dell'interfaccia utente on-block e degli ascoltatori di eventi.
  4. Gestisci lo smaltimento dei listener di eventi (lo smaltimento dell'interfaccia utente viene gestito per te).
  5. Implementa la gestione dei valori.
  6. Aggiungi una rappresentazione di testo del valore del campo per l'accessibilità.
  7. Aggiungere funzionalità aggiuntive, ad esempio:
  8. 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:

  1. 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.
  2. 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.
  3. (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

Diagramma di flusso che descrive l&#39;ordine in cui vengono eseguiti i validator

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_ su newValue.
  • Imposta la proprietà isDirty_ su true.

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 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.

Immagine del selettore di angolazione

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.

Immagine dell&#39;editor di immissione 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 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_.

Diagramma di flusso che descrive come decidere se eseguire l&#39;override di 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:

  1. La creazione del DOM deve essere gestita durante la inizializzazione, poiché è più efficiente.
  2. 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.