Saturday, September 10, 2011

Plone and Related Items use for Collections and Folders

This post is about an old but hidden feature of Plone Collections, and how related items are used in some non-standard ways in Plone.

Have you ever used related items for a Plone Collection? Maybe you never used them neither for Folders, so you simply learn that Plone choose to hide related items for some types.

But while for Folders this is simply a choice to make related items as hidden, for Collections is different.

Using related items for Folders
Every AT content schema, after being defined, call a function that is finalizeATCTSchema.

    def finalizeATCTSchema(schema, folderish=False, moveDiscussion=True):
        """Finalizes an ATCT type schema to alter some fields
        """
        schema.moveField('relatedItems', pos='bottom')
        if folderish:
            schema['relatedItems'].widget.visible['edit'] = 'invisible'
        ... 

The function receive the argument folderish=True for folder content types. As you can see this hide the related items widget when editing (and in this way no simple user can fill the field, so you'll never see it also in view).

But if you fill the field through an external code, the field appear also in Folder's view.

To restore the field normally, simply make the widget visible again.

Using related items for Collections
For Collections the related items field is not hidden but has a special purpose.

You must use it to add additional items to the collection search result, ignoring whatever type of criteria you have set in the Collection.

For example: you can have a Collection that show all contents inside a folder (using a simple well-know path criterion) then you can add to related items of the collection one or more contents outside that folder. The resultset of the collection will include also this content.

Another example: you have a collection that display news items on the portal, then you need to include also an important event. The normal way for Plone is to create a new news item that talk about this event, but you can also use this trick: add the event to related items.

Drawbacks
I consider this a good feature. Not common, but two examples above are not so uncommon... but probably you never know of this feature.
However this behavior has some problems.

Plone 4.1 double related items. Seems that there is a bug in recent Plone. On Plone 3 if you were using related items in collections, the related items section was not shown in the Collection view template. Now is... probably a little bug that need to be fixed.

Documentation. No one know of this behavior because this isn't readable anywhere in the Plone UI. The related items field is the same of always. Some suggestion that can help:
  • related items for Collections must be moved on the main fieldset, to highlight that there has some special meaning.
  • related items widget help text for Collections could give some additional information on this behavior.
Related items aren't as normal criterion results. Related items are added to the resultset of the Collection, but:
  • They are not sorted using the choosen sorting criteria
  • They are not mixed with normal results
  • They are always returned first
So related items are always used first, then other results are displayed. This can be good (because you can use this feature to put an high important news as first item of your Collection) but not simple to understand. However this is not a bug... mix related items and other results sorting them all can be complex and expensive for Plone.

How to sort those additional resultset? Recent Plone releases have finally add a way to sort related items (without using external add-ons like before) but using Plone 4.1 trunk the manual sorting is ignored for this feature. Maybe another little but to be fixed?

Can I use related items normally? Giving us this feature will steal us the possibility to use related items for Collections for the common purpose. I don't find this a big problem, however I get this request one time: the customer asked us to be able to use related items for collections in the classic way. Use the same field for a different task is someway strange.

Conclusion
Is this a good feature? I think it can be... however sometime I think that is too complex and simply needs to be removed. Some other times I find it smart.

What you think?

Sunday, August 21, 2011

TagCloud and SEO in Plone: problem solved and lesson learned

This article is an analysis of how a minor bug in a Plone product we are maintaining give us some problems on a production site, how fixing it was not enough to revert problems, and what lesson I learned.

The environment
The Plone site I'm talking about is an old Plone 3.3 installation with an old hardware. It worked without problems for years then suddenly it started to be slow (sometimes really slow).
In front of this Plone installation there's also a Varnish installation that cache also HTML for anonymous users (a standard for us).

So what's can be turned wrong?!

After started checking the problem I also find that it wasn't a memory consumption problem (a lot of free memory, thanks to the use of plone.app.blob also on this Plone 3.3 installation), but only a CPU usage (unluckily we have only a single core for the Plone site).

Next step was checking what the site was doing to keep the CPU so busy. Before installing products like zope.healthwatcher, I spent some minutes simply checking the HTTP log and I found that the site is... "really popular". I mean it is very often visited by web crawlers, mainly Googlebot. This is not strange: the site is the main site of a well know public agency, and update really often.

The Vaporisation problem found
Is this enough to make a Plone site "slow"? Obviously no! The problem was not Plone but the TagCloud portlet.

One of the best features of collective.vaporisation (maybe the main feature that convinced us to takeover the project and maintain it) is the joint navigation.
When activating it, clicking on one of the links inside the cloud will not simply display a search result, but a search result where you can also navigate through found items using related additional terms (something like a faceted navigation).

The first problem was that customer site use a lot of different keyword, using them widely in a lot of site contents.
In this way the joint navigation became complex. Imagine a web crawler that starts to scan the tag cloud results page: it's able to follow a lot of additional link that create a great permutation of different search results (let call this big number N).

The second problem (the real one) was a bug in the way Vaporisation was creating links from the cloud to the result page (note that version 1.2.0 of the TagCloud fixed this problem so recent releases will not give you such behavior): older releases were calling the cloud_search template on the context.
This mean that when the visitor was checking the cloud links from the home page, it will call Plone as http://thesite.com/cloud_search, but when visiting another page, the URL will became http://thesite.com/path/to/document/cloud_search.

This is a disaster for the cache that Varnish is trying to produce for our site: the two URL are different from the cache point of view, but in facts on Plone they are generating the same result. This is really bad (also for page rank).

This also raise the number of possible cloud search pages from N to NxM (where M is the number of documents in the site). Terrible!

The fix was simple: change the way the URL to the cloud is created, always give to users the http://thesite.com/cloud_search version.

Google: the elephant
When I released this fix I make another mistake (not very lucky...).
Always remember that in Plone the context can be important. If you created a site view that must be called onto the site root is better to define it callable only on this context. An common error can be define this view callable everywhere (like old CMF skins templates).

So even if all URL were always generated in the right way, the view was defined still as follow:

<browser:page
	 for="*"
	 name="cloud_search"
	 class=".search.CloudSearch"
	 template="search.pt"
	 permission="zope2.View"
	 />

In this way calling (manually) again the http://thesite.com/path/to/document/clud_search will still be valid.

Why this is still a problem? Haven't you fixed all possible wrong links?
Because of Google long memory.

Even if we removed all links to the useless cloud_search call, Google already indexed them so it continue to call all those URLs and make the Varnish cache useless.
Maybe with the time those kind of call could decrease (as the search engines will find no links to this page anymore), but after applied the fix we get no benefits.

Version 1.2.2 fixed this, defining also the right context for the view:

<browser:page
	 for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot"
	 name="cloud_search"
	 class=".search.CloudSearch"
	 template="search.pt"
	 permission="zope2.View"
	 />

After this change all calls to something that isn't http://thesite.com/cloud_search will simply generate a NotFound error page. This is good: Plone is very fast to generate this page, and this kind of pages (if no link lead to them) will rapidly disappear from the index.

Some SEO enhancement can help
Version 1.2.2 helped a lot, but in the meantime I was also reading a book about SEO and talked with coworkers (real SEO expert) of those kind of problems.

Must Google be aware of the cloud_search page? Is a good thing that this page will be indexed?Also, the site still use the joint navigation, so a major number of pages are still called, but in any case are those pages giving some good feedback to Web users that performs searches?

I think that there isn't an universal answer (I've doubt also related to simple Plone SERP... must be indexed by Google? Maybe Plone itself must think about this) but in this case no: let search engines index results page of search performed by tag cloud is useless.

Version 1.2.3 make the Vaporisation product more "SEO friendly".

First of all we can suggest search engines to not follow a link when scanning our site putting a rel='nofollow' attribute on it.

The new version of Vaporisation will put this rel nofollow value on every tag cloud link. I read recently that this attribute is only a suggestion, but can help.

The second and last change of this version is the use of a tag meta in the cloud SERP:

<meta name="robots" content="noindex, nofollow" />

This says to the search engine to not index this page (noindex value) but also don't follow any links from this page (nofollow value, less important there).

In this way Google quickly stopped to index also the big number of possible joint navigation results pages.

Damn Google Reader!
After every fix I applied, the site quickly became faster.
After all those fixes I still saw an heavy Google access to the site on a lot of different pages: this time to the search_rss page (used by Plone to generate RSS feeds from search result), commonly called with an URL like http://thesite.com/search_rss.
The site now was not so slow, but I liked to continue the investigation.

The problem was still the first one I analyzed above: the search_rss URL was available from the cloud_search page and so, when using older releases, was created on the context and not on the portal root.
So while a version lower than 1.2.0 was active, Google indexed a lot of cloud_search versions but for each one also a useless search_rss page.

Again, have fixed the problem for the "master" cloud_search page will not stop Google from indexing all the RSS calls.

This time we can't rely onto a well-done meta tag, as the RSS isn't HTML! Where I can put the tag?!

We have two alternative ways.

The quicker one is to block every web crawler access to the search_rss page, using simply a robots.txt file on the site root (but this will works with query parameters?):

User-agent: *
    Disallow: /search_rss

The other way, that also leave the search_rss page indexable by services like Google Reader, is to make also the search_rss template callable only from the site root.

This need some simple Plone customization (outside the Vaporisation product itself). Maybe Plone 5 (or 6?) will not use anymore CMF templates, but right now we have a lot of them all around Plone.
One of the problem of old-style template is that they can be called on every possible context. And obviously search_rss template is a very old template...

So the fix is not elegant as the cloud_search ones. We can:
  • configure Apache to disallow any search_rss call outside the site root
  • manually check if the site context is the site root; if not, we manually raise a NotFound error.

Conclusion
I found the resolution of this someway "funny".

Other Plone sites that installed directly the 1.2.0 version of Vaporisation didn't suffer all problems that are here described because the cloud_search page was always called in the "most correct way": on the site root.
So in this case Google did not indexed all other useless alternative way to call the same page (this mean also no need to fix the Plone basic search_rss)

Last thing: if you create a Plone template planned to be called on a context be sure to register it only on this context. If this template is an old CMF ones keep an eye on how you create links to this template.

Saturday, August 6, 2011

Data di Scadenza/Pubblicazione in Plone: la guida definitiva

E' una delle funzionalità da sempre presente nel CMS, eppure rimane ancora oggi poco capita dagli utenti e spesso provoca effetti imprevisti.

Stiamo parlando della data di scadenza e della data di pubblicazione di Plone.

Il caso peggiore che può capitare
Mi torna in mente un affascinante concetto letto in un libro sulla storia della crittografia ("Codici e Segreti", ve lo consiglio). Più o meno recitava così:
La sola cosa peggiore di non avere segretezza è credere di averla
Torno subito coi piedi per terra.
Plone ha un potente sistema per rendere i nostri dati segreti e sicuri: il workflow. Quando il workflow è ben configurato e i documenti o le cartelle sono nel giusto stato di revisione, possiamo dormire sonni tranquilli.

La cosa più difficile da capire per un redattore Plone è che le date di scadenza e pubblicazione non influiscono sul workflow!

Dopo tutti questi anni ho spiegato e visto spiegare questo concetto ai redattori molte volte eppure tante volte ho visto questa stessa caratteristica di Plone non essere capita.

E qui ci ricolleghiamo alla frase che ho citato sopra. Assai spesso l'utente crede (sebbene nessuno lo abbia mai spiegato in questi termini) che quando un documento di Plone scade, questo magicamente diventi privato.
Ho visto casi dove, a volte anche dopo anni di utilizzo di un sito in produzione, un redattore scopre che qualche altro utente riesce ad accedere ad un contenuto che non dovrebbe vedere e la loro prima impressione è che sia un baco di Plone (o peggio, un baco dell'installazione data dal fornitore).

Vi siete mai sentiti dire "Ma è scaduto! Perché si vede?!", oppure: "Credevo che diventassero privati!". Ed ecco che entriamo nel caso peggiore... per tutto questo tempo il redattore ha creduto che i suoi contenuti fossero al sicuro, ma non era così!

Per certi versi questo caso è particolarmente sentito per la scadenza dei contenuti, mentre raramente lo è per la data di pubblicazione (o visibilità effettiva). Nel seguito del documento mi riferirò quindi maggiormente al concetto di scadenza ma sia chiaro che quanto verrà spiegato vale anche per i documenti non ancora giunti alla data di pubblicazione.

Badate bene: se c'è una colpa non è dei redattori. Il funzionamento del meccanismo di scadenza/pubblicazione è in effetti difficile da assimilare (e la lunghezza che assumerà questo articolo è quantomai sospetta per poterlo negare... elefantiasi letteraria a parte!  :-)).

In più, come vedremo poi, è un meccanismo ibrido... in parte usa i permessi (il cuore della sicurezza Plone) ma in modo assai diverso da come i permessi funzionano per tante altre situazioni. Quindi anche gli sviluppatori Plone meno esperti, che magari si sentono già padroni dell'uso del workflow, possono commettere errori.

Prima cosa da capire: scaduto non significa privato
Questo semplice concetto a volte basta agli utenti per poter dire "quindi questo meccanismo non mi serve" perché avrebbero preferito che la scadenza portasse automaticamente all'archiviazione o alla "spubblicazione" del documento.
Consiglio a questi utenti di non abbandonare così precipitosamente questa funzionalità, perché rimane nonostante tutto molto interessante e utile se la si capisce fino in fondo.

Ammettiamo di essere in presenza di un semplice workflow a due stati: privato e pubblicato. Il contenuto privato non è visibile da nessun utente se non il redattore stesso (o il suo team) ma comunque non ai semplici ed anonimi visitatori del sito.
Il contenuto pubblicato è invece di pubblico dominio. Tutti lo vedono, tutti lo trovano (compreso Google).

Poi abbiamo la data di scadenza (o pubblicazione).

Queste due entità (stato di revisione del documento e data di pubblicazione) viaggiano in modo completamente disaccoppiato e non si influenzano in nessun modo. Posso avere tutte e quattro le possibili combinazioni:
  • documento privato e ancora valido
  • documento privato a scaduto
  • documento pubblicato e ancora valido
  • documento pubblicato e scaduto
La sicurezza (il workflow) è l'unica cosa certa. Se un contenuto è privato è sicuro. Qualunque sia la data di scadenza poco importa, il documento privato non può essere acceduto direttamente, o tramite ricerche da Google o nel sito. Non ci sono problemi relativamente a scarsa sicurezza in questi casi.

I problemi che potete riscontrare iniziano quando i documenti diventano pubblicati e per questo da ora in poi ci concentriamo su questi casi. Se un documento è pubblicato è accessibile al mondo. Le date di scadenza/pubblicazione servono solo a limitare alcuni tipi di accesso al contenuto.

La prima lezione da imparare è quindi non confondere la sicurezza del contenuto con la sua validità.

Contenuto scaduto significa "non è ricercabile"
Questa è la mia Legge Zero sull'uso della scadenza/pubblicazione in Plone. E' la prima cosa che dico agli utenti quando parlo di questa funzionalità, che sia ad un corso o ad un nuovo redattore (o purtroppo ad uno vecchio che magari non aveva capito).
Ovviamente questa frase non basta... va argomentata! Ma intanto vi fa iniziare la discussione in modo diverso dal più vago "quando un contenuto è scaduto, non è più visibile", che ricorda troppo quello che capita col passaggio allo stato di revisione privato.

Il funzionamento della data di scadenza/pubblicazione è in realtà davvero tutto qui: il contenuto non può essere trovato dal catalogo di Plone.
Questo significa quindi che il contenuto è escluso automaticamente:
  • dalla ricerca del sito (base, istantanea, avanzata)
  • dal navigatore
  • dai risultati delle collezioni
  • dalle viste applicabili alle cartelle
Come dicevo, il funzionamento va argomentato un po': l'utente deve capire che Plone usa la ricerca per quasi qualunque cosa venga mostrata nella sua interfaccia. Se è ovvio comprendere questa regola per quello che riguarda la ricerca del sito, è bene accennare al fatto che anche:
  • che il navigatore è frutto di una serie di utilizzi del catalogo di Plone
  • che le varie viste applicabili alle cartelle eseguono delle ricerche nel catalogo (questo in realtà dovrebbe essere cambiato da Plone 4.0 ma non è cambiato il risultato quindi non complichiamo le cose!)
  • che le collezioni non sono altro che "ricerche salvate" e quindi usano il catalogo
Il risultato di questa regola è che il contenuto scaduto, se non per il Manager del sito (il perché lo vedremo poi) risulta sparito.
L'effetto primario per l'utente finale è che in effetti (ad una prima occhiata che spesso confonde i redattori) subisce lo stesso effetto del cambio di stato quando si revoca il contenuto e lo si porta a privato: il contenuto non viene più trovato dalla ricerca, sparisce dai navigatori e dalle viste...

Ma la differenza quindi qual'è?

Google e cronologia hanno la memoria lunga
La differenza è semplice: un contenuto che non può essere trovato non significa sia inaccessibile mentre con l'uso del workflow abbiamo entrambe le cose.

Partiamo da Google, che ha indicizzato il vostro contenuto (in quanto c'è stato un momento nella sua storia dove probabilmente questo era pubblicato e non era scaduto). E' vero che anche i crawler dei motori di ricerca, visitando il sito come utenti anonimi, improvvisamente smetteranno di trovare link al documento in quanto il contenuto è sparito dai navigatori e dalle varie viste.

Ma questo basta per dire a Google di smettere di indicizzare quella pagina e farla sparire dagli archivi?
Ovviamente no! I motori di ricerca (e gli utenti, tramite questi) continueranno ad arrivare alla pagina. Google conosceva l'indirizzo e continuerà ad indicizzare quella pagina a meno che non decida spontaneamente di rimuoverla dall'indice. L'unico effetto certo è vederne calare il page rank, visto che i link a questa pagina saranno meno (o addirittura spariti completamente). Niente più.

A dire la verità forse Plone potrebbe esplicitamente avvertire i motori di ricerca di smettere di indicizzare le pagine scadute (basterebbe un tag META ben scritto... che sia un'ottimizzazione SEO da considerare)?

Senza andare a disturbare Big G, ricordate poi che gli utenti usano browser che sono sempre più avanzati. Oltre ai bookmarks (preferiti o segnalibri), possono contare sulla cronologia delle ricerche, l'autocompletamento, ... E' quindi comunque possibile che gli utenti raggiungano la vostra pagina semplicemente perché ci sono già stati (loro direttamente, o un altro utilizzatore dello stesso browser).

Infine: siete certi al 100% che nel resto del sito, o in un altro sito che non è sotto il controllo vostro o dei vostri redattori, qualcuno non abbia inserito un link alla pagina scaduta? Non l'avete per caso inviata per posta? Ad una mailing list? In un forum?

Capire questo vi eviterà di trovarvi nella situazione di credere che i vostri contenuti, i quali non dovrebbero essere più accessibili, siano spiacevolmente ancora pubblici.

Vorrei che i contenuti scaduti venissero archiviati
Questo è possibile? Certamente, ma va sviluppato qualcosa... a Plone va aggiunto qualche componente.
Un paio di idee:
  • un processo esterno chiama Plone ogni ora (o una volta al giorno, magari la notte) e scatena un'operazione che porta i contenuti scaduti allo stato privato
  • come sopra, ma li sposta in una cartella privata
  • aggiungere a tutti i contenuti del sito un controllo (una viewlet andrebbe bene ma probabilmente non sarebbe troppo elegante...) che controlli se l'utente corrente ha poteri per vedere i contenuti scaduti e in caso contrario lanci l'errore di "Non Autorizzato"
Tutte queste soluzioni sono praticabili ed hanno pro e contro.

Mi serve davvero farlo scadere?
La scadenza di News ed Eventi a mio avviso è nella natura stessa di questi contenuti, quindi sì, probabilmente dovete farli scadere.
Altre volte invece ho visto utenti che volevano fondamentalmente archiviare un documento, mettere una data di scadenza a quello vecchio e pubblicare quello nuovo, per poter avere sempre disponibile la vecchia versione.

A questi utenti ricordo che Plone ha un sistema di versionamento integrato. In questo caso non vi serve assolutamente giocare con la data di scadenza. Semplicemente modificando il contenuto e sostituendo le sue informazioni con quelle aggiornate fa sì che i vecchi dati vengano salvati come "vecchia versione".

Imparare ad usare le date di scadenza e pubblicazione
Ora che abbiamo capito come non intendere il funzionamento delle date di scadenza e pubblicazione e come evitare che il loro uso improprio provochi problemi al nostro sito, non ci rimane che tirare le somme e riassumere come vanno usate.

I documenti scaduti, per la loro natura, perdono grandissima visibilità nel sito e questo è un bene. Come dicevo sopra anche Google viene influenzato dalla sparizione di link che portano a quella pagina, quindi anche la ricerca della stessa risulterà più difficile.

Ma nella mente dei vostri redattori questa deve rimanere una pagina pubblica. Dovete chiarir loro questo concetto in modo definitivo. Il documento ha perso importanza, contiene informazioni non più aggiornate, ma se un utente particolare volesse accedervi potrebbe ancora farlo.
Dovete vedere questa caratteristica come un pregio... se il visitatore vuole leggere una news scaduta, relativa ad un evento dell'anno scorso sul vostro sito, perché impedirglielo?

Purtroppo, nella pratica di Plone questo non è un ragionamento che torna completamente. Plone non permette completamente nemmeno questo (a meno di modificare i permessi, come spiegato sotto): come dicevo sopra, non potete cercare il documento scaduto.

Siamo quindi in presenza di una situazione un po' "a mezza via". Se infatti la vostra scelta è consapevole e sapete come Plone si comporterà col vostro contenuto scaduto (e lo accettate), non è quindi facile per il visitatore del sito tornare a quel documento.

L'assurdità della situazione è che Google può ancora guidare i vostri utenti alla pagina scaduta, la ricerca integrata nel sito no.

Come funziona la "magia" dei documenti scaduti?
Questa sezione diventa un po' più tecnica ma servirà a capire perché succede tutto questo.

Nei fatti non appena il comportamento di Plone diventa chiaro mi sono sentito chiedere varie volte "ma non si può correggere"?
Come dicevo all'inizio, questo comportamento è percepito con un bug. Ma è bene chiarire che non è un bug... forse potrà essere definito un limite del sistema, ma è un comportamento che ha una precisa spiegazione.

Quando eseguiamo qualcosa che scatena una ricerca nel catalogo di Plone (e questo capita decine di volte per ogni click che provoca il caricamento di una pagina), per sapere se quel contenuto può essere trovato dall'utente corrente, viene analizzato uno speciale indice del catalogo dal nome allowedRolesAndUsers. Questo indice ha un comportamento complesso che non voglio approfondire; diciamo solo che contiene, per ogni contenuto, tutte le informazioni necessarie per capire se l'utente corrente (anche il visitatore anonimo), con i suoi ruoli e i ruoli dei gruppi di cui fa parte, può vedere un documento.

Lasciando momentaneamente perdere le ricerche nel catalogo, chiediamoci invece come funziona la sicurezza di Plone: come fa Plone a sapere se un certo utente può vedere un contenuto?

Viene verificato ogni ruolo dell'utente corrente per capire se ad uno di questi è associato il permesso View, che identifica proprio il permesso di vedere il contenuto. Questo (e tutti gli altri permessi che di solito interessano) sono gestiti dal workflow di Plone. Avere questo permesso significa vedere, non averlo significa non poter accedere e ottenere quindi la pagina di errore per accesso non autorizzato (nel caso si tenti comunque di visitarne l'URL).

Stato privato del workflow

Lo spostamento del contenuto da stato pubblicato a privato non fa altro che modificare quel valore.

Stato pubblicato del workflow

Il numero di ruoli (e anche l'essere Anonimo è visto come un ruolo) che può vedere il contenuto cambia.
Come già detto, la verifica della sicurezza di un oggetto è perfetta e non da problemi, ma le ricerche non funzionano in questo modo.

Le ricerche di Plone non accedono al contenuto vero e proprio per verificarne lo stato di revisione, ma usano l'indice allowedRolesAndUsers di cui parlavamo sopra.
Questo viene fatto per questioni di velocità: accedere al contenuto è una pratica decine di volte più lenta (e più costosa in termini di memoria) che non usare il catalogo. Quell'indice è una traduzione dello stato di visibilità corrente del contenuto (permesso di View) e viene aggiornato ad ogni modifica del contenuto stesso che ne provochi un cambio.

Non esiste quindi un permesso che si occupi dei contenuti scaduti?
Certo! Il permesso è "Access inactive portal content" (letteralmente: "accesso al contenuto inattivo") ma a differenza dello stato di revisione e del permesso di View, questo risente anche di un'altra variabile non controllata da Plone: il tempo.

Per questo non è possibile avere un ulteriore indice (o modificare il comportamento di allowedRolesAndUser) che memorizzi in qualche modo questa informazione perché l'informazione cambia con lo scorrere del tempo.
Non è nemmeno è possibile verificare il permesso direttamente sul contenuto: questa verifica comporterebbe il caricamento del contenuto stesso (e come detto poco fa è un procedimento che rallenterebbe troppo Plone, se immaginato ripetuto per centinaia o migliaia di documenti).

Quindi come viene usato? Nella pratica se l'utente corrente non ha questo permesso, ai criteri di ricerca viene letteralmente iniettato un nuovo criterio basato sull'indice effectiveRange, quindi l'intervallo di tempo in cui il contenuto è valido.
Ma come viene fatta quindi la verifica del permesso? La modifica di questo permesso non ha effetto sui singoli contenuti (e non va quindi usato nel workflow a meno di non introdurre la viewlet poco elegante ipotizzata sopra) ma può essere gestita a livello di sito.
Questo apre nuovi scenari e possibilità... e purtroppo qualche altra incomprensione.

Modifiche di "Access inactive portal content" da valutare
Abbiamo quindi imparato come questo permesso possa essere usato, ed in effetti influenzi la ricerca, sul sito Plone. Ecco infatti il motivo per cui il Manager del sito vede i documenti scaduti.

Ragionando un attimo, potrete arrivare a capire come questo porti anche ad un altro comportamento di Plone poco amichevole. Vi è mai capitato che l'autore di un contenuto (ruolo Owner) non veda più i propri documenti una volta che sono scaduti?
Ebbene... capita. L'utente è Owner sul contenuto, ma non lo è a livello di sito (l'Owner del sito è ovviamente il suo creatore, quindi l'utente Manager principale che di solito ha id "admin").

Questo ci porta in fretta a valutare le uniche due modifiche al permesso "Access inactive portal content" che vi consiglio di valutare.

Chi ha un account nel sito può vedere documenti scaduti
Nella maggior parte dei siti chi ha un account (di qualunque tipo) è un redattore o un revisore di contenuti. Ha quindi senso valutare se dare al ruolo "Member" (il Collaboratore) il permesso per trovare i contenuti scaduti.
Il Redattore A potrà vedere i contenuti scaduti, suoi e anche del Redattore B... ma questo è un problema? Dopo tutto sarebbero comunque accessibili conoscendone l'URL!

In questo modo evitate lo spiacevole caso del "non vedo i miei contenuti".

Tutti vedono i contenuti scaduti
L'interfaccia di Plone evidenzierebbe comunque l'accesso ad un contenuto che è scaduto, mostrando una scritta "scaduto".

Vista di un contenuto scaduto

Se vi sembra poco, la modifica di questo testo con qualcosa di più "acceso" è semplice.

Dunque perché non valutare se dare questo permesso anche agli anonimi? Otterreste che la ricerca nel sito funzionerà alla pari di Google, trovando di nuovo tutti i contenti pubblici, ma visitandone il dettaglio il visitatore potrà comunque accorgersi che sta accedendo a dati non più aggiornati.
Nota bene: purtroppo perché l'utente anonimo sia in grado di visualizzare quella scritta, come nell'immagine sopra, è anche necessario andare nel pannello di controllo di Plone (sezione "Sicurezza") e selezionare "Consenti a chiunque di vedere le informazioni personali".

Altre combinazioni?
Ci sono in effetti altri casi possibili che potete valutare ma li trovo meno comuni.
Esempio: se la squadra di revisione dei contenuti è unica nel sito (e quindi avete un gruppo di persone che hanno ruolo Reviewer ovunque) allora potreste dare il permesso "Access inactive portal content" a questo ruolo.
Stessa cosa per i contributori (Contributor) e gli Editor... dipende molto dalla dimensione e dalla struttura della vostra redazione, e del vostro sito.

Saturday, July 16, 2011

Hidden (great) secrets inside ATVocabularyManager

ATVocabularyManager is a well-know Plone product developed by BlueDynamics that make simple handling vocabulary values used by your contents directly inside Plone.
That's all: the power of the product is all in this first sentence.

Generic Setup Strike Back
In my personal experience I not commonly found projects that need a Plone user able to change vocabularies, but in the information architecture this is quite common (they call this "Controlled Vocabulary"). So I used ATVocabularyManager a couple of times in the past, but I never became habit to rely on it.

Another thing I don't liked was the unexistent Generic Setup integration.

Recently a customer asked us a new project with some new content types, with a lot of field with controlled vocabularies (with many values inside). Also he explicitly ask to be able to handle and change it in the future.

So I looked back to ATVocabularyManager, hoping that during this time something changed.

A New Hope
This time I note immediately that latest releases (1.5 branch for Plone 3.3, and 1.6 for Plone 4) give us something new.

First of all: for the first time I understand that the name prefix "AT" means obviously "Archetypes", but you can think it as "the way of controlling vocabulary is done using some archetypes contents". This mean that you can use it only if your vocabularies are inside archetypes content types? No!
You can also use it to handle whatever ZCML vocabulary you need (portlet? Dexterity?)

The other thing I found is that now we have Generic Setup integration. Great!

The Generic Setup integration
Right now the integration is not fully complete. Seems that export step is not there, but looking at the code I saw that the import step code (the most important!) is available. The product right now suffer only some missing of documentation.

How the import steps works? Instead of creating new vocabulary content types ("Simple Vocabulary", "Sorted Simple Vocabulary", ...), it is based on the "IMS VDEX Vocabulary File" content.

At first glance this can seem the less user friendly way and most obscure content type (and probably this is true) but going back to information architecture this is probably the best choice, because it's based on an XML international standard for handle vocabularies: the IMS VDEX.
Another good news: this format also support i18n (and also Plone)!

What I needed after this is simple: provide a VDEX compatible XML file. How? Let show a complete example.

How to add the GS support
First of all you need to provide a "vocabularies.xml file" to your profile directory.
The format of the file is as follow:

<?xml version="1.0"?>
<object name="portal_vocabularies" meta_type="ATVocabularyManager">
<object name="test.vdex" /> 
...
</object>

So you need to provide a reference to a vocabulary file for every vocabulary you need to import (.vdex of .xml file extensions are valid ones).

Where to put all vocabulary files? You need also to put at the same level a "vocabularies" directory. Inside this you simply need to put all files.

Now I will show the file format of our test.vdex file.

<vdex xmlns="http://www.imsglobal.org/xsd/imsvdex_v1p0"
orderSignificant="true">
  <vocabIdentifier>test-vocab</vocabIdentifier>
  <vocabName>
    <langstring language="en">A test vocabulary</langstring>
    <langstring language="it">Un vocabolario di test</langstring>
  </vocabName>
  <term>
    <termIdentifier>aaa</termIdentifier>
    <caption>
      <langstring language="en">A value</langstring>
      <langstring language="it">Un valore</langstring>
    </caption>
  </term>
  <term>
    <termIdentifier>bbb</termIdentifier>
    <caption>
      <langstring language="en">Another value</langstring>
      <langstring language="it">Un altro valore</langstring>
    </caption>
  </term>
</vdex>

That's all. We created a vocabulary with two entry inside (foo values are "aaa" and "bbb"). As say above, handle this directly from Plone is not very comfortable (simpler vocabulary type are easier to understand) however this is great for Generic Setup install step!

Internationalization note
A little bug on this approach. Seems that even if you plan to not provide an internationalization for you vocabularies (for example: you only need to provide your italian, spanish or something other translation) you still need to provide also the english one, or the vocabulary content inside ATVocabularyManager control panel will not show you the right title of the vocabulary (something like "unnamed vocabulary" instead of "Un vocabolario di test").
But you can use a trick and duplicate your locale specific translation also for english. Also, put english translation first.

Not very comfortable right now
If you still think it, you are right. Maybe that vdex is a well know standard for handle vocabulary, but build a vocabulary with this XML format can be not very simple.

For example, the customer give us a document (a MS Word attachment, obviously) with a set of lists of values. The easy way is to put all this in some CSV files, where columns are "italian translation" and "english translation".
But after that we need to convert this in the vdex format. How?

What I did is too look on the cheeseshop for a library that can convert a CSV in a VDEX file. What I find is vdexcsv!
Two funny thing about it:
  • It was released something like two hour before I performed that search!
  • The company behind this product is again BlueDynamics!
What this product does? You only need to easy_install it then you will be gifted with a new bash command: csv2vdex.

The documentation is clear: you need to provide a CSV file, some parameter, and you'll obtain your vdex file.

For the example above I used a CSV like this:

"key";"english";"italian"
"aaa";"A value";"Un valore"
"bbb";"Another value";"Un altro valore"

This was named test.csv.

Then I called the script in this way:
csv2vdex test-vocab 'A test vocabulary,Un vocabolario di test' test.csv test.vdex --languages en,it --startrow 1

One last step: the generated vdex file is perfect for all but the root node name. As in the example above you need to have the root node called vdex, but the script generate it as vocabulary. However after contacting Jens (the product creator, that also help me to reach this results) he was in agreement to change this in future releases of vdexcsv.
For now, simply rename the node!
EDIT (2011-08-22): the good guys released vdexcsv 1.1 that is now fully standard compliant, so no more needs to manually rename the node!

Saturday, June 25, 2011

Inno alla Collective

Qualche tempo fa mi è capitata una richiesta da parte di un cliente che al 70% si è rivelata già svolta grazie al lavoro di un'altra azienda (straniera) che produce prodotti Plone e che ha rilasciato il codice.
Fantastico!

Premessa: dato che il discorso che vorrei affrontare qui non è relativo a quel prodotto o a quell'azienda ma è più un discorso di attitudine generale, non scendo nei particolari. Chiamiamoli quindi Prodotto X e Azienda X (e visto che ci siamo, anche Cliente X).

Come innumerevoli altre volte, con soddisfazione ho potuto dire al Cliente X "quello che mi chiedi, Plone (la comunità) te lo offre praticamente già fatto". Soddisfazione sua (che ancora una volta ha capito quanto abbia fatto bene a scegliere Plone nel panorama dei CMS Open Source), soddisfazione mia (che lavoro meno e non devo per l'ennesima volta reinventare la ruota).

Rimane fuori quel piccolo 30%. Il Prodotto X era carente di:
  • traduzione italiana (questo capita ancora troppo spesso)
  • qualche funzionalità aggiuntiva, specifica del Cliente X, che poteva però essere resa una funzionalità generica e riutilizzabile per tutto il Prodotto X.
Aggiungiamo anche un piccolo particolare, ossia che il prodotto era testato (funzionante) solo per Plone 4. Il Cliente X ha una solida installazione Plone 3.3, sono certo che prima o poi passerà a Plone 4 (4.1?) ma senza fretta.
Quindi alla lista sopra, aggiungiamo anche il backport del codice a compatibilità con Plone 3.3.

La Collective
La comunità Plone è fantastica, tra le altre cose, per la sua adesione quasi totale all'uso del repository di codice comune: la Collective.
Raramente lo sviluppatore medio si prende il tempo di andare a cercare nella pagina della documentazione di un prodotto se c'è un link al repository del codice. E' certamente buona norma documentarlo esplicitamente (ne ho già parlato) ma i Ponisti sanno dove andare a trovare il codice.
E' come entrare in una casa di un amico, e questo ti chiede "prenditi pure una birra". La cercheresti fuori dal frigo?

Capita mai che il codice non sia dove me lo aspetto? Certo... è capitato, ma credetemi se vi dico che è un evento talmente raro da potermi dimenticare che alle volte succede!

Quale è stata la sorpresa questa volta? Ovviamente il Prodotto X (che tra le altre cose utilizza il namespace collective.xxx) non era sulla collective.

Digressione
Mi immagino già un certo numero di lettori che a queste prime parole stanno già affilando le armi pensando che questa sia una difesa all'uso di SVN. E' come se già leggessi commenti dire "la collective è roba vecchia, ora è meglio passare a Git".
Non lo nascondo, c'è una recente tendenza di spostare la gestione del codice da Subversion a Git e soprattutto Github.
Se persino il core dello sviluppo Plone si sta muovendo verso questa nuova tecnologia, sono certo al 100% che ci siano ottimi motivi ma qui non si sta parlando di quella collective.

Non deve importare la collective come "scelta tecnologia" ma come "idea": un posto dove lo sviluppatore Plone sappia di poter trovare tutto il codice che gli serve. Un posto dove tutti i plonisti del mondo possano collaborare!

Se poi la collective è un reposiroty di codice fatto con SVN, CVS, Git o come copia manuale del codice... non importa! Se c'è uno standard de-facto, usiamolo. Ad oggi avere il codice Plone sulla collective o su Github, non fa grande differenza!

Torniamo al Prodotto X dell'Azienda X
La mia idea, fin dal principio (e credo sia sempre un'ottima idea) è stata naturalmente quella di:
  • tradurre/far tradurre il Prodotto X in italiano
  • aggiungere la funzionalità richiesta dal Cliente X in modo tale che fosse generica (la nuova funzionalità stessa ovviamente andava ben tradotta)
  • far funzionare il prodotto con Plone 3.3 e superiori (attività banale questa volta)
Come si comincia in questi casi? Ecco un semplice consiglio:
Se possibile, non limitiamoci a sistemare in 10 minuti le cose che ci interessano del Prodotto X, magari sul nostro repository di codice privato o tramite patch, ma forniamo agli sviluppatori dell'Azienda X un branch del loro stesso prodotto.

Dopo questo chiediamo all'Azienda X di integrare le nostre modifiche!
Questo consiglio paga? A mio parere sempre. Un branch o una patch applicata ad un prodotto di terze parti che tenete per voi e per il vostro cliente non porterà a nulla.
La sensazione iniziale è di aver velocemente risolto il problema, ma è un'attitudine che guarda molto avanti nel tempo.

Se avessi preso quella strada avrei impiegato meno tempo (non nascondiamolo), ma prima o poi il Prodotto X avrebbe rilasciato una nuova versione (corretto un bug? Aggiornato il codice ad una nuova versione di Plone? Aggiunto una funzionalità?) e io sarei stato costretto ad "inseguire", a riapplicare la patch alla versione successiva del prodotto.
Ecco che quell'ora o due risparmiate oggi diventano quattro ore domani.

La posizione del codice
L'Azienda X esce però dagli standard, commette quindi un piccolo peccato. Il Prodotto X è open source ma il codice non si trovava sulla collective.

Scopro infine che il prodotto è ben documentato e nella documentazione c'è un link al repository del codice: scopro con perplessità che l'Azienda X ha scelto di usare Mercurial e un servizio simile a Github: Bitbucket.

Non so usare Mercurial e non avevo troppo tempo per imparare quei comandi minimi per usarlo tanto da poter creare un branch.
Quello che alla fine ho fatto è stato quindi mandare all'Azienda X un file di patch con le modifiche.
Per arrivare a quel file, quel prodotto finito, ho dovuto fare qualche tentativo in varie direzioni, che alla fine si sono tradotti in... una copia del prodotto nel nostro repository aziendale (questo perché avere il codice sotto controllo di versione permette di tornare sui propri passi con eleganza e velocità).

L'ironia sta tutta qui: la scelta dell'Azienda X di non usare la collective (e la mia incapacità nell'uso di Mercurial ovviamente!) mi ha obbligato ad una serie di operazioni che mi hanno fatto perdere più tempo del dovuto.
Sono convinto che lo sforzo sia stato ben ripagato: la patch è diventata parte del Prodotto X e questo a mio avviso è sempre un buon risultato.

Ecco quindi un nuovo consiglio:
Se il vostro codice è open source ed è nei vostri obbiettivi rilasciarlo, il posto giusto è la collective (+ Github).
L'Azienda Y preferisce un repository interno. Perché?
Sia ben chiaro: lavorare con l'Azienda X è stato molto fruttuoso. La scelta di usare Bitbucket è, come dicevo, un peccato veniale. Il repository era ad ogni modo pubblico e la patch è stata discussa e integrata con estrema velocità.

Esistono motivi per cui un prodotto rilasciato non debba invece rendere disponibile il codice sulla collective?

Non ne trovo. Nemmeno uno!

Non sto dicendo ovviamente che tutto il vostro codice debba essere rilasciato. Sono convinto che una parte di questo (che sia piccola e grande dipende dal vostro tipo di cliente medio) stia bene in posti riservati perché conterrebbe informazioni che riguardano il Vostro Cliente.
La scelta in questi casi è ovviamente il vostro repository, o quello del vostro cliente (nel caso ne abbia uno).

Ma se rilasciate il prodotto, perché non dovreste usare la collective?

Come dicevo, non trovo grandi motivi, ma posso mostrarvi i motivi per cui invece dovreste rilasciare il codice dove tutti possano contribuire.

Fix di bug
Un plonista usa il vostro prodotto e ha trovato un baco. Potrebbe averlo fissato per voi. Una cosa in meno che dovrete correggere voi.
Aggiornamento ad una versione successiva di Plone
Un plonista vuole usare il vostro prodotto e ne crea un branch che lo rende compatibile con una versione di Plone più recente. Quando uno dei vostri clienti, magari gli stessi per cui già avete sviluppato il prodotto, vi chiederanno "funziona con il nuovo Plone", voi potrete dire di sì... e sopratutto dire che qualcun altro ha pagato per quell'aggiornamento. La comunità funziona.
Aggiunta di una funzionalità
Il plonista, di solito tramite un branch, aggiunge al vostro prodotto una nuova funzionalità. Vi pare poco?!
Nuova lingua
Il plonista straniero ama il vostro prodotto e lo traduce nella sua lingua. Il bacino di utenza del vostro prodotto aumenta e con esso la vostra visibilità, o quella della vostra azienda.

Quanto descritto nei casi qui sopra non è qualcosa che ho letto sul "Manuale Etico del Plonista". Sono tutte cose che ho visto succedere... e non una sola volta!

Siate generici, e riciclate
Ecco forse il consiglio più importante.

Il Cliente Y vi chiederà qualcosa di nuovo. La prima cosa da fare è guardarsi intorno. Capire se c'è giù un prodotto che fa al caso vostro. Se lo trovate, Plone (e voi con lui) farete un figurone.

Se questo non accade ma trovate qualcosa di simile, vale quanto detto fin'ora... se la vostra necessità si avvicina al 60/70% o più a quanto fornito da un altro prodotto, valutate se è possibile estendere quel prodotto, o migliorarlo.

Anche in questo caso Plone (e voi) farete una bella figura.

Capitano invece casi in cui dovete praticamente per forza tornare a sviluppare qualcosa di completamente nuovo.

Prima di iniziare a risolvere il problema del Cliente Y a testa bassa pensate se quanto state sviluppando non possa essere reso più generale, e possa quindi essere rilasciato.

Vi è mai capitato, dopo una nuova richiesta, di dirvi "questa richiesta mi è già stata fatta"? Se sì, e non avete reso la soluzione del problema generale, avete perso un'occasione. Il copia/incolla del codice da un prodotto ad un altro non è una soluzione molto mantenibile...

Non inserite un nuovo pezzo di codice "a caso" dentro ad un altro prodotto. Non conta se questa nuova funzionalità vi sembra minima. Spendendo poco in più potreste ottenere qualcosa di prezioso, qualcosa che in futuro potrebbero usare altre persone, ed essere migliorato da altri.

Come già detto: quello che spendete oggi vi ritornerà domani. Investimento!

Prendo un esempio tra i tanti che potrei citare: collective.portaltabs. Questo ricalca perfettamente quando descritto sopra.
Abbiamo incontrato vari utenti che:
  • volevano il controllo dei link in testata del sito
  • avevano paura (o non avevano i poteri) per accedere alla ZMI
Senza un prodotto apposito l'unica soluzione è accedere alla ZMI ed imparare come fare. E' facile? Lo è certamente per molti di voi, ma l'utente inesperto non ama la ZMI. ZMI è dove nulla è in italiano, dove si possono fare "cose pericolose" e dove le cose non sono per nulla chiare.

Quel prodotto non fa nulla di speciale, ma fa qualcosa che vari nostri clienti hanno richiesto.

Traduzioni
Un altro grande ostacolo al rilascio al pubblico sono le traduzioni del prodotto. La tentazione di partire in quarta scrivendo le varie label e descrizioni della vostra interfaccia grafica direttamente in italiano può essere forte.

Molto spesso quello che si ottiene è quindi un buon prodotto, probabilmente anche riutilizzabile e riutilizzato tra vari clienti e installazioni, ma che non può essere rilasciato pubblicamente.

Le prime volte che vi scontrate con il sistema di traduzioni di Plone può sembrare una procedimento molto lento. Ancora una volta: è un investimento. Ad oggi per me è diventato normale scrivere tutto in inglese e tradurre tutto velocemente alla fine.
Fare le traduzioni dei vostri prodotti in Plone è facile, davvero facile (ho scoperto di recente che con la nuova versione 4.1 non ci sono alcuni problemi che ho riscontrato in questi anni) e richiede un tempo aggiuntivo piuttosto misero.

Se fare questo, il vostro prodotto potrà diventare qualcosa che tutta la comunità potrà riutilizzare ed aiutarvi a mantenerlo.

Plonista avvisato...

Sunday, June 12, 2011

z3c.jbot: magical with your skins resources

This article is the consequence of a small comment of David Glick at Stack Overflow.

If you never met before z3c.jbot, let simply say: this is a toy that commonly make happy the themer of your team. It simplify a lot the customization of a single template inside the Plone universe.
The use of z3c.jbot give you some little performance cost, and commonly you can do stuff without it, however it's use require less development time most of times.

What I learn from the David's comment, is what the official documentation of z3c.bot report in a simple sentence:
CMF objects
Any skin-object (e.g. images, templates) on the file system (directory views) can be overridden.
What is amazing there in the part that say "Any skin-object". Later we will show two great examples.

Complex skins layers subfolders
Maybe Plone 5 (or 6, or 7) won't use the CMF skins technology. However today the CMF skins is still a reality.
What's the problem of skins? Commonly no-one: customize an object in the skins layer is very simple, but only if the object itself isn't in a skin subfolder.
What's the problem with subfolders? Well, the problem is always the customization. To customize the object in the sublayer "/foo/object", you need to customize also the foo folder, putting all this in your personal skins layer. But in this way you also need to customize all other skins object in the foo folder.

So why a product developer sometimes register a single skins layer with subfolders instead of register multiple layers, or keep all resources in a flat simple layer?
Because sometimes you need to put into Plone external resources, that commonly are found as a folder tree structure.
One of the most simple example can be the need of include into Plone an advanced JavaScript product, that is released with static HTML file, images and CSS: all of those resources are already configured to be used keeping this structure. Change this product configuration can be painful.

I will show two main examples:
  • TinyMCE
  • Archetypes widgets
Both examples are available in a collective product there:
https://svn.plone.org/svn/collective/collective.examples.jbotforskins/trunk/

Customizing TinyMCE
Today customizing TinyMCE can be tricky. Maybe you want to change only a little part, maybe a single JavaScript, but the product register a simple skins layer named tinymce, that contain:
langs (dir)
plugins (dir)
resolveuid.py
themes (dir)
tiny_mce_init.js
tiny_mce.js
tiny_mce_plone3.css.dtml
tiny_mce_popup.js
tiny_mce_src.js
tinymce_wysiwyg_support.pt
utils (dir)
wysiwyg_support.pt
I had only a couple of experiences customizing TinyMCE and I was forced to develop a new plugin both the times. Why? Because commonly you need to change something not in the skins layer root (where customizing is easy), but in one of the plugins subsection.

A way can be inject you modification using JavaScript, but this can be really tricky, because TinyMCE WYSIWYG editor is generated using JavaScript and loading an IFRAME (sometimes more that a single IFRAME, if you need to change something in the plonelink or ploneimage plugin).

So, as I sayd, a way is to develop another plugin, that (for example) will replace the basic plonelink ones. You need copy and paste all the plugin code, then you change what you need.
More or less you have created a plugin branch... when another version of TinyMCE will be released, you are forced to check your code for find any changes you need to merge in your plugin...

Let now show how z3c.jbot helps. For example, we simply add an alert JavaScript message inside one of the TinyMCE file. In this way can be really simple to see if our changes are taken (because using TinyMCE we see an alert every time).

We will take only this file into consideration:
Products/TinyMCE/skins/tinymce/plugins/plonelink/editor_plugin.js
How to change only this file without replacing the whole plugin?

First of all we add a Plone product to our buildout. The product can be really simple so we need only a ZCML registration, a folder and out customized JavaScript source.

First of all, we register the z3c.jbot folder.

After that we simply put in the jbot_templates folder the customized JavaScript source. The magic of z3c.jbot is use the name of the customized file for know what to customize. This name must reflect the canonical name of the original file, so:
Products.TinyMCE.skins.tinymce.plugins.plonelink.editor_plugin.js
Very easy! We customized a single file without creating a clone-plugin!

Archetypes widget
Another evil task of the Plone universe is when you need to customize an archetype widget. Why? I bet you know the answer... because all widgets are stored in a skins subfolder:
skins/archetypes/widgets/...
Change the template used for a widget is simple. Create a new template for a widget is easy... however is not so easy to change the default template for all widget of a kind.

Let show an example: imagine you need to customize the file widget, that is all stored in the file.pt template:
Products/Archetypes/skins/archetypes/widgets/file.pt
As before, we can simply put our customized file in the jbot_templates folder. The file name will be:
Products.Archetypes.skins.archetypes.widgets.file.pt
What we can put in the new widget? For example we could like to replace the HTML file upload with a fantastic HTML 5 Drag&Drop Javascript widget. All this can be obtained providing a customized file.pt templates.

Again, this is simple, but only thanks to z3c.jbot!

Sunday, May 29, 2011

JavaScript is (like) a compiled language

False! JavaScript is not a compiled language.

However now that I get your attention, I want to introduce a programming technique that came (again, as my last post) from the great "Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries" (yes, I'm keep reading it).

The technique is introduced for tasks like producing functions that must be cross-browser, but the meaning behind is something more.

Function, Rewrite Thyself!

What can be the simplest way to obtain a cross-browser piece of code?
var crossBrowserMagicFunction = function() {
// some lines of code to know what browser we have
var browser = getBrowserType();
// now take some action
if (browser==='firefox') {
// do something in the Firefox way
...
} else if (browser==='chrome') {
// do something in the Google way
...
} else if (browser==='opera') {
// do something in the Opera way
...
} else if (browser==='ie') {
// do something in a crappy way
...
}
...
}
The hypothetical getBrowserType function is a example function that (maybe) perform some checks to know what browser is the one used by the visitor (who of you know the window.navigator.appCodeName can suggest that this getBrowserType function call simple this primitive code, however the "browser sniffing" is always best choice).

What's wrong with this approach? Every time we call crossBrowserMagicFunction we are repeating from scratch all the tests from knowing what kind of browser we are using.

To simplify this and perform less call to the same code we can obviously move all the code that check the browser type out of the function. You can suggest to perform this check only one time, when the page has been loaded, then save the result into a global variable (I'll use jQuery syntax for this example).
$(document).ready(function() {
// some lines of code to know what browser we have
...
BROWSER_TYPE = ...;
})
...
var crossBrowserMagicFunction = function() {
var browser = BROWSER_TYPE;
// now take some action
if (browser==='firefox') {
// do something in the Firefox way
...
} else if (browser==='chrome') {
// do something in the Google way
...
} else if (browser==='opera') {
// do something in the Opera way
...
} else if (browser==='ie') {
// do something in a crappy way
...
}
...
}
This can seems a final solution, but in last years the approach to perform "something" knowing exactly what kind of browser the visitor is using is becoming quickly obsolete.
A lot of JavaScript guides suggest you to perform instead a "browser features sniffing" operation.

Why?
Because you don't want to know what browser the visitor is using (who cares?). You want to know if (for example) the browser is able to play a music resource using HTML 5 support!
Even jQuery has deprecated old jQuery.browser features, suggesting to use instead jQuery.support. This approach also make your code compatible with future versions of a browser (a check that today works only with Firefox maybe tomorrow will work in IE 11).

What change this in our example?
Well: this invalidate the use of a global BROWSER_TYPE information. Our code can be full of functions crossBrowserMagicFuction functions, like:
  • playSound
  • playVideo
  • dragDrop
  • ...
... checks for browser features sniffing can (will) sniff for different features in different functions.

So we go back to the original code: inside the function we will perform our checks.
Now we must find a way to limit the problem of efficiency left: we need a way to perform the browser features checks only a time (at least, one time per function).

This is the way:
var crossBrowserFunction = function() {
function setup() {
... // let know what this browser is able to do
}
function doSomething () {
... // do really something info taken from setup
}
setup();
return doSomething;
}();
Done!

Just explain this a little: crossBrowserFunction is a self-invoking function, that create a JavaScript closure inside. A self-invoking function is defined and immediately called.

The two internal functions are private, but the second function doSomething are returned from the calling, so became public (note that we return doSomething, not the call of doSomething... we don't have any "()" at that line). So the result of defining crossBrowserFunction is obtaining a function that performed some check before.

In this way all the browser feature sniffing stuff is performed only one time.

Also important for me is the closure itself. Every variable defined inside the self-invoking function is available when we call later crossBrowserFunction, because in facts this is a reference to a function defined inside the closure!

For this reason I taken this title for the post.
This programming way remind me the behavior we have in a compiled language:
  • The "compile time" there is the defining an call of the self-invoking function.
  • Every use of the function obtained will be faster (no runtime check of browser features) and optimized for the current browser (platform).