Blog

Come realizzare un component Python in Genropy

Come realizzare un component Python in Genropy

In alcuni casi può capitare di voler personalizzare il comportamento di alcuni widget o component “core” di Genropy, oppure creare veri e propri blocchi o interfacce personalizzate da zero. Prendiamo ad esempio il caso di una form particolarmente complessa, che vogliamo ripetere uguale (o quasi) su più pagine.

È sempre buona norma rendere il nostro codice il più possibile riutilizzabile, flessibile e generico: piuttosto che scrivere quindi righe e righe di codice ogni volta, ci converrà utilizzare i component di Genropy, che ci permettono di scrivere un blocco di codice in un punto dell’applicativo e richiamarlo comodamente al bisogno. Un component può essere molto semplice o anche estremamente complesso: si pensi ad esempio al component core per eccellenza di Genropy, il TableHandler, grazie al quale con una sola riga di codice è possibile mostrare la particolare visualizzazione dei record di una tabella.

Il component può inoltre essere reso parametrico a piacimento, rendendolo estremamente flessibile allo specifico caso d’uso e modificando quindi di volta in volta anche sensibilmente ciò che viene visualizzato.

Vediamo quindi come è possibile costruire un component Python in Genropy sfruttandone al massimo le potenzialità.

Prendiamo a titolo di esempio un caso molto semplice: vogliamo personalizzare una simpleTextArea al fine di visualizzare i caratteri rimasti a disposizione rispetto a una lunghezza massima predefinita, e troncare i caratteri qualora questa lunghezza venga superata.

In questo caso potrebbe essere sufficiente aggiungere alla textArea il parametro connect_onkeyup per modificarne il comportamento alla digitazione dei caratteri, e aggiungere poi una breve riga di testo che mostri quello che succede in tempo reale:

def test_0_textarea(self, pane): 
    fb = pane.formbuilder(cols=1) 
    fb.textarea(value='^.text_msg', lbl='My text', height='50px', width='200px', connect_onkeyup="""var tgt = $1.target; 
                   var my_text = tgt.value; 
                   var remaining = 30 - my_text.length; 
                   SET .rem = remaining; 
                   SET .clr = (remaining<10)?'red':'grey'; 
                   if(remaining<3){ genro.playSound('ping') }; 
                   if(remaining<0){ tgt.value = my_text.slice(0,30) };""") 
    last_line = fb.div(font_style='italic', font_size='8pt') 
    last_line.span('Remaining: ') 
    last_line.span('^.rem', color='^.clr')

Questa soluzione ovviamente funziona, ma come anticipavamo è piuttosto inefficiente. Ipotizziamo per esempio di voler usare questa textArea in più punti della nostra form, o in più pagine: dovremo ripetere tutte queste righe di codice ogni volta? E se volessimo effettuare una modifica, per esempio al colore o al suono utilizzato, dovremo fare questa modifica in ogni singolo punto? Questa strada, insomma, funziona, ma è poco percorribile e densa di insidie. Genropy ci offre una soluzione molto più efficiente al problema, attraverso la creazione di un component.

Procediamo quindi con la creazione di una risorsa Python, un file .py che andiamo quindi a collocare nella cartella resources del nostro package, e dove andiamo a definire una classe MsgArea, un metodo messageArea, al cui interno di fatto incolliamo quanto fatto in precedenza (con qualche modifica):

from gnr.web.gnrbaseclasses import BaseComponent

class MsgArea(BaseComponent):
    def messageArea(self, parent, value=None, height=None, width=None, **kwargs): 
        box = parent.div(**kwargs) 
        box.textarea(value=value, height=height, width=width, connect_onkeyup="""var tgt = $1.target; 
                   var my_text = tgt.value; 
                   var remaining = 30 - my_text.length; 
                   SET .rem = remaining; 
                   SET .clr = (remaining<10)?'red':'grey'; 
                   if(remaining<3){ genro.playSound(sound) }; 
                   if(remaining<0){ tgt.value = my_text.slice(0,30) };""") 
        last_line = box.div(font_style='italic', font_size='8pt')
        last_line.span('Remaining: ') 
        last_line.span('^.rem', color='^.clr')
        return box

Le modifiche che facciamo riguardano il contenitore (creiamo infatti una box, che appendiamo al contenitore parent che riceveremo quando chiameremo questo metodo), il valore, l’altezza e la larghezza, che non saranno “fissi” ma parametrici (anch’essi ricevuti dal chiamante). Per convenzione facciamo poi ritornare al metodo la box che abbiamo costruito.

Il test che avevamo costruito si semplifica notevolmente diventando come segue:

class GnrCustomWebPage(object):
    py_requires = "gnrcomponents/testhandler:TestHandlerBase,msgarea_component:MsgArea"

    def test_1_msgarea(self, pane): 
        fb = pane.formbuilder(cols=1)   
        self.messageArea(fb, value='^.text_msg', lbl='My text', height='50px', width='200px')

Vediamo quindi che dopo aver “importato” il component nel py_requires, la chiamata è davvero breve (una sola riga), e riguarda il contenitore fb e i parametri che abbiamo esplicitato.

Come rendere il component più flessibile e sensibile ai parametri

Ma se volessimo a questo punto cambiare il colore, il suono, o la lunghezza massima, dovremmo per forza andare a modificare a mano il codice del component? Guardando ciò che abbiamo fatto con valore, altezza e larghezza, intuiamo che la risposta è no: è possibile infatti rendere il component infinitamente flessibile, rendendolo sensibile a molti parametri, così da garantirci un comportamento differente semplicemente dandogli in pasto un parametro piuttosto che un altro.

Modifichiamo quindi il nostro component come segue:

def messageArea(self, parent, value=None, height=None, width=None, max_len=None,
    sound=None, color_ok=None, color_warning=None, **kwargs): 
    box = parent.div(_workspace=True, **kwargs) 
    box.textarea(value=value, height=height, width=width, _ml=max_len, _sd=sound, _clrok=color_ok, _clrwg=color_warning, 
    connect_onkeyup="""var tgt = $1.target; 
                  var my_text = tgt.value; 
                  var currattr = this.currentAttributes(); 
                  var max_len = currattr._ml || 80; 
                  var sound = currattr._sd || 'ping'; 
                  var color_ok = currattr._clrok || 'grey'; 
                  var color_wg = currattr._clrwg || 'red'; 
                  var remaining = max_len - my_text.length; 
                  SET #WORKSPACE.rem = remaining; 
                  SET #WORKSPACE.clr = (remaining<max_len/10)?color_wg:color_ok;
                  if(remaining<3){ genro.playSound(sound) }; 
                  if(remaining<0){ tgt.value = my_text.slice(0,max_len) };""")
    last_line = box.div(font_style='italic', font_size='8pt')
    last_line.span('Remaining: ') last_line.span('^#WORKSPACE.rem', color='^#WORKSPACE.clr')
    return box

Si noti che introduciamo anche il concetto di workspace, un attributo che passiamo al contenitore box che ci permette di creare uno spazio di lavoro temporaneo in cui salvare i valori che ci servono solo come variabili di sistema e non vogliamo conservare nello spazio di lavoro del test (o, se siamo in una Form, del record).

Il test invece si modificherà come segue:

def test_1_msgarea(self, pane): 
    fb = pane.formbuilder(cols=1) 
    self.messageArea(fb, value='^.text_msg', lbl='My text', height='50px', width='200px', max_len=40, color_ok='green', color_warning='orange', sound='frog')

Si intuisce quindi come sia semplice passare al component originario infiniti parametri e renderlo estremamente dinamico.

Come semplificare ulteriormente la sintassi con lo struct_method

Ci rendiamo a questo punto conto che la chiamata al component non dà tuttavia allo stesso una vera e propria “identità” all’oggetto: è infatti una normale chiamata a un metodo in cui vengono passati come parametri un contenitore e una serie di altri attributi. Sappiamo però che molti component di Genropy usano una sintassi più semplice e che dona più concretezza all’oggetto, ad esempio:

pane.inlineTableHandler(relation='@righe',viewResource='ViewFromFattura',
     picker='prodotto_id', picker_structure_field='prodotto_tipo_id')

Questa sintassi pare più semplice della nostra, non si passa un contenitore ma vi si appende direttamente l’oggetto, e sembra più intuitiva sia per noi che dobbiamo usarla in più punti dell’applicativo, sia per eventuali altri sviluppatori che dovessero condividere con noi il progetto. Abbiamo quindi modo anche noi di ottenere un risultato simile?

La risposta ovviamente è sì, sfruttando il concetto di struct_method. Modifichiamo quindi il component come segue:

from gnr.web.gnrwebstruct import struct_method

class MsgArea(BaseComponent):
    @struct_method 
    def prefix_messageArea(self, parent, value=None, height=None, width=None, max_len=None, 
        sound=None, color_ok=None, color_warning=None, **kwargs):

Oltre a importare il decoratore dovremo avere l’accortezza di utilizzare un prefisso a piacere, che in questo caso chiamiamo genericamente “prefix“. A questo punto potremo richiamare il component nel nostro test con la sintassi seguente:

def test_1_msgarea(self, pane): 
    fb = pane.formbuilder(cols=1) 
    fb.messageArea(value='^.text_msg', lbl='My text', height='50px', width='200px', max_len=40, 
         color_ok='green', color_warning='orange', sound='frog')

Abbiamo insomma visto come sia possibile sfruttare il concetto di component per richiamare blocchi di codice all’interno di un applicativo, e come sia semplice costruirne uno. È inoltre possibile renderlo dinamico a piacimento e semplificarne la sintassi grazie allo struct_method.

Ti interessa saperne di più o vuoi raccontarci la tua? Condividi la tua esperienza con noi su Telegram!


Ti stai avvicinando al mondo Genropy e desideri saperne di più? Seguici sui social per tenerti in contatto con le ultime novità che Genropy ha da offrire:

Genropy Seguici su Facebook
Pagina Facebook

 

Genropy Seguici Gruppo Facebook
Gruppo Facebook

 

Genropy Gruppo Telegram
Gruppo Telegram