DataFormula

Un dataFormula è un elemento attivo della pagina che puo ricevere vari parametri sia come costanti che come riferimenti (path attivi e passivi) al datastore.

La sua forma generica è:

pane.dataFormula(path, formula, par1=..., par2=...)

Al variare del valore di uno dei parametri la formula viene ricalcolata e il risultato scritto nel datastore al path desiderato.

Per prima cosa creiamo un titolo, un box e un formbuilder:

def main(self,root,**kwargs):
    root.h1('Triangle Area',text_align='center')

    box=root.div(margin='5px',datapath='triangle',
               border='1px solid silver')

    fb = box.formbuilder(cols=3,fld_width='80px')

Definiamo all’interno del formbuilder due numberTextBox che serviranno per inserire base e altezza del triangolo:

fb.numberTextBox('^.base',lbl='Base',default_value=0)
fb.numberTextBox('^.height',lbl='Height',default_value=0)

Mettiamo poi un numberTextBox con attributo readOnly=True che prenderà il proprio valore al path “.area”:

fb.numberTextBox('^.area',lbl='Area',readOnly=True)

Infine mettiamo il dataFormula:

fb.dataFormula('.area','b*h/2', b='^.base', h='^.height')

Da notare che il primo parametro, ovvero il path di destinazione NON ha un simbolo di puntatore mentre sia b che h ricevono il valore da path relativi preceduti dal simbolo ^.

Ogni volta che cambiamo il valore di base o di altezza la formula viene ricalcolata e il valore di area aggiornato.

Iniziamo come sempre dal main dove mettiamo un box con un titolo e un formbuilder:

def main(self,root,**kwargs):
    box=root.div(border='1px solid gray', width='400px',color='#666',
                 datapath='balance',margin='5px',rounded=4)
    box.div('Personal Balance',text_align='center',font_size='24px')

    fb = box.formbuilder(cols=1)

Il primo valore che andiamo a mettere sono le nostre entrate mensili:

fb.numberTextBox('^.income',lbl='Income',width='100px',default_value=0)

Abbiamo poi definito un metodo expensesGroup che ci permette di definire un insieme di voci di spesa e che usiamo 3 volte per altrettanti gruppi:

self.expensesGroup(fb,datapath='.home',title='Home expenses',
                   items='rent,electricity,internet,cleaning,insurance')
self.expensesGroup(fb,datapath='.work',title='Work expenses',
                   items='brushes,canvas,colors')
self.expensesGroup(fb,datapath='.personal',title='Personal expenses',
                   items='food,newspapers,transport,restaurant,cinema')

Mettiamo poi un numberTextBox readOnly per totalizzare le spese e la relativa formula di calcolo:

fb.numberTextBox('^.total_expenses',lbl='Total expenses',width='100px',
                 readOnly=True)

fb.dataFormula('.total_expenses','home+work+personal',
               home='^.home.total',
               work='^.work.total',
               personal='^.personal.total')

Ultimo elemento sarà il saldo e la relativa formula:

fb.numberTextBox('^.balance',lbl='Balance',width='100px',
          background='lightyellow',
          color='^.balance_color',font_weight='bold',
          readOnly=True)

fb.dataFormula('.balance','income-total_expenses',
               income='^.income',
               total_expenses='^.total_expenses')

Notiamo infine un dataFormula usato per calcolare il colore con cui mostrare il saldo:

fb.dataFormula('.balance_color',
                "(balance>100)?'green':'blue'",
                balance='^.balance',
               _if='balance>0', _else="'red'")

Questo dataFormula introduce il parametero _if che offre la possibilità di condizionare la formula e il parametro _else che fornisce l’espressione da usare nel caso la _if non sia soddisfatta.

Vediamo ora invece il metodo expensesGroup che riceve il formbuilder, datapath da usare, un titolo e una lista di voci da chiedere:

def expensesGroup(self,fb,datapath=None,title=None, items=None):

Nel formbuilder ricevuto mettiamo un div destinato a contenere un formbuilder innestato. Il div userà il datapath ricevuto mentre il formbuilder innestato aggiungerà a sua volta un datapath='.detail' per i valori che andremo a digitare:

fb=box.formbuilder(cols=1,datapath='.detail' ,
                      fld_width='50px',lbl_width='100px',
                      border_spacing='2px')

Andiamo ora a prendere le voci dalla lista splittata sulla “,”:

for item in items.split(','):
    fb.numberTextBox(value='^.%s' % item,default_value=0,
                        lbl_font_style='italic',
                        lbl=item.capitalize())

Per ogni voce specifichiamo un path relativo uguale al suo nome e mettiamo una label uguale al nome con la prima lettera maiuscola. Mettiamo anche che il valore di default è 0.

Ora dobbiamo calcolare la somma dei valori digitati e quindi mettiamo:

box.dataFormula('.total','detail.sum()', detail='^.detail')

Da notare che abbiamo posizionato il dataFormula non nel formbuilder (che opera a livello di datapth=”.detail”), ma nel box che invece ha come datapath quello del gruppo di spese. Infatti il nostro dataFormula riceve come parametro detail='^.detail' e quindi all’interno dello script la variabile detail conterrà tutta la bag dei valori del gruppo. Usiamo la funzione sum delle bag per sommare i valori di tutti i nodi della bag detail ricevuta. Da notare che il dataFormula scatterà per qualsiasi cambiamento all’interno del path “.detail” e quindi non appena cambiamo un valore la somma viene eseguita e il risultato messo al path “.total”.

Ora mettiamo un altro elemento nel formbuilder per mostrare il totale:

fb.numberTextBox('^.#parent.total',width='50px',
              background='lightyellow',
              lbl_font_weight='bold',
              readOnly=True,lbl='Total')

Il numberTextBox che mettiamo per mostrare il valore calcolato prende il proprio valore al path '^.#parent.total' . #parent è un elemento di path speciale che procede a ritroso nella gerarchia dello store e quindi, dato che il formbuilder sarebbe al path “.detail”, usando “#parent” ci portiamo a livello superiore dove si trova il valore “.total”.

Lo scopo di questo esempio è di mostrare come usare i path relativi e i dataFormula per costruire degli slider che configurino i colori di un div.

Come al solito partiamo dal main e costruiamo un box che contiene un formbuilder:

def main(self,root,**kwargs):
    box=root.div(border='1px solid gray', width='380px', color='#666',
                 datapath='colormaker',margin='5px',rounded=4)
    box.div('Color Maker',text_align='center',font_size='24px')
    fb = box.formbuilder(cols=4,lblpos='T',lblalign='center')

A differenza dei formbuilder visti fin ora, questa volta usiamo l’attributo lblpos =”T” posiziona le etichette sopra i campi.

Procediamo ora ad inserire nel formbuilder 3 div che definiscono dei datapath relativi e le relative etichette “Background” , “Color” e “Shadow”. Tali div verranno poi passati al metodo self.colorRgb che provvederà a costruire all’interno del div ricevuto i tre slider per variare le componenti RGB:

self.colorRgb(fb.div(datapath='.bkg',lbl='Background'))
self.colorRgb(fb.div(datapath='.color',lbl='Color'))
self.colorRgb(fb.div(datapath='.shadow',lbl='Shadow'))

Infine posizioniamo nel formbuilder un div i cui colori sono definiti dalla posizione dei cursori degli slider:

fb.div('Test', padding='6px',font_size='30px',margin='10px',
               width='100px',height='100px',rounded=8,
               background_color='^.bkg.rgb',
               color='^.color.rgb',
               shadow_color='^.shadow.rgb',
               shadow='4px 4px 8px')

Notiamo che background_color, color e shadow_color sono collegati al valore “.rgb” dei rispettivi path relativi.

Veniamo ora al metodo colorRgb. Costruiamo un altro formbuilder che, come quello visto in precedenza, avrà le etichette posizionate sopra ai valori:

def colorRgb(self,pane):
    fb = pane.formbuilder(cols=3,lblpos='T',lblalign='center',
                      fldalign='center', lbl_font_weight='bold')

Procediamo ora a chiamare 3 volte il metodo colorSlider passando il formbuilder stesso, e la componente di colore:

self.colorSlider(fb, value='^.red', component='red')
self.colorSlider(fb, value='^.green', component='green')
self.colorSlider(fb, value='^.blue', component='blue')

Notiamo che i valori vanno a path relativi '^.red', '^.green' e '^.blue'.

Mettiamo ora un dataFormula che partendo dalle 3 componenti di colore costruisca una stringa del tipo RGB(xx,yy,zz) e la metta al path “.rgb”:

fb.dataFormula('.rgb',"'rgb(+'+red+','+green+','+blue+')'",
               red='^.red',blue='^.blue',green='^.green',
               _onStart=True)

Il parametro `_onStart=True` richiede al sistema di eseguire il calcolo non solo alla variazione dei valori preceduti da ^ ma anche al caricamento della pagina. In questo modo anche al caricamento vengono subito caricati i colori.

Può sembrare superfluo ricordare che in realtà ci sono 9 cursori e 3 dataFormula perchè il metodo colorRgb è chiamato 3 volte.

Resta solo ora da esaminare il metodo colorSlider che costruisce uno slider verticale opportunamente configurato per fornire valori tra 0 e 255 e per avere un valore iniziale random in questo range.

Si noti che i path nel datastore sono su due livelli :

../_images/00_colormaker.png

Grazie ai path relativi è possibile riusare i metodi di costruzione e il codice è molto più compatto e manutenibile.

Per i dettagli sui parametri e sulla sintassi del widget dataFormula si rimanda all’apposita sezione sulla Widgetpedia