DataController

A differenza del dataFormula, un dataController non restituisce un valore ma esegue un blocco di codice Javascript.

Vediamo alcuni esempi di uso di dataController.

All'accettazione del valore al path '^.bytes', scatta il dataController che va a settare al path '.conv' il risultato. Notiamo l'uso della macro SET per mettere il valore calcolato nel path desiderato.

Useremo questo esempio per vedere alcune tecniche di cui non abbiamo parlato e nella spiegazione del codice introdurremo alcuni concetti nuovi.

Iniziamo dalla prima linea di codice nella quale usiamo il metodo css per definire una regola di css. Come nel caso di data questo metodo non genera un sourceNode corrispondente ma si limita a trasportare sul client una regola di css. I css possono essere definiti in file separati ma in alcuni casi può essere comodo definire delle regole css localmente usando appunto il metodo css come qui riportato:

root.css('.cellnumber',"""margin:1px; text-align:center;
                       height:18px;line-height:18px;
                       font-size:14px; font-family:courier;
                       color:#666; width:38px;
                       display:inline-block; background:#eaeaea;""")

Definiamo poi un borderContainer e un top dove mettere il titolo e il campo per richiedere il limite nel calcolo dei numeri primi:

bc=root.borderContainer(datapath='eratosthenes',margin='10px',
                                        border='1px solid silver',rounded=12)
top=bc.contentPane(region='top',border_bottom='1px solid silver')
top.div('Sieve of Eratosthenes',text_align='center',font_size='24px',color='#888')
fb = top.formbuilder(cols=1)

Mettiamo ora un numberTextBox con opportune validazioni per evitare che il limite sia superiore a 1000 dato che il tempo di calcolo potrebbe altrimenti risultare eccessivo.

Provvediamo poi a mettere un div nel quale andrà costruito il crivello:

center=bc.contentPane(region='center')
sieve=center.div(width='400px',margin='auto',
        margin_top='10px',margin_bottom='10px')

Per questo esempio utilizzeremo una tecnica alquanto inconsueta per creare il crivello in piccoli step, come a dare l'illusione di una costruzione animata. La prima fase sarà la creazione del crivello in base al numero massimo digitato. Per costruire il crivello useremo la costruzione lato client, ovvero la capacità che abbiamo di manipolare il sourcestore direttamente da javascript inserendo, togliendo o modificando elementi.

Vedremo inoltre come sia possibile definire un parametro _timing per un dataController (ma anche per un dataFormula e per un dataRpc), che provvede ad eseguire la chiamata in modo automatico ad un intervallo di tempo prefissato.

Definiamo quindi il timer per il controller che provvederà a costruire il crivello e per quello che dovrà poi procedere al calcolo:

center.data('.build_timer',0)
center.data('.calculate_timer',0)

Veniamo ora al primo dataController che si occupa di disegnare il crivello:

center.dataController("""var content=sieve.getValue();
                         if (!_timing){
                               sieve.replaceContent(genro.src.newRoot());
                               sieve._('div',{innerHTML:' ',_class:'cellnumber notvisited'})
                               SET .build_timer=0.01
                           }else{
                               n= content.len()+1
                               if(n<=nmax){
                                   sieve._('div',{innerHTML:n,_class:'cellnumber notvisited'})
                               }else{
                                   SET .build_timer=0
                                   SET .curr_n=2
                                   SET .calculate_timer=0.1
                               }
                            }""",nmax='^.nmax',sieve=sieve,_timing='^.build_timer')

Esaminiamo per prima cosa i parametri passati al dataController. Notiamo per prima cosa il parametro nmax che riceve il suo valore al path '^.nmax' e che quindi fa scattare il dataController ogni volta che tale valore cambia. Vediamo poi il parametro sieve=sieve che in pratica trasporta in modo automatico la referenza lato python sul client e quindi rende disponibile sul client un modo di usare in javascript la referenza preparata sul server. Come questo possa avvenire è alquanto complesso ed esula dallo scopo del tutorial. In ogni caso all'interno dello script potremo manipolare il div 'sieve' dichiarato in python. Ultimo parametro è`` _timing='^.build_timer'`` . Dal momento che nel path .build_timer avevamo messo il valore 0 il timer è inizialmente disattivato.

Nel momento in cui digitiamo un valore per il numero limite il controller scatta una prima volta ed esegue

var content=sieve.getValue();
if (!_timing){
      sieve.replaceContent(genro.src.newRoot());
      sieve._('div',{innerHTML:'&nbsp;',_class:'cellnumber'})
      SET .build_timer=0.01
}

La variabile sieve che ci arriva è un sourceNode e con getValue() ne prendiamo il contenuto, ovvero gli elementi al suo interno. All'inizio sarà vuota ma se ad esempio, dopo una prima esecuzione, cambieremo il numero digitato, sarà necessario svuotare il crivello precedente. Per farlo creiamo una nuova radice di sorgente e col comando replaceContent, rimpiazziamo il vecchio contenuto con una nuovo elemento vuoto. La funzione '_' di un sourceNode serve ad aggiungere degli elementi con una sintassi del tutto equivalente a quella che useremmo in python:

sieve._('div',{innerHTML:'&nbsp;',_class:'cellnumber'})

è perfettamente identica a:

sieve.div(innerHTML='&nbsp;',_class='cellnumber')

Dal momento che all'inizio la variabile _timing vale 0 provvediamo a svuotare il crivello e a mettere l'elemento 1 che non viene considerato. Inoltre, con SET .build_timer=0.01 attiviamo il timer che provvederà a richiamarci dopo 10 millisecondi.

Alla seconda volta, attivati dal timer, passeremo nell'altro ramo della if:

n= content.len()+1
if(n<=nmax){
   sieve._('div',{innerHTML:n,_class:'cellnumber'})
}else{
   SET .build_timer=0
   SET .curr_n=2
   SET .calculate_timer=0.1
}

In questo caso quindi, leggendo la lunghezza della bag calcoliamo n, ovvero il numero corrente, e se non abbiamo ancora superato il numero massimo creiamo un nuovo div il cuoi contenuto è il numero corrente. Quando invece abbiamo superato il valore massimo spegnamo il timer di costruzione mettendo SET .build_timer=0. Inizializziamo nel datastore il path '.curr_n' al valore di 2 e attiviamo il timer di calcolo con SET .calculate_timer=0.1. Per il calcolo scegliamo un valore di timer di 100 millisecondi per rallentare l'esecuzione e vedere il progressivo riempimento del crivello.

Passiamo ora ad esaminare il dataController successivo:

  if (curr_n<=nmax){
    var nodes=sieve.getValue().getNodes();
    var node=nodes[curr_n-1]
    if (!node._factors) {
       node.setAttribute('_class','cellnumber prime')
       for(n2 = curr_n+curr_n; n2 <= nmax; n2=n2+curr_n) {
          var n_mult=nodes[n2 - 1]
          if(!n_mult._factors){
             n_mult.setAttribute('_class','cellnumber noprime')
             n_mult._factors=[]
          }
          n_mult._factors.push(curr_n)
          n_mult.setAttribute('tip',n_mult._factors.join(','))
       }
    }
    do { curr_n++}
    while(curr_n<=nmax && nodes[curr_n-1]._factors)
    SET .curr_n=curr_n
}
else{
    SET .calculate_timer=0
}

Questo secondo dataController riceve la bag del crivello (che ricordiamo è nel sourcestore) nella variabile sieve. Riceve poi il valore corrente che trova nel datastore al path '=.curr_n'. Il controller viene invocato da _timing che ha ricevuto un valore al termine della fase di costruzione.

Se il valore corrente (curr_n) è inferiore al massimo prendiamo il nodo corrispondente e se non è ancora stato scartato (ovvero se non ha _factors) è un numero primo. Pertanto ne settiamo l'attributo _class in modo da mostrarlo come numero primo.

Procediamo poi a calcolarne tutti i multipli fino al numero massimo e a mettere loro una classe che li identifichi come non primi. Durante lo stesso passaggio andiamo a definire sul nodo una lista _factors che conterrà tutti i fattori primi del numero. Inoltre settiamo l'attributo tip in modo che lasciando qualche istante il mouse su un numero non primo vengano mostrati i suoi fattori primi.

Terminato questo passaggio andiamo a trovare il primo nodo dopo quello corrente che non sia stato marcato come non primo, ovvero che non abbia _factors.

Dopo essere stata richiamata nmax volte la funzione ha terminato il suo compito e procede a mettere a 0 il suo timer.

A questo punto, mettendo un nuovo valore massimo, il meccanismo riparte svuotando il crivello precedente e ripetendo i passaggi visti.

Questo esempio, oltre a mostrare dei dataController piuttosto complessi, ha manipolato prevalentemente il sourcestore mostrando come si possa intervenire su di esso lato client. In un prossimo capitolo esamineremo meglio questa tecnica, mostrando come alcuni problemi di interfaccia possano facilmente essere superati in questo modo.