Source e Data

Come precedentemente accennato, una pagina è un’applicazione web che si basa su due store, uno per la struttura ed uno per i dati. Vediamo nel dettaglio questo meccanismo.

Inizializzazione di una pagina

Abbiamo visto precedentemente che il contenuto della pagina è inviato dal server durante la chiamata main sotto forma di un XML che rappresenta la Bag della struttura della pagina.

Prendiamo in esame il seguente codice:

def main(self,root,**kwargs):
root.data('foo.bar','Hello World')
bc=root.borderContainer(border='1px solid silver')
top=bc.contentPane(region ='top', height='100px',
                   background='blue')
center=bc.contentPane(region ='center',background='yellow')
center.div('^foo.bar',background='green',text_align='center',
           padding='5px',width='100px',color='white')

Questo codice invierà questo XML:

<GenRoBag>
   <resultType>node</resultType>
   <result __cls="domsource">
      <data_0 path="foo.bar" tag="data" strippedKwargs="__cls">Hello World</data_0>
      <BorderContainer_1 tag="BorderContainer" border="1px solid silver">
           <ContentPane_0 _T="BAG" region="top" tag="ContentPane" background="blue" height="100px" />
           <ContentPane_1 region="center" tag="ContentPane" background="yellow">
                 <div_0 _T="BAG" color="white" text_align="center" innerHTML="^foo.bar"
                 padding="5px" width="100px" tag="div" background="green" />
           </ContentPane_1>
      </BorderContainer_1>
   </result>
</GenRoBag>

che genererà sul client un sourcestore ed un datastore, che il builder tradurrà nei domNode e widget necessari.

../_images/00_stores.png

Consideriamo ora i sourceNode (che nella figura sono bordati di rosso). Ogni sourceNode, che è nodo di Bag, contiene negli attributi i parametri che abbiamo messo nel codice. Nel caso particolare, ad esempio:

center.div('^foo.bar',background='green',text_align='center',
                  padding='5px',width='100px',color='white')

Se variamo uno degli attributi il builder adeguerà immediatamente il contenuto del DOM per tenere conto della nostra modifica.

Unica eccezione è l’istruzione:

root.data('foo.bar','Hello World')

Questo comando, invece di costruire un sourceNode, costruisce un dataNode nel datastore. Il comando data è l’unico che agisce in questo modo e serve a inviare al client, insieme ai widget, e agli altri elementi, dei dati iniziali.

La pagina in uso

Una volta creata la pagina nella memoria del browser, dall’interazione con l’utente e/o con chiamate di rete il contenuto degli store si modifica e quindi viene modificato il DOM della pagina per visualizzare opportunamente i cambiamenti.

La prima interazione ad esempio è quella di un utente che in un campo “Nome” digita ad esempio “John”.

Una volta uscito dal campo (dopo aver passato le validazioni eventualmente presenti), il valore “John” è messo dal sistema al path destinato a contenerlo. Il fatto che venga messo questo valore fa sì che la bag dei dati provveda a notificare il cambiamento ai sourceNode che hanno sottoscritto il cambiamento al path 'record.name'. In questo caso sia il textBox che il div.

Durante la notifica al sourceNode vengono mandate molte informazioni tra cui il valore precedente, il nuovo valore e la reason.

La reason è un parametro che può essere aggiunto mentre si setta un valore e in questo caso, uscendo dal textBox, il sistema cambia il valore nello store dei dati ma, nel farlo, aggiunge come reason il sourceNode del widget che ha generato il cambiamento.

Il sistema che governa i trigger, quando riceve la notifica di un cambiamento ad un path, esamina i nodi che hanno sottoscritto quel path e invia loro la notifica solo se la reason della notifica stessa non è il medesimo sourceNode.

Questo meccanismo evita la retroazione delle notifiche di cambiamento.

Set e get relativi

Usando le bag lato client abbiamo a disposizione molti modi.

Il datastore non è altro che una bag che troviamo a 'genro._data' e quindi potremmo scrivere:

genro._data.setItem('record.name','Carol')
var name=genro._data.getItem('record.name')

In realtà si usa più spesso un metodo di genro che fa la stessa cosa ma nascondendo l’indirizzo del datastore:

genro.setData('record.name','Bart')
var name=genro.getData('record.name')

In entrambi i casi (che possiamo testare con i primi 4 bottoni), usiamo un path assoluto. In genropy si utilizzano però molto spesso dei path relativi perchè in questo modo è possibile scrivere codice riusabile.

Ovviamente per usare un path relativo devo essere nello scope di un sourceNode. Ovvero, il pezzo di codice javascript che eseguo ha in this un ben specifico sourceNode.

Ogni script eseguito nell’ambito di un nodo ha il nodo stesso come scope. Vediamo ad esempio il bottone “Set Relative Frank”:

fb.button('Set Relative Frank', action= "this.setRelativeData('.name','Frank')")
fb.button('Set Relative',action="alert(this.getRelativeData('.name','Frank'))")

Dal momento che la action del bottone è eseguita nello scope del sourceNode del bottone stesso, possiamo usare il metodo setRelativeData che un sourceNode rende disponibile per assegnare dei valori in path relativi al sourceNode stesso. In questo caso il bottone è in un formbuilder situato in un contentPane che ha come datapath “record”.

Quindi lo script del bottone, quando chiediamo di fare una setRelativeData, prende il datapath corrente (in questo caso “record”), aggiunge il path voluto (in questo caso “.name”), e provvede a settare il valore. Se dovessimo duplicare questo bottone in un altro punto con un path diverso, il risultato dell’azione sarebbe sempre e comunque di mettere un valore al path “.name” relativo al datapath corrente del bottone stesso.

Identicamente per il caso del bottone “Get Relative” che legge un valore con il metodo di sourceNode getRelativeData.

Un altro modo di leggere e scrivere valori relativi è l’utilizzo della macro GET e SET. Le macro non sono altro che un modo compatto per scrivere le istruzioni getRelativeData e setRelativeData. Vediamo nel nostro esempio i due bottoni:

fb.button('Use SET for Mary',action="SET .name='Mary'")
fb.button('Use GET',action="alert (GET .name)")

Scrivere "SET .name='Mary'" oppure "this.setRelativeData('.name','Mary')" è assolutamente identico ma la prima forma è più concisa e leggibile.

Stesso ragionamento vale per la macro GET per leggere un valore.

Gli ultimi due bottoni mostrano invece che possiamo usare il passaggio dei parametri per iniettare delle variabili nello scope del comando eseguito:

fb.button('Get parameter absolute',action='alert (n)',n='=record.name')
fb.button('Get parameter relative',action='alert (n)',n='=.name')

In entrambi i casi aggiungiamo alla chiamata del bottone un parametro 'n' che poniamo uguale ad un path (assoluto o relativo), prefissato dal simbolo “=”. Ricordiamo che il simbolo “=” è un puntatore “lazy” e quindi legge il valore al path desiderato ma non viene richiamato dai trigger di cambiamento del valore. Torneremo più avanti su questo concetto. Per il momento ci limitiamo a notare che questa sintassi ci consente di avere come variabili, all’interno del metodo che chiamiamo, dei valori letti nel datastore.