28 agosto 2009

jQuery + Drupal: search box a scomparsa

Quello che mi accingo ad esporre è un po' un hack, ma è molto carino e comodo.

Drupal è il mio CMS preferito ad oggi, anche se il rapporto è una specie di amore/odio. Ad esempio, essendo la maschera di ricerca un blocco, di default non se ne può avere più di una istanza per pagina.

Ora, generalmente questo limite non è un grosso problema. Però io per Gothic Network volevo una voce nel menu principale che consentisse di ricercare senza dover accedere ad un'altra pagina, e che non alterasse il layout esistente. Un search box era già presente più in giù nella pagina, scrollando, nella colonna di destra.



Una soluzione potrebbe essere stata utilizzare la funzione module_invoke per inserire "a mano" il blocco del modulo search nella pagina, inserendo un minimo di php nei template.
Vedi ad esempio questo post (oppure questo) per capire come usare questo semplice metodo.

Io ho voluto usare il mio amato jQuery, visto che avevo anche in mente di rendere la maschera "a scomparsa", cioè che comparisse soltanto all'attivazione di un link.

L'idea quindi è questa: avere una voce dei primary links chiamata "ricerca" che funge da interruttore per la comparsa/scomparsa un search box (piuttosto che puntare alla pagina "search").

Affinché quanto esporrò funzioni, è necessario che il blocco "Search" sia stato attivato da qualche parte nella pagina.

Questo script è stato sviluppato su un'installazione di Drupal versione 5.

1. Creazione del link
Aggiungere una voce ai primary links è semplice, si va in admin/build/menu e si sceglie "aggiungi voce": io ho chiamato la mia "ricerca", facendo caso che fosse l'ultima della lista (attribuendogli cioè un "peso" elevato). Importante ricordarsi di specificare come indirizzo di link "search", affinché in caso di non abilitazione del JavaScript, l'utente sia correttamente ridirezionato alla pagina di ricerca avanzata. Questo si chiama "degrading gracefully".

2. Studio del codice HTML
Prima di costruirci il nostro searchbox con jQuery, diamo un'occhiata alla struttura della maschera di ricerca creata dal modulo "Search" di Drupal.

<div class="block block-search" id="block-search-0">
 <h2 class="title">Ricerca</h2>
 <div class="content">
 <form action="/"  accept-charset="UTF-8" method="post" id="search-block-form">
  <div><div class="container-inline">
   <div class="form-item" id="edit-search-block-form-keys-wrapper">
    <input type="text" maxlength="128" name="search_block_form_keys" id="edit-search-block-form-keys"  size="15" value="" title="Inserisci i termini da cercare." class="form-text" />
   </div>
   <input type="submit" name="op" id="edit-submit" value="Cerca"  class="form-submit" />
   <input type="hidden" name="form_token" id="edit-search-block-form-form-token" value="ae2b5565789a051ff48bb23e0eac3970"  />
   <input type="hidden" name="form_id" id="edit-search-block-form" value="search_block_form"  />
  </div></div>
 </form>
 </div>
</div>


La maschera di ricerca Drupal è ovviamente un form, con una serie di tag <input> che dovremo riprodurre totalmente e fedelmente nel nostro script, se vogliamo che funzioni. Particolare attenzione deve essere posta al tag form_token (riga 11), che contiene una ID univoca dalla quale dipende il buon funzionamento della ricerca: il dato vincolante è il valore dell'attributo "value".

3. Codice jQuery
Ecco quello che faremo: appenderemo al link creato al punto 1 una nuova maschera di ricerca e la nasconderemo, poi creeremo uno switch che la attivi a piacimento.


3.1 Individuazione del form_token
Prima cosa da fare: ottenere il value del form_token semplicemente andandolo a pescare con il metodo attr(name):

$(document).ready(function(){
/* get form token */
var token = $('#sidebar_right #edit-search-block-form-form-token').attr("value");
});


Questo valore va preso ogni volta che si carica la pagina, e per questo è la prima istruzione che diamo con lo script.

È importante specificare la sezione della pagina che contiene il searchbox originale, e non quello creato da jQuery. Nel mio caso esso si trova nella colonna di destra.

3.2 Creazione del searchbox
Si individua il link "ricerca" (in questo caso l'ultimo della navlist dei primary links) e vi si crea di seguito un elemento <form> dalla ID #jquerysearch, all'interno del quale inseriamo tutti gli elementi input visti sopra:

$(document).ready(function(){
/* build search box */
$("#navlist li:last a").after("<form action='#' method='post' id='jquerysearch'>");
$("#jquerysearch").append("<input type='text' maxlength='128' size='15' name='search_block_form_keys' id='edit-search-block-form-keys' value='' title='Inserisci i termini da cercare.' />")
.append("<input type='submit' name='op' id='edit-submit' value='Cerca' />")
.append("<input type='hidden' name='form_token' id='edit-search-block-form-form-token' value='' />")
.append("<input type='hidden' name='form_id' id='edit-search-block-form' value='search_block_form' />")
.append("<br /><a id='advancedsearch' href='http://www.gothicnetwork.org/search'>ricerca avanzata</a>")
.append("</form>")
.hide();
});


Per prima cosa, tramite il metodo after(content) si crea l'elemento <form> e gli si attribuisce il suo ID: after() crea l'elemento di seguito a quello selezionato, mentre append(content) (che usiamo subito dopo) inserisce l'elemento all'interno di quello selezionato.
In generale quindi, si usa after() per creare il blocco, e append() per riempirlo, con due istruzioni diverse.

Ogni tag viene appeso con una nuova funzione append(), e l'istruzione termina con un hide() per nascondere la maschera appena creata (hide() in jQuery corrisponde ad un display: none; nei CSS), in quanto deve essere attivata soltanto con il click sul link apposito.

Da notare che l'attributo value del form_token (riga 7) è volutamente vuoto, in quanto quel parametro sarà passato in seguito.

Ho inoltre inserito anche un link diretto alla pagina della ricerca avanzata, specificandone l'ID (che useremo tra poco).

Adesso abbiamo un searchbox nascosto, che necessita di un interruttore per comparire.

3.3 Creazione dello switch
Affniché cliccando sul link "ricerca" compaia il nostro searchbox e non si sia indirizzati sulla pagina della ricerca avanzata, usiamo il metodo toggle():

/* search box switch */
$("#navlist li:last a:not('#advancedsearch')").click(function() {
 $("#jquerysearch").toggle('normal');
 $("#jquerysearch #edit-search-block-form-form-token").attr('value' , token);
 return false;
});


Stavolta il selettore not() è fondamentale per esser sicuri di non selezionare anche il link per la ricerca avanzata, creato poco fa.

La riga 4 serve per passare il valore del form_token preso sopra al punto 3.1 al corrispondente elemento del nostro box.

L'istruzione return false; è fondamentale: serve per impedire al browser di caricare la pagina specificata dal tag <a> selezionato — in sostanza, disattiva il link.

Ecco infine lo script completo:

$(document).ready(function(){
/* get form token */
var token = $('#sidebar_right #edit-search-block-form-form-token').attr("value");
/* build search box */
$("#navlist li:last a").after("<form action='#' method='post' id='jquerysearch'>");
$("#jquerysearch").append("<input type='text' maxlength='128' size='15' name='search_block_form_keys' id='edit-search-block-form-keys' value='' title='Inserisci i termini da cercare.' />")
.append("<input type='submit' name='op' id='edit-submit' value='Cerca' />")
.append("<input type='hidden' name='form_token' id='edit-search-block-form-form-token' value='' />")
.append("<input type='hidden' name='form_id' id='edit-search-block-form' value='search_block_form' />")
.append("<br /><a id='advancedsearch' href='http://www.gothicnetwork.org/search'>ricerca avanzata</a>")
.append("</form>")
.hide();
/* search box switch */
$("#navlist li:last a:not('#advancedsearch')").click(function() {
$("#jquerysearch").toggle('normal');
$("#jquerysearch #edit-search-block-form-form-token").attr('value' , token);
return false;
});
});


Adesso è tutto pronto: basta inserire nel page.tpl.php (o chi per lui) un link allo script appena creato, ed il gioco è fatto.
È consigliabile inserire del CSS per stilare il searchbox conformemente alla pagina in cui è inserito: si può inserire direttamente da jQuery, come stile inline dei vari tag. Io ho seguito questa strada.

Il risultato finale lo potete vedere in una qualsiasi delle pagine di Gothic Network.

Caveat: dicevo che questo script è un po' un hack perché andiamo a creare coppie di elementi con lo stesso ID, e questo non sarebbe affatto un comportamento corretto a livello di codice XHTML. Comunque la validazione della pagina passa, perché gli elementi incriminati vengono creati soltanto all'attivazione dello switch, e lo script funziona a dovere.

Appendice
  • Selezionare una voce menu che non sia l'ultima in lista: usare il metodo [attribute=value] specificando a:href=search.
  • Documentazione sulla customizzazione dei searchbox in Drupal: http://drupal.org/node/45295
  • Se non vogliamo essere vincolati ad usare il searchbox qui costruito soltanto come seconda maschera di ricerca, è necessario generare un form_token univoco.
    Come si evince dal link proposto al punto precedente, si deve creare un tag <input> di questo tipo:

    <input type="hidden" name="form_token" id="a-unique-id" value="<?php print drupal_get_token('search_theme_form'); ?>" />