Come aggiungere un pizzico di AJAX a z3c.form in modo sporco e funzionale

Il supporto AJAX di z3c.form è scarno.. ma a volte è facile aggiungerlo, con un piccolo trucco.

Aggiungere un po' di fluidità in una form contenente delle subform dinamiche (aggiunte e rimosse dall'utente) in z3c.form si può, e senza dover mettere mano al basso livello (a costo di accettare qualche "inefficienza"..)

Ma prima un po' di "contesto"

Plone è il nostro mondo, e dobbiamo gestire dei form, come fare?

  • possiamo creare dei content type con Archetypes, e affidarci alla sua libreria di widget molto ricca

  • possiamo usare PloneformGen e le sue logiche per configuratori

  • possiamo usare z3c.form, e sfruttare al massimo potenza e flessibilità di questa libreria

Scelgo Archetypes solo se ho bisogno di manipolare dei contenuti (in un prossimo post vedremo quando conviene pensare che un'informazione sia un "contenuto" e quando no.. lasciate un commento se vi serve una tale spiegazione :)).

PloneFormGen tendo a lasciarlo ai configuratori puri, perchè pone a priori troppi limiti dal punto di vista del processo di sviluppo: anche su questo potrò tornare in seguito, se qualcuno è interessato.

z3c.form va benissimo nella maggior parte dei miei casi, ma si tratta di una libreria in evoluzione, e non ancora "completa" per come potrebbe esserlo.

Il caso specifico: Liste di Form

Da alcune versioni a questa parte una prerogativa di z3c.form è quella di consentire la gestione di campi "List" in cui l'elemento è un generico "Object" con un suo schema (che rappresenta di fatto una sub-form). 

Che significa e a cosa serve tutto ciò?

Ci hanno chiesto di creare un form con cui raccogliere dati relativi ad un utente: a un certo punto nel wireframe gli si fanno inserire i suoi numeri di telefono in modo dinamico, in particolare per ogni numero si permette anche di specificare con un select box se si tratta di un cellulare, di un fax, di un numero personale..

Gestire questa cosa è facile come:

 (1) definire lo schema per il singolo numero di telefono:
from zope.interface import Interface
from zope import schema

class IPhone(Interface):

  type = schema.Choice(title=u'tipo',
                required=True,
                values=(u'ufficio', u'casa', u'cellulare'))
                phone = schema.TextLine(title=u'numero',
                required=True)

(2) creare l'oggetto che faccia da supporto a tale struttura dati:

from zope.interface import implements
from zope.schema.fieldproperty import FieldProperty
from z3c.form.object import registerFactoryAdapter

class Phone(object):

    type = FieldProperty(IPhone['type'])
    phone = FieldProperty(IPhone['phone'])

    def __getitem__(self, key):
        return self.__dict__[key]

registerFactoryAdapter(IPhone, Phone)

(3) incastonare questo comportamento nell'attributo del form padre

class IUserData(Interface):

    name = schema.TextLine(title=u'nome',
                           required=True)
    surname = schema.TextLine(title=u'cognome',
                           required=True)
    phones = schema.List(title=u'telefono',
                         required=False,
                         value_type=schema.Object(schema=IPhone))
    address = schema.TextLine(title=indirizzo',
                           required=True)
    [...]

assegnando l'interfaccia IUserData ad una vista z3c.form, il wireframe magicamente prenderà vita, a patto di adottare una versione dalla 2.0 in su! (cfr. changelog su http://pypi.python.org/pypi/z3c.form)

Mettiamoci un pizzico di JQuery!

z3c.form dà vita allo schema nel modo migliore, facendo il submit dell'intera form ad ogni clic sui pulsanti "aggiungi" ed "elimina selezionati". In questo modo, il caso in cui javascript non è disponibile è perfettamente gestito.

Se lo schema della form è minimale come quello proposto, tutto sommato problemi non ne avremo, ma come fare se la form principale è carica di informazioni?

La soluzione più corretta chiederebbe di inserire le logiche necessarie nel widget che adatta il nostro campo "phones", ma io cercavo una soluzione non invasiva, e soprattutto "rapida"(c).

Alla fine san jquery mi ha salvato, con una piccola aggiunta al template (ho marcato il tag html che ospita il rendering del widget per "phones" con l'id "phones_wdg") e questo pezzetto di codice benedetto:

jq(document).ready(function() {
  
    function replace(event) {
        event.preventDefault();
        event.stopPropagation();    

        var wdg = event.data.wdg
        var field = event.data.field
        var submit_type = event.data.submit_type
        var btn = 'form.widgets.' + field + '.buttons.' + submit_type
        var btn_id_add = '#form-widgets-' + field + '-buttons-add'
        var btn_id_remove = '#form-widgets-' + field + '-buttons-remove'
        var params = jq('form').serializeArray();
        params.push({'name': btn, 'value': 'ajax'});
        jq.post(jq('form').attr('action'),
             params,
             function(data){
                 jq(wdg).html((jq(wdg, data).html()));
                 jq(btn_id_add).bind('click', 
                                 {field: field,
                                  submit_type: 'add',
                                  wdg: wdg},
                                  replace);
                 jq(btn_id_remove).bind('click', 
                                 {field: field,
                                  submit_type: 'remove',
                                  wdg: wdg},
                                  replace);
             });
       };

     jq('#form-widgets-phones-buttons-add').bind('click',
            {field: 'phones',
             submit_type: 'add',
             wdg: '#phones_wdg'},
             replace);
     jq('#form-widgets-phones-buttons-remove').bind('click',
            {field: 'phones',
             submit_type: 'remove',
             wdg: '#phones_wdg'},
             replace);
});

Il trucco jedi è presto detto: al clic sui pulsanti per aggiungere/togliere i subform, faccio un post "trasparente" al server, e sostituisco al widget sulla pagina quello che mi sta rimandando il server in risposta al post. Sicuramente inefficiente, ma piuttosto efficace in questo caso..

Nell'attesa che tali logiche finiscano più correttamente nel pacchetto base di z3c.form (qualcuno si offre volontario?! ;)), buon jquery a tutti!

Share this on

Share |

On same topics

Commenti

comments powered by Disqus