Modificare le definizioni dei blocchi

Una domanda comune è come modificare la definizione di un blocco esistente. Ad esempio, potresti voler aggiungere un controllo di connessione o modificare un campo in un input di valore.

In generale, non è possibile modificare una definizione di blocco in situ.

Come modificare una definizione esistente

Se vuoi modificare la definizione di un tipo di blocco esistente:

  1. Crea una copia della definizione esistente, inclusi i generatori di codice blocco.
  2. Modifica la copia e assegna un nuovo nome al tipo.
  3. Aggiungi la nuova definizione a Blockly.Blocks.

Perché non posso modificare direttamente una definizione esistente?

Se vuoi sapere perché non puoi modificare una definizione esistente, continua a leggere. Valuteremo alcune possibilità.

Modificare direttamente una definizione esistente

Esistono due modi per modificare direttamente la definizione di un blocco esistente: la modifica del codice e il fork del codice. Entrambe le pratiche sono vivamente sconsigliate, in quanto corri il rischio di danneggiare il codice che dipende dal codice sottoposto a monkey patching o dal codice derivato. Entrambe le tecniche rendono inoltre difficile integrare aggiornamenti e correzioni di bug. Per ulteriori informazioni, consulta Che cos'è il monkey patching? e Fork Blockly.

Creare una sottoclasse di una definizione esistente

Potresti pensare di creare una sottoclasse di una definizione esistente. Purtroppo, questo non è possibile perché le definizioni dei blocchi non sono classi, ma mixin. Ad esempio, non è possibile sovrascrivere una proprietà di colore perché la definizione non ha una proprietà di colore. Ha invece una funzione init che chiama setColour per impostare la proprietà del colore sul blocco. Poiché la chiamata è all'interno di init, non è possibile sostituirla senza sostituire l'intera funzione init.

Sovrascrivere una funzione in una definizione esistente

È possibile sovrascrivere una funzione in una definizione esistente:

Blockly.Blocks['existing_block'].init = function() {/*new function*/};

Funziona, ma devi copiare e modificare la funzione esistente, non puoi sostituire magicamente solo una riga. Esistono diversi problemi con questo approccio:

  • Non è molto diverso dalla copia e dalla modifica dell'intera definizione.
  • Non puoi utilizzarla per modificare la funzione init dei blocchi definiti in JSON, ad esempio i blocchi integrati di Blockly. Questo perché non esiste una funzione init da copiare: viene generata in fase di esecuzione.
  • Richiede di definire il blocco utilizzando JavaScript, il che potrebbe causare problemi con la localizzazione.

Sovrascrivi i risultati di init

Un modo per "sostituire solo una riga" di una funzione init è sostituire la funzione init con una funzione che chiama la funzione init originale e poi sovrascrive il risultato di quella chiamata. Ad esempio, il seguente codice cambia il colore del blocco logic_null:

const originalInit = Blockly.Blocks['logic_null'].init;
Blockly.Blocks['logic_null'].init = function() {
  originalInit.call(this);
  this.setColour(300);
}

Purtroppo, questa opzione è meno utile di quanto sembri. Ad esempio:

  • L'ampliamento dei controlli di connessione o l'applicazione di un convalidatore di campi meno restrittivo potrebbe invalidare le supposizioni fatte dai generatori di codice blocco e dai gestori di eventi.

  • La sostituzione di un campo con un valore inserito interrompe i generatori di codice blocchi e i convalidatori dei campi e potrebbe interrompere i gestori degli eventi. Inoltre, può essere estremamente difficile da eseguire per i blocchi localizzati perché impostazioni internazionali diverse possono comportare blocchi con tipi e ordini diversi di input e campi.

Sostituire una coppia chiave-valore in una definizione JSON

Se il JSON di un blocco è disponibile pubblicamente, potrebbe essere possibile sovrascrivere i singoli valori JSON. Ad esempio:

// Block definition.
blockJson = {...};
Blockly.Blocks['my_block'] = {
  init: function() {
    initJson(blockJson); // Called when the block is created.
  }
}

// Third-party code.
blockJson.colour = 100;

Tuttavia, questo funziona solo se l'oggetto JSON è disponibile pubblicamente, la definizione definisce esplicitamente la funzione init e la funzione init chiama initJson. Non funziona se il JSON viene passato a defineBlocksWithJsonArray o createBlockDefinitionsFromJsonArray perché viene elaborato prima che una terza parte abbia la possibilità di modificarlo. Tieni presente che i blocchi integrati di Blockly utilizzano createBlockDefinitionsFromJsonArray.

Ma cosa succede se il JSON non è definito in questo modo? Non dovrebbe essere ancora possibile sovrascrivere le proprietà JSON? Purtroppo no. La definizione contiene una funzione init (non JSON) e non esiste una funzione per convertire una funzione init in JSON.

// Doesn't work. There is no getJson() function.
const json = Blockly.Blocks['existing_block'].getJson();
json['message0'] = 'my new message0';
Blockly.Blocks['existing_block'].init = function () {
  initJson(json);
};

Progettare per il riutilizzo

Quando progetti i tuoi blocchi personalizzati, potresti riuscire a progettarli in modo da promuovere il riutilizzo.

Riutilizzare JSON

Se hai due blocchi sostanzialmente simili, puoi creare una definizione JSON principale e riutilizzarla nelle definizioni secondarie. Ad esempio:

const parentJson = {
  // shared properties
};

Blockly.Blocks['child_block_1'] = {
  init: function() {
    initJson({...parentJson, colour: 100})
  }
}

Blockly.Blocks['child_block_2'] = {
  init: function() {
    initJson({...parentJson, colour: 200})
  }
}

Un'altra alternativa è definire il JSON in un oggetto disponibile pubblicamente e passarlo a initJson nella funzione init. In questo modo, è possibile per altri utenti sovrascrivere le singole proprietà. Per ulteriori informazioni, consulta Sovrapporre una coppia chiave-valore in una definizione JSON.

Riutilizzare le funzioni

I blocchi possono definire una serie di funzioni standard, come gestori di eventi a livello di blocco, descrizioni comando personalizzate, convalidatori di campi e le funzioni utilizzate dai mutator, nonché funzioni che forniscono un comportamento personalizzato, come una funzione che imposta i valori dei campi da dati esterni, come la corrente posizione del braccio di un robot.

Potrebbe essere possibile riutilizzare queste funzioni in più blocchi.

Utilizzare un campo a discesa

Se hai un insieme di blocchi sostanzialmente uguali, tranne per un operatore, potresti essere in grado di progettare un singolo blocco con un campo a discesa per l'operatore. Ad esempio:

  • Il blocco logic_operation integrato utilizza un menu a discesa con gli operatori and e or.
  • Il blocco math_arithmetic integrato utilizza un menu a discesa con gli operatori +, -, ×, ÷ e ^.

La scrittura di generatori di codice per questi blocchi è in genere un po' più complessa, ma comunque più facile rispetto alla scrittura e alla gestione di più blocchi.

Utilizzare un mutatore

Se hai un insieme di blocchi che rappresentano varianti diverse della stessa struttura di programmazione, potresti essere in grado di creare un singolo blocco che utilizza un mutatore. Ad esempio, il blocco controls_if integrato può rappresentare più varianti di istruzioni if-then-else.