Personal tools
Come usare i widget Plone in semplici template HTML

Reverse engineering dei widget Plone: si può!

Dec 19, 2013

Come usare i widget Plone in semplici template HTML

Plone ha alcuni bellissimi widget HTML, ma cosa capita quando vogliamo "rubarli" e utilizzarli in un template? Due esempi (e un invito a tentare voi stessi!)

Nell'uso di tutti i giorni che facciamo del CMS Plone ci troviamo di fronte a una serie di comodi widget.
Se volessimo tentare una definizione di widget nel contesto Plone, potremmo dire "una porzione di HTML atta a definire la raccolta dati di una singola informazione". Se la definizione che ho tentato di dare vi sembra complicare le cose, proviamo ad essere più pratici:

  • per chiedere all'utente il proprio nome, viene usato un widget di tipo stringa che si traduce in un semplice campo input HTML
  • per chiedere una data ci si trova, invece, di fronte a un più complesso insieme di menù a tendina e a un calendario JavaScript, ma lo scopo finale è quello di raccogliere un'informazione di tipo data/ora.

Perché usare sempre gli stessi widget? Perché in questo modo forniamo un'interfaccia utente che diventa famigliare e ripetuta in tutti i contesti dove viene mostrato un form - che sia di invio mail, di inserimento di contenuti, ecc.

La strada maestra per poter generare i propri form general-purpose (e quindi riutilizzare questi widget) è l'uso di librerie di form (z3c.form in primis).

Cosa succede, però, se ci si trova a voler riutilizzare questi widget per altri scopi, non canonici, come realizzare un semplice form HTML partendo da una pagina bianca? E' quello che andremo a esplorare in questo articolo.

Prima di tutto: perché? In alcuni casi, librerie come z3c.form possono diventare difficili da utilizzare, magari perché si sta tentando di realizzare un form molto complesso e altamente personalizzato.
Queste librerie sono ottime per realizzare velocemente una serie di form che devono mantenere lo stesso layout, ma tendono a diventare via via più complesse nel momento in cui si ha la necessità di cambiare il codice generato.

In questi casi estremi è a volte più semplice (e meno costoso in termini di tempo) realizzare un semplice form all'interno di un template e immettere direttamente il proprio HTML.

Dopotutto, la realizzazione di un template Plone contenente un form si limita a una registrazione di uno ZCML...

<browser:page
for="*"
name="myform"
template="myform.pt"
permission="zope.Public"
/>

...e a una vista, che si può limitare ad un singolo template...

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
metal:use-macro="context/main_template/macros/master">
<body>

<metal:content-core fill-slot="content-core">
<metal:content-core define-macro="content-core">

<form action="@@gestisciform" method="post">

<input type="text" name="name" value="Nome" />
<input type="submit" name="send" value="Invia" />

</form>

</metal:content-core>
</metal:content-core>

</body>
</html>

Semplice form in PloneAncora più interessante: dimenticandosi della registrazione ZCML e aggiunto il template direttamente da ZMI, ci permette di lavorare via web (TTW)! Questo non è possibile ad oggi usando librerie di form.

Andiamo avanti. Dove viene inviato il form? Nell'esempio sopra, a un'altra vista (dal nome "gestisciform") che si occuperà di "far qualcosa" con i dati.
Il form qui sopra è esageratamente semplice, ma da ora è necessaria una semplice conoscenza di HTML (e probabilmente anche di TAL) e il form può assumere la forma che si preferisce.

Possibile che sia tutto così semplice? Infatti non lo è... I problemi iniziano quando si ha la necessità di replicare i comodissimi widget di Plone nei nostri form, soprattutto quelli che si basano sulla presenza di codice JavaScript.
In questi casi ci sono due strade:

  • tornare alle librerie di form, e gestire la complessità della personalizzazione spinta che si vuole realizzare
  • eseguire un'operazione di reverse engineering e applicare i widget e il loro codice JavaScript al nostro HTML.

Di seguito verrà illustrato come integrare in un semplice template Plone due delle sue funzionalità: l'editor TinyMCE e il widget per eseguire riferimenti all'interno del sito (referencebrowserwidget).

In entrambi i casi il risultato è stato ottenuto partendo dall'uso che viene fatto di questi widget in Plone e analizzando il codice HTML generato per estrarne i segreti. Questo approccio può essere valido e replicabile anche per altri widget qui non discussi! Provate!

Integrazione di TinyMCE

A differenza di quanto capitava con le vecchie versioni di TinyMCE (1.2 e inferiori), integrare la versione 1.3 (pre-installata da Plone 4.3 in poi) è estremamente semplice! L'unica cosa che è necessario fare è realizzare una textarea HTML e aggiungervi alcune classi CSS, più un attributo HTML 5.
Ma andiamo con ordine!

Per ospitare una textarea, il nostro form deve cambiare così (da ora in poi mi limito a includere la parti salienti del codice HTML che va modificato):

<form action="@@gestisciform" method="post">

<textarea name="testo"></textarea>
<input type="submit" name="send" value="Invia" />

</form>

Form con TinyMCE (rotto)Ovviamente questo è tutto tranne che un editor WYSIWYG, ma è anche ciò che verrà mostrato all'utente in caso di JavaScript disabilitato.

Le due classi CSS da aggiungere sono mce_editable e pat-tinymce. La loro presenza viene catturata dall'integrazione tra TinyMCE e jQuery e l'editor viene invocato.

<textarea name="testo" class="pat-tinymce mce_editable"></textarea>

Però ancora non basta: se ci limitiamo a questo, otterremo solo un errore JavaScript; l'effetto visivo è che la textarea sembra sparire, ma non viene sostituita con nulla.
E' necessario aggiungere alla textarea un attributo contenente la configurazione di TinyMCE: data-mce-config. Questa configurazione assume la forma di una complessa struttura JSON, ma esiste un metodo molto semplice per caricare la configurazione predefinita del sito:

<form action="@@gestisciform" method="post">

<textarea name="testo" class="pat-tinymce mce_editable"
tal:define="configuration_method nocall:here/@@tinymce-jsonconfiguration;
configuration_json python:configuration_method(field=None);"
tal:attributes="data-mce-config configuration_json"></textarea>
<input type="submit" name="send" value="Invia" />

</form>

La configurazione viene caricata invocando un'altra vista (fornita dal prodotto Plone per TinyMCE); non resta che inserirla all'interno dell'attributo e il gioco è fatto!

Form Plone con TinyMCE

Integrazione del Browser Reference Widget

Integrare in un form il widget per le referenze a contenuti di Archetypes non è un'operazione semplice come quella vista poco fa.
Il problema principale è che il widget e tutto il suo JavaScript sono stati fatti per essere usati all'interno del framework Archetypes; non seguono, quindi, un vero e proprio "separation of concerns" degli elementi. Sarebbe stato assai più interessante se la funzionalità avesse fornito separatamente un'interfaccia per ricercare e selezionare contenuti del sito e l'integrazione come field di Archetypes.

Questo limite ci traduce in un singolo problema (non da poco a dire il vero): il widget va invocato su un contenuto Archetypes che fornisca un vero e proprio ReferenceField. Nei fatti questo campo può anche essere nascosto e mai mostrato agli utenti, ma deve esistere.

Ad ogni modo, proseguiamo con l'analisi!

La prima versione del nostro form è piena di HTML nascosto, che prenderà vita non appena riusciremo ad "attivare" le funzionalità JavaScript.
Per quanto detto sopra, per testare questo template sarà necessario lanciarlo su un contesto Archetypes che abbia un campo di tipo ReferenceField: una qualunque pagina del sito andrà bene, anche la predefinita front-page, perché contenente il campo "Contenuti correlati" (relatedItems).

<form action="@@gestisciform" method="post">

<div id="atrb_referenze" class="overlay-ajax overlay">
<div class="close"><span>Chiudi</span></div>
<div class="pb-ajax">
<div class="overlaycontent" style="font-size: 125%"></div>
</div>
</div>

<input type="hidden" name="referenze:default:list" value="" />
<input type="hidden" value="1" name="referenze-sortable" />

<div style="float: left"> <!-- don't remove this. it is needed for DOM traversal -->
</div>
<ul class="visualNoMarker" id="ref_browser_items_referenze" >
</ul>

<input type="button" rel="#atrb_referenze" src=""
class="addreference searchButton" value="Aggiungi..."
tal:define="startup_directory context/absolute_url;
helper python:context.restrictedTraverse('refbrowserhelper', nothing);
at_url helper/getAtURL|nothing;"
tal:attributes="src string:${startup_directory}/refbrowser_popup?fieldName=referenze&amp;fieldRealName=relatedItems&amp;at_url=${at_url}&amp;" />

<input type="submit" name="send" value="Invia" />

</form>

Form con BrowserReferenceWidget (rotto)Analizziamone i punti essenziali:

  • deve essere stabilito un nome del campo, che va ripetuto in vari punti del codice: nel nostro caso è stato scelto "referenze". Questo nome compone numerosi id di elementi della pagina, e vanno sistemati tutti
  • c'è un unico riferimento ad un parametro fieldRealName, che deve contenere un nome reale di un ReferenceField
  • c'è un DIV nascosto (id=atrb_reference), che sarà l'overlay AJAX di jQueryTools (pre-installato in Plone) che conterrà la popup di selezione degli elementi
  • esiste un UL inizialmente vuoto (id=ref_browser_items_reference) dove verranno aggiunti gli elementi selezionati
  • esiste un bottone "Aggiungi...", la cui parte principale è un attributo src (invalido... HTML5 non era ancora una realtà al tempo) il cui contenuto sarà sfruttato dal JavaScript del widget.

Il risultato di questo template è un form con un pulsante completamente inattivo.

Quello che manca è un po' di codice JavaScript che ci permetta di attivare le funzionalità del widget. Aggiungiamo quindi un po' di JavaScript all'interno del template (modificandone la parte iniziale):

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
metal:use-macro="context/main_template/macros/master">

<head>

<metal:head fill-slot="javascript_head_slot">
<script type="text/javascript" src=""
tal:attributes="src string:${portal_url}/referencebrowser.js"></script>
</metal:head>

</head>

<body>
...

Questo JavaScript è piuttosto semplice, non fa altro che includere nella pagina corrente la chiamata al file referencebrowser.js. Questo, grazie alla struttura HTML utilizzata, farà tutto il resto del lavoro!

Form con BrowserReferenceWidget

Parlando di dati inviati

Ma questo tipo di campo che cosa invia? Qual è il valore che inviamo al server all'operazione di submit del form?
L'informazione è l'id univoco del contenuto(i): lo uuid. La vista a cui inviamo i dati potrà utilizzare questo dato per caricare il contenuto che abbiamo referenziato e gestirla come meglio crede.

E la referenza singola?

Questo esempio include il formato del ReferenceField per eseguire referenze multiple; la referenza singola è possibile con pochi accorgimenti e differenze nel codice HTML generato, ma soprattutto richiede un campo ReferenceField senza l'attributo multiValued attivo.
Lascio la realizzazione al lettore, come esercizio di analisi Plonesca!

Conclusione

Spero che questa breve carrellata vi abbia fatto capire come Plone, pur rimanendo un CMS di livello enterprise, permetta comunque di utilizzare le proprie funzionalità senza addentrarsi troppo nei meandri della sua architettura.

comments powered by Disqus