• Non ci sono risultati.

Questa parte del lavoro è avvenuta con due diverse modalità: creazione dei blocchi di utilità, non dipendenti quindi dall’installazione in uso, e creazione dei blocchi relativi al contesto, quindi di tipo trigger e azione. A loro volta i blocchi di utilità si possono suddividere in “statici”, ovvero il cui comportamento non è collegato ad eventi nel workspace, e “dinamici”, che possono invece variare forma e funzionalità.

Per specificare il prototipo dei blocchi bisogna definire la funzione associata alla proprietà “init”. Sarà chiamata ogni volta che viene sollevato un evento “create” relativo a quel blocco. I blocchi “statici” sono stati definiti usando la sintassi JSON, come nell’esempio riportato.

Blockly.Blocks['condition'] = { init: function () {

this.jsonInit({

"type": "block_type",

"message0": "If (condition) %1", "args0": [ { "type": "field_image", "src": "https://giove.isti.cnr.it/demo/pat/src/img/condition.jpeg", "width": 25, "height": 25,

"alt": "condition icon", "flipRtl": false

} ],

"tooltip": "Extended state in a time frame", "helpUrl": "" }); this.setMovable(false); } };

Code snippet 1: definizione di un blocco statico tramite il metodo jsonInit. Essendo un blocco accessorio (tipo “condizione” che si applica al trigger) è stato reso non movibile, per impedire “smontaggi” accidentali della struttura.

Tutti i blocchi di questo tipo sono stati creati (o importati per quanto riguarda quelli creati precedentemente nella fase di design tramite Blockly Developer Tools) nel modulo staticBlocks. I blocchi personalizzati sono così aggiunti all’oggetto Blocks, proprietà del namespace Blockly che si occupa di effettuare la mappatura tra i nomi dei blocchi e i prototipi degli oggetti relativi. E’ possibile così usare i blocchi, aggiungendoli alla Toolbox oppure chiamando direttamente la funzione newBlock della classe workspace.  

let newTree = `

<xml id="toolbox" style="display: none"> <category colour="0" name="Rule blocks"> <block type="rule"></block>

</category>

<category id="trigger_list" colour="#065699" name="Trigger list"> ${triggersXml}

</category>

<category name="Trigger operators" colour="210"> <block type="and"></block>

<block type="or"></block> <block type="group"></block> </category>

<category name="Action list" colour="#069975"> ${actionsXml}

</category>

<category name="Action operators" colour="150"> <block type="parallel_dynamic"></block> </category>

</xml> ` ;

Code snippet 2: ridefinizione della toolbox (usando template literals) una volta creati dinamicamente i blocchi trigger e action.

Ad ogni blocco deve essere associata una funzione di generazione che ha lo scopo di creare il codice associato al blocco. Inizialmente questo è stato usato per generare tramite JavaScript la descrizione JSON associato al blocco e per visualizzarla nell’interfaccia. In questo modo poteva essere direttamente salvata ed inviata Rule Manager.

Questo si è rivelato problematico, perché il formato della regola non è una semplice lista di elementi ma una struttura annidata. La descrizione della regola in formato JSON non era realizzabile basandosi unicamente sommando le informazioni relative ad ogni blocco, ma ad ogni modifica nel workspace era necessario rileggere ed eventualmente modificare tutta la struttura. Per questo non è stato generato direttamente codice dai blocchi, ma sono stati usati come formato di interscambio. In questo modo è possibile usare le funzioni di Blockly per gestire la struttura ricorsiva della regola e per convertire il contenuto del workspace in formato XML per i salvataggi. La conversione da blocchi a JSON avviene solamente quando si vuole salvare o inviare la regola al Rule Manager, invece di doverla eseguire ogni volta che avviene un cambiamento nel workspace.

Per quanto riguarda la creazione dei blocchi di utilità dinamici è stato invece deciso, data la maggiore complessità, di usare il formato JavaScript.

Per permettere comportamenti dinamici è necessario che nei blocchi siano definiti:

● il campo init, che deve contenere la funzione che sarà chiamata ogni volta che un evento riguardante il blocco viene sollevato, e che contiene ad esempio le operazioni per il salvataggio delle informazioni ottenute dall’evento, come la modifica di un campo in quel blocco. All’interno di questa funzione può avvenire direttamente la chiamata alla funzione updateShape, passando ad essa i nuovi dati, oppure può, sempre all’interno di essa, essere invece definito un campo dinamico, come appunto una checkbox, contenente una callback alla funzione updateShape eseguita ad ogni modifica di quel campo;

● il campo updateShape_, che contiene la funzione che opera sul blocco effettuando le modifiche relative ai nuovi dati ricevuti;

● eventualmente il campo mutationToDom, contenente una funzione anch’essa chiamata ad ogni evento riguardante il blocco è che ha lo scopo di usare i nuovi dati per modificare l’XML relativo al blocco, registrando così le eventuali mutazioni, in modo che se il blocco viene es. duplicato o richiamato dal database, la funzione init userà i dati salvati nell’XML per ricrearlo, evitando che vengano usati i dati di default.

● eventualmente il campo domToMutation, che serve invece, ad ogni evento relativo, a usare i dati salvati nell’xml del blocco per leggere l’elemento relativo alla mutazione nell’XML e riconfigurare il blocco in base a esso.

Essi sono stati definiti ad esempio nel blocco “not_dynamic”, che nella funzione init inizializza una checkbox con callback alla funzione updateShape, che serve a disegnare o cancellare dal blocco i campi aggiuntivi relativi ai vincoli temporali, che vengono aggiunti quando viene spuntata la checkBox “when”, nella funzione mutationToDom salva in un container XML lo stato della checkbox, e in domToMutation legge il valore salvato e richiama updateShape passandogli il valore.

mutationToDom: function () {

let container = document.createElement('mutation');

let whenInput = (this.getFieldValue('when_input') == 'TRUE'); container.setAttribute('when_input', whenInput);

return container; },

domToMutation: function (xmlElement) {

let hasTimeInput = (xmlElement.getAttribute('when_input') == 'true');

// Updateshape è una helper function: non deve essere chiamata direttamente ma // tramite domToMutation, altrimenti non viene registrato che il numero di // inputs è stato modificato

this.updateShape_(hasTimeInput); },

Immagine 11: esempio del comportamento del blocco “not_dynamic”.

Per i comportamenti complessi è stato deciso di appoggiarsi ai listeners. Un esempio è il comportamento del blocco “parallel_dynamic”. E’ possibile definire tramite una input il numero di rami paralleli che saranno presenti nel blocco, che di conseguenza modifica il numero di possibili collegamenti per poter inserire blocchi azione. Sono presenti dei blocchi segnaposto tra il blocco contenitore “parallel_dynamic” e i blocchi azione da inserire.

E’ stato necessario inserirli perché nella libreria non era possibile definire il comportamento richiesto da questo blocco, ovvero collegare più di un blocco all’uscita “nextBlock” del blocco precedente. Utilizzando però dei blocchi segnaposto, che presentino sia il collegamento ad un input (assente nei blocchi di tipo azione), sia un collegamento verso il prossimo blocco, è stato possibile ottenere il risultato desiderato senza modificare la coerenza dei blocchi (senza aggiungere quindi ulteriori input ai blocchi di tipo azione, complicando inutilmente la presentazione dell’interfaccia). Tramite un listener è stato possibile invece ascoltare ogni evento relativo al blocco “parallel_dynamic” e aggiornare di conseguenza il numero di segnaposto ad esso collegati.

La creazione e l’aggiunta alla toolbox dei blocchi dinamici, letti da file esterno, avviene in modo diverso. Data la natura delle operazioni di recupero dei trigger e delle azioni, esse devono avvenire in modo asincrono. Dopo il recupero viene creato ricorsivamente l’albero dei trigger, aggiungendo alla toolbox la struttura XML appropriata (categoria top-level, categoria middle-level, attributo).

Se si tratta di un attributo viene chiamata la funzione che si occupa della effettiva creazione del blocco, passandogli i dati letti dal contesto. Per creazione si intende aggiungere all’oggetto Blockly.blocks un nuovo oggetto relativo al blocco che stiamo definendo, e usare la funzione init per descriverne l’aspetto e il comportamento. Tutto questo avviene in modo simile anche per quanto riguarda le azioni.

/**

* Funzione asincrona: aspetta il caricamento dei trigger, chiama funct per * creare l'albero dei trigger e ridisegnare la toolbox

*/

async function waitForTriggers() {

myTriggers = await Init.loadTriggersAsync();

const triggerWithCategory = addCategoryToAttributeTrigger(myTriggers); loadTriggersToolboxRecursive(triggerWithCategory);

rebuildToolbox(); }

Code snippet 4: funzione per il caricamento dell’albero dei trigger e ricostruzione della Toolbox

4.4 Controlli e suggerimenti

Documenti correlati