29 dicembre 2009

Wordpress + jQuery: Newsletter widget

Oggi vedremo un modo estremamente semplice di implementare (from scratch) un piccolo widget in un qualsiasi tema Wordpress, che consente agli utenti di iscrivere un indirizzo e-mail ad un servizio di newsletter. Il tutto sarà realizzato con jQuery, e non è altro che un'applicazione semplificata di quello che ho affrontato nel post precedente, sulla validazione di form con jQuery.

Ciò che andremo a creare l'ho sviluppato per la nuova versione dinamica del sito del Metodo Xiu Zhen Dao, che usa appunto Wordpress come CMS: il widget in questione è visibile nella barra laterale destra, in una qualsiasi pagina:



In questo caso, per rendere la vita semplice a noi e agli utenti, c'è un solo campo da riempire con il proprio indirizzo e-mail, e il widget riesce a validare correttamente questo campo, ad esempio controllando che sia presente il simbolo '@' e almeno un punto '.' (tutto questo sarà approfondito sotto):


I punti nodali sono due:
  1. Creare la struttura del form in un widget testuale
  2. Inserire il codice necessario al funzionamento del widget tramite un file esterno, da aggiungere al file header.php.
1. Markup HTML
Si deve innanzitutto creare la struttura HTML del form, e va inserita in un widget nuovo, di tipo testo: si va nel pannello di amministrazione di Wordpress, nella sezione Aspetto → Widget, e si sceglie la tipologia Testo ("Testo o HTML libero"). Ecco qua il mio widget compilato:



Il campo titolo del widget deve essere riempito a parte, mentre nel corpo del testo ho inserito il codice di un form assolutamente di base:

<div id="newsletter_form">
 Iscriviti alla newsletter del Metodo XiuZhenDao
 <form action="" method="post" id="form_news">
  <fieldset>
   <input id="email_news" name="email_news" type="text" value="la tua e-mail" />
   <input id="submit_news" type="submit" value="iscriviti" />
  </fieldset>
 </form>
</div>
Il CSS utilizzato per formattare il form è praticamente trascurabile, perché basta poco oltre a ciò che è già presente nello stile del tema Wordpress, dove generalmente gli input box e i pulsanti sono già stilati. Questo stile può essere inserito nel documento .css principale del tema, o in un qualunque altro foglio di stile incluso nella pagina dove si inserirà il widget.

/* newsletter widget */
#newsletter_form #email_news{
 width: 130px;
}
#newsletter_form #submit_news{
 margin-top: 5px;
 padding: 2px;
 cursor: pointer;
}
#newsletter_form .catched{
 font-style: italic;
}
La classe .catched ci servirà dopo, nel messaggio di successo.

2. validazione jQuery
È tempo di scrivere lo script di validazione: è sostanzialmente identico a quello che ho usato nel post precedente, ma in più stavolta implementeremo una validazione più professionale per il nostro campo e-mail (visto che è l'unico che abbiamo, stavolta!)

Ecco cosa fa lo script: quando l'utente clicca il pulsante "iscriviti" controlla che il campo sia stato riempito correttamente. Se ciò non si verifica, colora di rosso il campo; altrimenti invia un'e-mail all'amministratore con l'indirizzo inserito e visualizza un messaggio di successo.
(function($){
    $(document).ready(function(){
    
    // newsletter widget
 $('#email_news').click(function() {
  $(this).attr('value', '');
 });
 
 $("#submit_news").click(function() {
 
 // stile errore
 var errorclass = {
  'background' : '#ff435b',
  'color' : '#000',
  'borderColor' : '#000'
 }
 
 // analisi del dato inserito
 var email_news = $("input#email_news").val();
 var ln = email_news.length;
 var atpos = email_news.indexOf("@");
 var dotpos = email_news.lastIndexOf(".");
 
 // validazione campo e-mail
 if ((atpos < 1)||((dotpos - atpos) < 2)||(dotpos == ln-1)) {
  $("input#email_news").css(errorclass).focus();
     return false;
 }

 // passaggio dati, invio e-mail e messaggio di successo
 var dataString = 'email_news=' + email_news;  
 //alert (dataString); return false;
 $.ajax({  
  type: "POST",  
  url: "newsletter.php",  
  data: dataString,  
  success: function() {
   $('#newsletter_form').html("
"); $('#message_news').html('

Grazie! Hai iscritto alla newsletter

').append('').hide(); $('#message_news .catched').html(email_news); $('#message_news').fadeIn('slow'); } }); return false; }); }); })(jQuery);
Diamo una scorsa alla prima parte del codice:
  1. La prima istruzione (righe 5-7) serve soltanto per cancellare automaticamente il testo del campo quando l'utente ci clicca sopra per scrivere il suo dato.
  2. La riga 9 intercetta l'evento del click sul pulsante d'invio per dare inizio alle procedure di validazione.
  3. Le righe 12-16 definiscono una classe CSS che serve per modificare l'aspetto del campo input nel caso di errore.
La validazione del campo e-mail controlla che il dato inserito soddisfi le seguenti condizioni:
  1. Non deve essere nullo
  2. Deve contenere almeno un carattere '@' e almeno un punto '.'
  3. Il carattere '@' non deve essere all'inizio del dato inserito
  4. Il punto '.' deve essere ad almeno un carattere di distanza dopo '@' ma non può stare per ultimo.
Per fa questo, prima di tutto si memorizza il dato inserito dall'utente col metodo val(), e poi si esamina:
  1. se ne calcola la lunghezza col metodo length (riga 20)
  2. si individua la posizione del carattere '@' col metodo indexOf() (riga 21)
  3. si individua la posizione dell'ultimo punto '.' inserito col metodo lastIndexOf() (riga 22).
A questo punto, si può effettuare la validazione: le condizioni sono tutte singolarmente sufficienti a rendere errato un indirizzo e-mail, quindi sono collegate tra loro con un OR (||).
  1. Poiché il metodo indexOf() restituisce -1 se il carattere non è presente e 0 se è in prima posizione, con il controllo atpos < 1 si vede subito sia se '@' esiste o se è per primo
  2. Con dotpos - atpos < 2 ci si assicura che '@' e '.' non siano consecutivi, e che l'ultimo '.' sia a destra di '@'
  3. Con l'ultima condizione, dotpos == ln-1, si controlla invece se il punto '.' è in ultima posizione.
Questa validazione è molto simile a quella suggerita da w3schools.com nell'articolo JavaScript Form Validation.

Se la validazione non passa, si colora di rosso il campo applicandogli la classe .errorclass (riga 26).

Tutto quello che viene dopo riguarda il passaggio dei dati ad uno script php per l'invio dell'e-mail, ed è stato affrontato nel dettaglio nel post sulla validazione di form con jQuery.
In sostanza, si usa il metodo $.ajax, che ci consente anche di visualizzare un messaggio di successo, senza dover ricaricare questa o altre pagine.
Degno di nota, questa volta, l'inclusione del dato memorizzato nel messaggio di successo per l'utente, a conferma di quanto da lui inserito: basta inserire uno <span> con una classe per riconoscerlo (nel mio caso .catched) e passargli il valore memorizzato, col metodo html(val) (riga 40).

3. script PHP
L'ultima cosa da vedere è lo script che invia l'e-mail, e questo sì che è davvero minimale! Io l'ho scritto così:
<?php

$body = "indirizzo e-mail iscritto: ".$_POST['email_news'];
$header = "From: ".$_POST['email_news'];
 
mail("indirizzo@email.dominio", "Newsletter widget", $body, $header);
?>


4. file da includere
Ricapitolando, dopo aver inserito il codice HTML nel widget (sopra al punto 1), si deve inserire un file contenente lo script jQuery che abbiamo visto sopra (al punto 2) nel template header.php (e comunque all'interno dello <head> di ogni pagina su cui sarà visualizzato il widget), ad esempio in questo modo:
 
 <script type="text/javascript" src="newsletter_validation.js"></script>
Poi si deve mettere lo script php all'indirizzo specificato nel campo url del metodo $.ajax (forse la cosa più comoda è inserire un url assoluto dopo aver piazzato il file, ad esempio nella directory del tema di Wordpress).

06 dicembre 2009

jQuery: custom form validation

Avrei anche potuto intitolare questo post "principi di form validation", perché quello che vedremo è come sia possibile scriversi un semplice script di validazione form partendo da zero.
È possibile utilizzare il famoso plugin di jQuery chiamato appunto Validation creato da Jörn Zaeferrer, che aggiunge in maniera molto semplice delle funzioni personalizzabili per validare un form.
Noi qui non utilizzeremo questa strada, ma guarderemo da vicino quali sono i problemi e le necessità cui si va incontro se si vuole validare un form con jQuery, e ne ricaveremo la flessibilità che si ottiene da una maggiore comprensione.

Il nostro scopo finale è avere un form che viene validato senza il bisogno di caricare un'altra pagina e che invii un'email formattata arbitrariamente, e che al contempo sia sufficientemente flessibile da supportare delle variazioni alla ricetta generale.

L'esempio viene dal form contatti che ho implementato nella "tirata a lucido" del sito www.bfelettrotecnica.it, e la base da cui partiremo è pari pari quella proposta in questo tutorial di Net Tuts+ (a opera di Eric), e che consiglio di leggere comunque, prima di cominciare.



1. Markup HTML
Come si vede dall'immagine, il form contiene numerosi campi, molti obbligatori (ma non tutti), un select, una textarea e anche due radio per una selezione esclusiva: si consente all'utente di scegliere se rappresenta un privato oppure un'azienda, e di riempire dei campi che variano in base a questa scelta.

Ecco il codice del form:

<div id="contactform">
<form action="#" method="post">
 <fieldset>
  <legend>Richiesta Informazioni</legend>
  <ol>
   <li>
    <input id="privato" type="radio" value="privato" name="tipologia" /> <span class="radioin">Privato</span><br />
    <label for="nome">Nome e cognome <i>*</i></label>
    <input id="nome" name="nome" type="text" class="obb" />
    <label id='nome_err' class='error'>inserisci il tuo nominativo</label>
   </li>

   <li>
    <input id="societa" type="radio" value="azienda" name="tipologia" /> <span class="radioin">Azienda</span><br />
    <label for="azienda">Ragione sociale <i>*</i></label>
    <input id="azienda" name="azienda" type="text" class="obb" />
    <label id='azienda_err' class='error'>inserisci la ragione sociale</label>
    <label for="referente">Referente <i>*</i></label>
    <input id="referente" name="referente" type="text" class="obb" />
    <label id='referente_err' class='error'>inserisci il nominativo di un referente</label>
   </li>
            
   <li style="margin-top: 15px;">
    <label for="indirizzo">Indirizzo <i>*</i></label>
    <input id="indirizzo" name="indirizzo" type="text" class="obb" />
    <label id='indirizzo_err' class='error'>inserisci l'indirizzo dell'attività</label>  
    <label style="width: 75px;" for="cap">CAP <i>*</i></label>
    <input style="width: 50px" id="cap" name="cap" type="text" class="obb" />
    <label id='cap_err' class='error'>inserisci il codice di avviamento postale</label>
   </li>

   <li>
    <label for="citta">Città <i>*</i></label>
    <input id="citta" name="citta" type="text" class="obb" />
    <label id='citta_err' class='error'>inserisci il nome della città</label>  
    <label style="width: 75px;" for="provincia">Provincia <i>*</i></label>
    <input style="width: 50px;" id="provincia" name="provincia" type="text" class="obb" />
    <label id='provincia_err' class='error'>inserisci la sigla della provincia</label>
   </li>
            
   <li>
    <label for="telefono">Telefono</label>
    <input id="telefono" name="telefono" type="text" />
   </li>

   <li>
    <label for="email">e-mail <i>*</i></label>
    <input id="email" name="email" type="text" class="obb" />
    <label id='email_err' class='error'>inserisci il tuo indirizzo e-mail</label>
   </li>

   <li>
    <label for="area">Area d'interesse</label>
    <select id="area" name="area">
     <option value="void"> </option>
     <option value="centraline">Centraline di Controllo</option>
     <option value="plc">Programmazione PLC</option>
     <option value="Automazione">Automazione Industriale</option>
     <option value="Quadri Elettrici">Quadri Elettrici</option>
     <option value="Impianti Elettrici">Impianti Elettrici</option>
    </select>
   </li>

   <li>
    <label for="messaggio">Messaggio: <i>*</i></label>
    <textarea id="messaggio" name="messaggio" rows="5" cols="38" class="obb"></textarea>
    <label id='messaggio_err' class='error'>scrivi la tua richiesta</label>
   </li>
  </ol>
 </fieldset>
 <span id="input"><input id="submit" type="submit" value="Invia Messaggio" /></span>
 <span style="font-style: italic;">I dati personali verranno trattati nel rispetto della normativa sulla privacy.</span>
</form>
</div>

I punti degni di nota sono:
  • Nella prima riga, action="#", poiché utilizzeremo jQuery per gestire tutta la parte relativa al passaggio dei dati allo script php che invierà l'email finale.
  • I tag <label> dalla classe error sono dei messaggi di errore che verranno visualizzati qualora il rispettivo campo obbligatorio non sia stato compilato. Essi sono nascosti di default.
  • I campi obbligatori sono denotati come al solito dall'asterisco (*), inserito in un tag corsivo <i>, e dalla classe obb.
  • Il tag <div> che racchiude tutto il form si renderà necessario quando visualizzeremo un messaggio di successo per l'utente.

La classe "error" è dedicata ai messaggi d'errore, mentre la classe "errorinput" è per i campi riempiti erroneamente. Serve qualcosa che salti all'occhio, e in questi casi il colore d'elezione è il rosso. Io ho usato qualcosa di questo tipo, passato attraverso il CSS della pagina:

.error{
 color: red;
 font-style: italic;
 float: left;
}
.errorinput{
 border: 1px solid red;
 background: #ffcbcb;
 color: #000;
 margin-bottom: .5em;
}

La proprietà float mi è necessaria nelle righe con due tag input, per non distruggere il layout del form.

2. jQuery: form validation
2.0 validazione di base

La prima cosa da fare è nascondere i messaggi d'errore e prender possesso dell'evento click sul pulsante di invio del form, per poter eseguire le nostre validazioni, invece di inviare subito i dati:

$(document).ready(function(){

$('.error').hide();

$("#submit").click(function() {

// qui ci va tutta la validazione che si attiva quando l'utente intende inviare il messaggio

}

});

E fin qui siamo praticamente pari pari il tutorial di Net Tuts+.

La validazione che viene ora, prevede di controllare ciascun campo input, e se esso risulta vuoto, visualizzare il corrispondente messaggio d'errore.
Net Tuts+ ha solo tre campi, e quindi scrive la funzione di validazione tre volte, una per ciascun campo: siccome io ho un sacco di campi, ho velocizzato il tutto con un bel ciclo.

Per prima cosa, si inizializza una variabile, dataString, che andrà a contenere tutti i dati da passare allo script mail php, e sarà nella forma:
dataString = [nome_campo_1=] + [valore_campo_1] + [&nome_campo_2=] + [valore_campo_2] + ... + [&nome_campo_n=] + [valore_campo_n];


Poi, per analizzare ogni elemento <input> iterando, si usa il metodo each(callback): per ogni tag <input> (che non sia il pulsante d'invio o uno dei due radio all'inizio, e che non sia uno dei campi disabilitati, come vedremo al punto 4, sotto) e per la textarea lo script deve salvare due informazioni: il suo valore (col dato inserito dall'utente), che si ottiene con il metodo val(), e il suo ID. Con quest'ultimo andiamo subito a creare l'ID del corrispondente messaggio d'errore, aggiungendo il suffisso "_err".

Ecco lo script per queste prime operazioni:

$("#submit").click(function() {

$('.error').hide();

var dataString = "";

$('input[id!=submit][type!=radio], textarea, select').not('input[disabled]').each(function (i){
 var val = $(this).val();
 var id = $(this).attr("id");
 var err = id + "_err";
});

}

Ho inserito l'istruzione che nasconde i messaggi d'errore anche qui per resettare la validazione ogni volta che si preme il pulsante d'invio.

Ora, la validazione in sé: se il campo è vuoto (val == "") e il campo è obbligatorio (cioè detiene la classe obb), è necessario fare due azioni:
  1. Assegnare la classe d'errore "errorinput" (vista prima) al tag <input> per colorarlo di rosso,
  2. Visualizzare il corrispondente messaggio d'errore, tramite l'ID manipolato poc'anzi.

Se la validazione passa, si memorizza il dato inserito dall'utente accodandolo alla variabile dataString.

Inserendo queste operazioni, il ciclo completo diventa come segue:

$("#submit").click(function() {

var dataString = "";

$('input[id!=submit][type!=radio], textarea, select').not('input[disabled]').each(function (i){
 var val = $(this).val();
 var id = $(this).attr("id");
 var err = id + "_err";

// notifica dell'errore per i campi obbligatori
 if ((val == "") && ($(this).is('.obb'))) {
  $(this).addClass('errorinput').focus();
  $("label[id=" + err + "]").slideDown();
     return true;
 } else {
  $(this).removeClass('errorinput'); // rimozione cautelare della classe errore

// memorizzazione dato valutato
  if (i==0) {
   dataString = id + '=' + val;
  } else {  
   dataString = dataString + '&' + id + '=' + val;
  }
 }
});

}

});

Note su quanto inserito:

  • Sono necessarie due dichiarazioni per la variabile dataString, perché alla prima iterazione (i==0) non si deve anteporre il simbolo '&' al nome del dato che si memorizza.
  • Il messaggio d'errore, nel mio form, è individuale per ciascun campo. Se avessimo usato un unico tipo di messaggio per tutti i campi, il classico "questo campo è obbligatorio" (come fa Net Tuts+), si sarebbe potuto generarlo in questo punto dello script, risparmiando markup nel corpo della pagina.
  • Inserire un return true in questo punto significa dire al calcolatore di proseguire con le altre iterazioni del ciclo, senza fermarsi qui (cosa che si sarebbe ottenuta dichiarando return false): a livello pratico, questo vuol dire che tutti gli errori verranno visualizzati contemporaneamente (tutti i campi incompleti diventano rossi e si visualizzano i messaggi corrispondenti). Con return false si evidenzia un errore alla volta, come nel caso del tutorial di Net Tuts+.

Quanto visto finora è sufficiente a validare un form con un qualsiasi numero di campi, riuscendo a distinguere tra valori obbligatori o meno.

2.1 Radio buttons

Adesso si deve implementare la funzione dei pulsanti "radio" che consentono di scegliere tra "privato" e "azienda".
Il concetto è che l'uno esclude l'altro, e quindi se si riempie il campo di "privato" non si riempiono quelli di "azienda", e viceversa. Farlo capire al validatore è importante, perché sono tutti campi obbligatori!

In questo caso lo script deve solo ricordare all'utente di effettuare la scelta, la validazione dei campi risultanti è già stata implementata sopra.

Pertanto aggiungiamo un paio di righe all'inizio della funzione:

$("#submit").click(function() {

var dataString = "";

if ( (! $('#privato').is(':checked')) && (! $('#societa').is(':checked')) ) {
 $('.radioin').css('color' , 'red').append('&nbsp;&larr;');
 $('ol li:first').after('scegli se rappresenti un privato o un\'azienda');
 $('#radioerror').hide().slideDown('normal');
 return false;
}

//segue ciclo di validazione come visto sopra


Lo script non fa che controllare se nessuno dei radio è cliccato, allorché colora di rosso i titoli (e ci appende pure una freccia per meglio evidenziarli) e visualizza un messaggio.
Il return false fa sì che lo script si interrompa e nessun'altra validazione venga effettuata se l'utente prima non compie la sua scelta: i campi da validare infatti cambiano, come abbiamo detto.

3: Invio email
3.0 Preliminari
La parte di invio dell'email tramite un file php esterno è esattamente uguale a quella esposta nel tutorial di Net Tuts+, che fa utilizzo del metodo $.ajax.

Prima però di passare i dati al php, si deve inserire un'istruzione che blocchi lo script se esistono ancora errori nella compilazione del form:

if ($('.error').is(':visible')) {
 return false;
}

Poi, in fase di sviluppo, è utilissimo il suggerimento di Net Tuts+ di inserire un alert che stampi il contenuto di dataString, per controllare che siano stati memorizzati correttamente tutti i dati necessari:

alert (dataString); return false;

Questa riga verrà ovviamente commentata nella versione finale dello script!

3.1 Posting dei dati con jQuery
Adesso possiamo finalmente vedere come passare i dati all'invio email e visualizzare un messaggio di "successo" per l'utente:

$.ajax({
 type: "POST",  
 url: "email.php",  
 data: dataString,  
 success: function() {
  $('#contact_form').html('
'); $('#successo').html('

messaggio inoltrato correttamente

') .append('

sarai contattato appena possibile.

').hide().fadeIn('slow'); } }); return false;

L'opzione type corrisponde in questo caso all'attributo method del tag <form>, e viene settata a POST.
L'indirizzo del file php è relativo alla pagina html dove viene eseguito lo script.

L'opzione success ci consente di scrivere una semplice funzione che genera un messaggio di "inoltro corretto", e si fa semplicemente svuotando il <div> che racchiude il form, sostituendo quest'ultimo con un paio di righe: il metodo html(val) sostituisce tutto il contenuto dell'elemento selezionato con dell'HTML specificato. Nel nostro caso ci mettiamo un blocco che riempiamo con il messaggio scelto. L'ID assegnata al blocco ci consente di stilarlo con del CSS.

Il return false che chiude il nostro script di validazione serve per impedire al pulsante submit di eseguire la sua azione: tanto abbiamo già fatto tutto noi!

3.2 Script php
Lo script php è davvero semplice:

<?php

if ($_POST['azienda']) {
 $body = "Tipologia: Azienda\n\nRagione sociale: ".$_POST['azienda']."\nReferente: ".$_POST['referente'];
} else {
 $body = "Tipologia: privato\n\nNominativo: ".$_POST['nome'];
}

$body .= "\n\nIndirizzo: ".$_POST['indirizzo']."\n".$_POST['cap']." ".$_POST['citta']." (".$_POST['provincia'].")\n\nTelefono: ".$_POST['telefono']."\n\nArea d'interesse: ".$_POST['area']."\n\nMessaggio: ".$_POST['messaggio'];

$header = "From: ".$_POST['email'];

mail("indirizzo@email.dominio", "modulo contatti", $body, $header);
?>

La condizione serve soltanto per formattare l'email in modo diverso a seconda che sia stato scelto "privato" oppure "azienda"; poi si aggiungono tutti gli altri campi, che non cambiano.

Quello che è elencato in body va a finire nel corpo dell'email, mentre lo header ci consente di specificare l'indirizzo email dell'utente, che il client email userà ad esempio per "rispondere" al messaggio.

Con la funzione mail si invia l'email all'indirizzo specificato. Il secondo parametro è il subject dell'email, mentre il terzo ed il quarto passano i dati arrangiati prima.

4: Tocchi finali
L'unica cosa indispensabile da fare ancora è disabilitare i campi cui l'utente rinuncia effettuando la scelta tra "privato" e "azienda", perché usiamo questo attributo per escluderli dalla validazione.
E poi, visto che ci siamo, aggiungere qualche funzionalità in più a quei radio buttons non guasta.

Poiché questo deve avvenire prima della validazione, queste istruzioni saranno inserite nello script prima dell'evento click sviluppato nei punti precedenti.

L'evento ideale da intercettare è il cambiamento di stato dei pulsanti, e il metodo change() è proprio quello che fa al caso nostro; vediamo ad esempio cosa succede quando l'utente clicca sul pulsante per scegliere la categoria "privato":

$('#societa').change(function () {
 $('#nome').removeClass('obb').removeClass('errorinput').attr('disabled' , 'disabled');
 $('label[for=nome] i').empty();
 $('label[for=nome]').css('color' , '#999');
 $('#azienda, #referente').addClass('obb').removeAttr("disabled");
 $('label[for=azienda] i, label[for=referente] i').html('*');
 $('label[for=azienda], label[for=referente]').css('color' , '#000'); 
 $('.radioin').css('color' , '#000');
 $('.radioin span').remove();
 $('fieldset li:eq(0) .error, #radioerror').slideUp();
})

Le righe 2, 3 e 4 agiscono sui campi cui si rinuncia: essi diventano facoltativi e disabilitati (non ci si può scrivere); per maggiore chiarezza cancello anche l'asterisco e rendo la scritta più "sbiadita" con del grigio, e tolgo eventuali errori di validazione.
Le righe 4, 5 e 6 ripristinano lo stato obbligatorio dei campi prescelti (che potevano essere diventati facoltativi per un'altra scelta privato/azienda).
Le righe 7, 8 e 9 infine resettano eventuali notifiche di errore da parte della validazione.
In questo modo l'utente può cambiare idea quando vuole, e la validazione si adegua.

La stessa funzione, invertendo i riferimenti, si ricopia per l'altro pulsante, e il gioco è fatto.

Un extra interessante da inserire è la disabilitazione del pulsante d'invio mentre lo script sta processando i dati, così, nel tempo tra la pressione del pulsante e la visualizzazione del messaggio di successo, nessun utente particolarmente impaziente potrà inviare il form più di una volta.
Questo è proprio un voler essere perfezionisti, perché con script del genere il tempo di attesa è praticamente nullo! Confesso che questo me lo ha fatto venire in mente un commento al tutorial di Net Tuts+...

Basta disabilitare il pulsante prima della chiamata di $.ajax():

$('#submit').attr('disabled' , 'disabled');

È bene però riabilitarlo se la pagina viene ricaricata, per consentire all'utente di inviare un nuovo messaggio: questo si fa inserendo l'istruzione come prima cosa nel codice jQuery:

$(document).ready(function(){

$('#submit').removeAttr("disabled");

// e segue tutto il resto...

5: script completo
Ed ecco infine lo script completo, ottenuto mettendo insieme tutti i segmenti esaminati.

$(document).ready(function(){

/* form validation */

$('#submit').removeAttr("disabled");
$('.error').hide();

$('#privato').change(function () {
 $('#azienda, #referente').removeClass('obb').removeClass('errorinput').attr('disabled' , 'disabled');
 $('label[for=azienda] i, label[for=referente] i').empty();
 $('label[for=azienda], label[for=referente]').css('color' , '#999');
 $('#nome').addClass('obb').removeAttr("disabled"); 
 $('label[for=nome] i').html('*');
 $('label[for=nome]').css('color' , '#000');
 $('.radioin').css('color' , '#000');
 $('.radioin span').remove();
 $('fieldset li:eq(1) .error, #radioerror').slideUp();
})
$('#societa').change(function () {
 $('#nome').removeClass('obb').removeClass('errorinput').attr('disabled' , 'disabled');
 $('label[for=nome] i').empty();
 $('label[for=nome]').css('color' , '#999');
 $('#azienda, #referente').addClass('obb').removeAttr("disabled");
 $('label[for=azienda] i, label[for=referente] i').html('*');
 $('label[for=azienda], label[for=referente]').css('color' , '#000'); 
 $('.radioin').css('color' , '#000');
 $('.radioin span').remove();
 $('fieldset li:eq(0) .error, #radioerror').slideUp();
})


$("#submit").click(function() {

$('.error').hide();

var dataString = "";

if ( (! $('#privato').is(':checked')) && (! $('#societa').is(':checked')) ) {
 $('.radioin').css('color' , 'red').append('&nbsp;&larr;');
 $('ol li:first').after('scegli se rappresenti un privato o un\'azienda');
 $('#radioerror').hide().slideDown('normal');
 return false;
}

$('input[id!=submit][type!=radio], textarea, select').not('input[disabled]').each(function (i){
 var val = $(this).val();
 var id = $(this).attr("id");
 var err = id + "_err";
 if ((val == "") && ($(this).is('.obb'))) {
  $(this).addClass('errorinput').focus();
  $("label[id=" + err + "]").slideDown();
     return true;
 } else {
  $(this).removeClass('errorinput');
  if (i==0) {
   dataString = id + '=' + val;
  } else {  
   dataString = dataString + '&' + id + '=' + val;
  }
 }
});
if ($('.error').is(':visible')) {
 return false;
}

//alert (dataString); return false;

$('#submit').attr('disabled' , 'disabled');

$.ajax({
 type: "POST",  
 url: "email.php",  
 data: dataString,  
 success: function() {
  $('#contactform').html('
'); $('#successo').html('

messaggio inoltrato correttamente

') .append('

una risposta sarà inviata prima possibile.

') .append('') .hide().fadeIn('slow'); } }); return false; }); });

Ultima raccomandazione
Utilizzare esclusivamente l'ultima versione di jQuery, perché ad esempio nella versione 1.3.2 c'è un bug con IE8 (e chi se non lui?) che interpreta alla rovescia il check sulla visibilità degli errori, rendendo lo script inutile.

Ulteriori sviluppi
Per andare avanti da qui, si può passare alla validazione avanzata, ad esempio com'è implementata dal plugin Validation, che distingue campi numerici, email, url, ecc. e avverte l'utente quando i dati inseriti non sono conformi al formato atteso.
Il bello di questo è che avviene mentre l'utente sta digitando, e non c'è bisogno di aspettare la pressione del pulsante d'invio.
Questi sono ulteriori passi avanti nell'ottica del web 2.0, ma una volta inquadrati i concetti di base di cui sopra, non è difficile crearsi un prodotto su misura per le proprie necessità.

30 novembre 2009

Minimal poster design

Ho cominciato a creare una serie di poster minimali, con l'intenzione di comunicare un solo concetto per volta, spogliandolo di ogni complessità decorativa o tecnica, e di ogni compiacimento estetico superfluo.

I concetti che cerco di esprimere qui sono legati alla filosofia taoista, perché a loro ben si adatta, per la caratteristica "semplicità" quasi austera del ricercato ritorno alla natura primaria, un metodo di comunicazione più che altro non verbale e spoglio di orpelli.
Poi in realtà se andiamo a vedere le opere d'arte figuativa originali della Cina antica, esse sono particolarmente cariche di dettagli minuti e precisissimi, e sono tutt'altro che minimali, in tal senso. Ma la loro essenzialità non è quella che si intende oggi quando parliamo di "design".

La filosofia taoista cui mi ricollego qui è quella che studio seguendo il Metodo XiuZhenDao, per cui mi occupo, tra l'altro, della parte web.

Più o meno negli stessi giorni in cui ho dato inizio a questa serie, ho scoperto powertotheposter.org (dopo aver visto l'interessantissimo documentario The Corporation), e alcuni dei lavori che sono lì hanno alle spalle un concetto di design simile.

Ad esempio, ho riadattato un poster ivi pubblicato per esprimere una delle frasi più famose del Tao Tê Ching:

L'Uno generò il Due,
il Due generò il Tre,
il Tre generò le diecimila creature.
Tao Tê Ching, XLII



Nel mio poster, l'Uno è il foglio bianco, nel quale si divide il due, Yin e Yang, che nella loro relazione determinano il Tre (rappresentata dalla linea ondulata che li separa e li unisce). Le "diecimila creature", cioè le essenze del mondo manifestato, sono le innumerevoli freccie, proiettate in ogni direzione.
Questo si ricollega ai vari miti cosmogonici, come Prajapati che viene fatto a pezzi e lanciato in ogni direzione dello spazio, e in questo caso alla leggenda cinese che vuole che gli uomini siano stati plasmati da una coppia di dei dalle sabbie di diverso colore del Fiume Giallo.

Questo poster è per forza di cose il primo della serie.


Un altro, cerca di esprimere l'equilibrio di un uomo che si sia ricongiunto con l'armonia della natura, e raffigura un personaggio che sta seduto in tranquillità alla sommità di un monte, incurante delle nuvole minacciose al di sopra di lui e dell'oceano al di sotto, che minaccia di inghiottirlo.
A testimonianza del suo equilibrio il fatto che l'uomo si trova nel centro, a metà tra cielo e terra (come vuole la Grande Triade), al suo posto naturale.

Uno dei concetti più importanti del taoismo è quello di "vuoto": uno dei poster cerca di comunicare questo concetto sfruttando la famosa metafora della tazza, che finché è vuota può ricevere il tè. Mi rifaccio qui alla famosissima storia del Maestro zen e dell'uomo di scienza, che, non a caso, è anche la prima delle "101 storie zen":

Nan-in, un maestro giapponese dell'èra Meiji (1868-1912), ricevette la visita di un professore universitario che era andato da lui per interrogarlo sullo Zen.

Nan-in servì il tè. Colmò la tazza del suo ospite, e poi continuò a versare.

Il professore guardò traboccare il tè, poi non riuscì più a contenersi. «È ricolma. Non ce n'entra più!»

«Come questa tazza,» disse Nan-in, «tu sei ricolmo delle tue opinioni e congetture. Come posso spiegarti lo Zen, se prima non vuoti la tua tazza?»
101 Storie Zen, 1, ed. Piccola Biblioteca Adelphi




In questo poster ho ritratto la mia piccola teierina di terracotta (quella che uso per i tè pu-erh pressati). Tutti questi poster, a parte il primo, sono disegnati a mano, poi scannerizzati e quindi ricomposti e "inchiostrati" digitalmente.

Per ricreare l'effetto impreciso dell'inchiostrazione manuale, in Photoshop, ho creato delle selezioni dei contorni dell'area da colorare e poi le ho modificate con il comando "refine edge" (alt+cmd+R) usando i massimi valori di smusso e di espansione del bordo.

La selezione risultante viene traformata in un livello di riempimento a tinta unita, magari in blend mode "moltiplica" per un effetto di sovrapposizione degli strati d'inchiostro con i contorni del disegno.


22 ottobre 2009

Post Romantic Empire Final Fest

Questo weekend, il 17 e il 18 Ottobre, ero a Roma in qualità di inviato speciale di gothicNetwork al Final Fest del Post Romantic Empire: una maratona di 30 ore consecutive di musica, dalle 18 di sabato a notte fonda di domenica, presso l'Init, locale a ridosso di un acquedotto romano e accanto alla ferrovia della stazione Tuscolana. Con tanto di pranzo (o Dead Banquet, come dicevano loro) domenicale a base di leccornie frugali varie e alcuni squisiti pomodorini datterini.

La musica presente spaziava dal cantautoriale al dark contemporaneo, con una massiccia presenza di elettronica sperimentale, e un sentore improvvisativo spontaneo e creativo che fa tanto evento indipendente, slegato dalle logiche monetarie più spinte.

Comunque, presto ci sarà uno speciale di gothicNetwork a proposito del Fest, e tutto sarà più chiaro per tutti.

Nel frattempo volevo condividere una diecina di foto fatte durante l'evento: un gioioso necrologio nero e rosa dall'atmosfera amichevole e rilassata come poche volte mi è capitato di sentire in casi del genere, dove artisti e spettatori erano tutti insieme a godersi la festa.











09 ottobre 2009

a Holga trip of sorts (I)










07 ottobre 2009

Asleep, struggling to awake

analisi di un'improvvisazione quasi ragionata

È successo un fatto strano. Ho pubblicato 4 brani su SoundCloud nei mesi scorsi, giusto per provare il servizio e pubblicare qualcosa che secondo me era ingiustamente stato relegato all'oblio, e uno di questi è stato ascoltato ad oggi più di 500 volte, andando ben oltre ogni mia aspettativa, vista la mancanza di pubblicità di questo materiale.

Ho deciso quindi di analizzare questo pezzo, giusto per mostrare com'è nato. Intanto eccolo:

Asleep, struggling to awake di blacktealover

Questo brano faceva parte di quelli che creai per un progetto collaborativo ormai abbandonato di arte visiva + musica (per altre informazioni vedi il primo post di questo blog).

Il tema del pezzo lo definisce il titolo Asleep, struggling to awake: è il sonno, il torpore che non si riesce a spezzare e che tanta parte occupa delle nostre esistenze. Non mi riferisco soltanto, infatti, al sonno notturno, al riposo più o meno meritato (e spesso abusato) nel proprio letto o sul proprio divano; ma bensì a quella sensazione — a volte subdola, a volte quasi drammatica — di essere addormentati di fronte alla realtà, di vivere il momento presente in modo distante, oltre una coltre di nebbia.

Moltissimi autori e tradizioni parlano di questo stato di mancanza di presenza a sé: Herman Hesse, Gurdjieff, il Buddha, i Vangeli... in ogni epoca vi sono stati uomini che si sono accorti di questo stato e che hanno cercato di superarlo.

Il pezzo non vuole arrivare assolutamente a queste altezze psicologiche, filosofiche e spirituali! È stato creato in un'oretta seguendo un impulso sovra-razionale (come tutte le esperienze creative reali), che solo a posteriori ho potuto ricondurre a queste coordinate concettuali, in base alle sensazioni provate durante la creazione del brano e al suo successivo riascolto.

Il pezzo è davvero semplice: solo due linee di chitarra elettrica, di cui una "di contorno". La prima linea, la più importante, si basa su un semplice giro iniziale a nota singola sulla scala di La- Lidio (quarto grado di Mi-). L'effetto di delay stereofonico è stato aggiunto per conferire il senso di straniamento e alienazione, ed è (più o meno) in tempo con la musica.

Tutta la prima parte del pezzo si basa su una semplice figura ritmica cadenzata, per dar spazio al delay e per coltivare la sensazione di torpore, quasi paralizzante, di muoversi in una nebbia densa. La musica non progredisce, né va verso una qualsiasi risoluzione. Le sequenze degli accordi, molto semplici, sono cicliche e possono essere ripetute indefinitamente.
Questo è l'aspetto della ripetizione banale e noiosa di questa modalità dell'esistenza: tutto si ripete come in un dramma teatrale che ogni volta ricomincia daccapo.
L'unico modo per spezzare questa piatta continuità è una risoluzione che cerchi di romperne la ciclicità — e questo è ciò che avviene al minuto 1:23.

Passando all'accordo di Mi- (dominante di La-) in prima posizione, il tentativo di rompere il muro del sonno è rappresentato da degli accenti, colpi vigorosi e ripetuti sulle corde della chitarra, inflitti con molta forza (un fortissimo, in terminologia classica ...ho letteralmente "picchiato" la chitarra e le corde qui!).

La risoluzione e la disperazione del tentativo si rafforzano grazie al fatto che l'accordo viene cambiato solo dopo due battute, risolvendo in Do maggiore che, avendo soltanto una nota di differenza rispetto al Mi- (il Si che passa a Do salendo di un solo semitono) mi consente di mantenere la stessa intensità espressiva ed estetica ma al contempo di sfruttare il passaggio melodico al VI grado, sempre di grande effetto.

Tutta questa breve sezione dovrebbe essere davvero intensa, e forte da togliere il fiato, perché questi momenti si vivono proprio così.

La cadenza lenta degli accenti sottolinea la difficoltà del tentativo, e la grande fatica che comporta. Gli accordi che seguono infatti, sono sempre forti, ma molto più radi, dovuti alla stanchezza e alla disillusione accumulate.

Questa, alla fine, fa sì che tutto ritorni come prima: il progressivo e malinconico ritorno al tema iniziale che comincia al minuto 2:12 simboleggia proprio questo progressivo riabbandonarsi al sonno.
Quando ci si riaddormenta poi, c'è una breve pausa e subito riprende il tema del "sonno" iniziale, che porta il brano ad una conclusione.

La seconda linea di chitarra è stata improvvisata sulla prima, e consta quasi esclusivamente di una nota di Mi (particolarmente il Mi del XII tasto sulla prima corda) suonata con un tempo di metronomo differente da quello della linea principale. Nella sezione del 2:12 suono le terze degli accordi (la nota che determina la natura "maggiore" o "minore" di un accordo) per poter giocare sull'emotività del maggiore/minore.

Ambedue le linee sono improvvisate e sono state registrate in unico take (mi pare il secondo), con una chitarra elettrica collegata direttamente alla scheda sonora.
Gli effetti sono stati aggiunti postumi: più che altro uno stereo-delay e un po' di riverbero.

Purtroppo, a causa della natura improvvisata e della velocità con cui è stato creato, non ho mai scritto lo spartito di questo piccolo pezzettino.

06 ottobre 2009

jQuery: integrazione Fancybox & Tooltip

Questo post tratta l'integrazione di due ottimi plugin jQuery:
  • Fancybox – un clone di Lightbox dall'interfaccia più accattivante e con alcune funzioni ben implementate,
  • Tooltip – un plugin molto versatile per customizzare i tooltip che normalmente si hanno in base all'attributo title dei tag <a>
Questi due plugin si suppongono quindi già installati e configurati.

Lo scopo di questo hack è quello di avere una galleria Fancybox con i titoli delle immagini che possono ospitare link e che mostrano un "tooltip": è quello che ho fatto per la galleria di graphic design su Nâda-Design, in cui alcune immagini hanno un link verso la relativa pagina dell'account deviantArt.
Per far capire all'utente che il link punta verso una pagina deviantART ho inserito un tooltip che mostra il logo della community.



Il fine è quello di poter inserire dell'HTML nel titolo mostrato da Fancybox, e potervi così collegare un link e un tooltip, e nel contempo far anche validare la pagina come XHTML Strict!

1. Inserimento del link nel titolo dell'immagine

Il titolo dell'immagine nella galleria Fancybox è estratto dall'attributo title del link che lancia l'immagine stessa: poiché title supporta come valore una stringa di testo generica, è possibile, in linea di principio, inserirvi dell'HTML (avendo ovviamente cura di sostituire tutti i caratteri speciali con le relative entità), ad esempio:

<a href="#img1.jpg" rel="[fancybox]" title="&lt;a href=&quot;link_esterno&quot; target=&quot;_blank&quot; title=&quot;titolo&quot;&gt;testo_link&lt;/a&gt;&quot;&gt;&lt;img /&gt;</a>

In pratica si è inserito un altro tag <a> completo all'interno dell'attributo title.

Il problema di questo procedimento è che il browser interpreterà comunque l'attributo, e visualizzerà il suo tooltip predefinito con tutti i dati che vi abbiamo inserito... con un effetto particolarmente disdicevole!



Ma poiché questa è una funzione non disattivabile esternamente, non ci rimane altro da fare che abbandonare l'attributo title, e sceglierne un altro: e questa decisione, ovviamente, ci complicherà enormemente la vita...

Non possiamo scegliere l'attributo id, perché supporta solo valori univoci senza caratteri speciali. Scegliamo dunque l'attributo rev: esso, da definizione W3Schools, "specifica la relazione esistente tra il documento linkato e il documento corrente".

Ma le caratteristiche che ce lo fanno scegliere sono essenzialmente due:
  1. Può contenere una stringa di testo,
  2. Non è interpretato da nessuno dei browser principali.

Questo vuol dire che possiamo inserirci tutto quello che vogliamo senza che i browser se ne accorgano, e mantenendo la validazione della pagina.
Comunque anche se il documento valida, non si può certo parlare di contenuto aderente agli standard!

Quindi alla fine un mio tag <a> avrà quest'aspetto:

<a rel="[fancybox]" rev="&lt;a href=&quot;http://blacktealover.deviantart.com/art/Reincarnation-118622078&quot; target=&quot;_blank&quot; title=&quot;.&quot;&gt;reincarnation&lt;/a&gt;" title="reincarnation" href="images/graphics/large/reincarnation.gif"><img src="images/graphics/reincarnation.gif" alt="reincarnation" width="160" height="80"/></a>

Il title del link "aggiuntivo" deve essere non vuoto se vogliamo che il tooltip venga mostrato.

2. Hack di Fancybox
A questo punto si deve andare a modificare il codice di Fancybox per fargli prendere l'attributo rev invece del title come fa normalmente.

Le modifiche sono semplicissime: ci interessa soltanto la sezione di codice dalla linea 60 alla 97 del file jquery.fancybox-1.2.1.js; in particolare andiamo a sostituire la riga 63:

var item = {href: elem.href, title: elem.title};

con il seguente codice:

if ($(elem).attr("rev")){
 var item = {href: elem.href, title: elem.rev};
} else {
 var item = {href: elem.href, title: elem.title};
}

e la linea che originariamente era la 78

item = {href: subGroup[i].href, title: subGroup[i].title};

con questo:

if ($(subGroup[i]).attr("rev")){
 item = {href: subGroup[i].href, title: subGroup[i].rev};
} else {
 item = {href: subGroup[i].href, title: subGroup[i].title};
}

Le due sezioni di codice sono rispettivamente per un'immagine singola e per un gruppo d'immagini.

Ho inserito il check sull'esistenza dell'attributo rev perché così nel caso si decida di non inserire nulla di aggiuntivo nel titolo dell'immagine, Fancybox si comporta come di consueto, andando a pescare il contenuto del title. In questo modo non ci bruciamo la terra dietro le spalle.

Affinché il link funzioni è necessario disabilitare l'opzione hideOnContentClick (che si trova nella lista delle funzioni, in fondo al file dello script) settandola su false.

Adesso, per completare questa prima parte dedicata a Fancybox, basta andare a stilare il nostro link, con del CSS.
Il selettore che ci interessa per andare a colpire il link nel titolo dell'immagine è #fancy_title; ad esempio:

#fancy_title a{
 color: #f9c587;
 text-decoration: none;
}
#fancy_title a:hover{
 color: #fad5a7;
 text-decoration: underline;
}

3. Inserimento del tooltip
Per richiamare un tooltip sul link appena inserito, il modo migliore è usare la funzione di callback di Fancybox.

In fondo al file di Fancybox, jquery.fancybox-1.2.1.js, andiamo ad inserire la funzione che genererà il tooltip, e che chiameremo semplicemente "fancyboxCallback".
Andiamo a dichiararla nell'opzione di callback:

callbackOnShow : tooltipCallback,

e poi, subito sotto, la definiamo:

function tooltipCallback(){
  $('#fancy_title a').tooltip({
   track: true,
   delay: 0,
   fade: 50,
   showURL: false,
   fixPNG: true,
   extraClass: "datip",
   bodyHandler: function() {
    return $("").attr({
     src: "balloon_da.png",
     width: "30",
     height: "33"
    });
   },
   top: -40,
   left: 0
  });
 };

La riga 2 definisce l'elemento su cui creiamo questo tooltip; degli attributi è importante la extraClass, che useremo dopo per assegnare delle proprietà fondamentali, e la funzione passata tramite bodyHandler. È con questo parametro che si può iniettare nel tooltip dell'HTML qualsiasi: in questo caso si riempie il blocco con una immagine.

Nel mio caso, infatti, non voglio che il tooltip visualizzi il testo del title del mio link, ma un'unica immagine (sempre la stessa per questa galleria) che rappresenta il logo di deviantART. Negli attributi che passo al tag <img /> c'è l'immagine precedentemente creata (+ le sue dimensioni).

Per finire, andiamo a sviluppare la classe dedicata, datip: essa si inserisce nel file jquery.tooltip.css e nel mio caso è:

#tooltip.datip{
 background: 0;
 width: 30px; height: 33px;
 padding: 0;
}
#tooltip.datip img{
 position: absolute;
 top: 0; left: 0;
}

La prima parte del codice CSS elimina l'eventuale sfondo presente del tooltip generico della pagina e setta le dimensioni esatte dell'immagine che verrà inserita nel blocco.
La seconda parte riguarda il contenuto del tooltip, che in questo caso è un'immagine, e serve per ottenere un posizionamento perfetto.

È ovvio, data la versatilità di Tooltip e il modo generico in cui abbiamo modificato Fancybox, che in linea di principio è possibile sbizzarrirsi inserendo nel titolo dell'immagine dell'HTML vario e mostrare tooltip con di tutto di più; ad esempio una didascalia estesa con molto testo e/o immagini, o altro.

Come al solito, il limite sono la fantasia e l'estro di ciascuno.

01 ottobre 2009

a traveller's guide in seven steps

1Aim beyond

Niente è mai troppo distante. Se miri alle stelle forse raggiungi solo un lampione, ma almeno ti stacchi dal suolo. Non ci sono distanze che non meritino d'esser percorse.

2Always flow spontaneously


Come fa l'acqua, segui la via della natura. Non pensare troppo, le occasioni non stanno ad aspettare.

L'acqua non è mai inoperosa, né di giorno né di notte. [...] Se le si oppone una diga, si ferma; se le si apre una chiusa, scorre. Si adatta a qualsiasi recipiente, sia esso rotondo, quadrato, o d'altra forma.
Ko Chang Sheng, commento al cap. VIII del Tao Te Ching


3Follow the signs in the stars


Lascia che il caso ti sorprenda: non forzare il tuo punto di vista, cambia i tuoi piani. Stupisciti senza timore.

4Find solace in movement


Fa' che il movimento sia il tuo riposo e il tuo conforto. Giosci del mondo mentre scorre di fronte al tuo volto.

5Look for crossroads and singularities


L'inusuale è perenne fonte di delizia per il viaggiatore. Stimola l'imprevisto cercando le circostanze adatte, e rafforza la tua capacità di adattamento.

6Wear out your shoes


Non rimanere mai fermo, non smetter mai di vagare. La polvere sulle calzature è la medaglia del girovago.

7Never give up


Dietro ogni angolo c'è una delizia sconosciuta. La stanchezza non ci interessa: se non stiamo camminando, stiamo dormendo; in ambedue i casi, che sia sogno o realtà, stiamo viaggiando. O al limite stiamo bevendo una birra.


Queste foto sono tutte state scattate durante un unico viaggio. I concetti che esse accompagnano, invece, si applicano sempre, in ogni momento e in ogni luogo: sono principi che ispirano molte delle mie azioni. Devo per forza tirar fuori la stravecchia metafora dell'esistenza come viaggio? Che noia, sempre a filosofeggiare sulle stesse idee trite e ritrite... questi piccoli suggerimenti vogliono solo essere qualcosa di concreto, che faccia godere al meglio il viaggio in sé, senza perder troppo tempo a pensarci su. Che le occasioni tornano anche, ma dopo un sacco di tempo.

30 agosto 2009

jQuery: sistema di finestre stile windows

Per l'ultima versione di Nâda-Design, ho voluto mantenere l'impostazione ad una sola pagina, ma stavolta rendendola davvero succinta.
C'è un Ouroboros (che in questo caso, tralasciando parzialmente il suo simbolismo tradizionale, sta a raffigurare la creatività, che sempre rinasce e si rinnova) da cui si originano i tre link principali dedicati alle tre aree creative principali di cui mi occupo, più un link informativo. Stavolta non si divaga!

I link non aprono altre pagine, bensì delle finestre sullo stile di quelle dei sistemi operativi, che si utilizzano per esplorare il contenuto del proprio computer.

Le caratteristiche da riprodurre sul web di tali finestre sono:
  1. Trascinabilità, tramite il classico click+drag del mouse;
  2. Sovrapponibilità, con possibilità di portare "di fronte" una finestra che sta sotto ad un'altra, cliccandola;
  3. Ridimensionabilità, trascinandone uno dei bordi o uno degli angoli.
Per il resto, le finestre sono dei blocchi di contenuto <div> posizionati in modo assoluto, ciascuno col proprio z-index, e inizialmente nascosti (display: none).

 
Vediamo nel dettaglio il markup XHTML, il CSS e il codice jQuery necessari per realizzare questo progetto.

1. Codice XHTML

Ciascuna finestra avrà una struttura di questo tipo:

<div class="window" id="finestra1">
 <span class="close">X</span>
 <h2>titolo</h2>
 <p>contenuto</p>
</div>

La classe window serve per specificare gli attributi essenziali comuni ad ogni finestra, mentre l'ID univoca per le caratteristiche variabili, come sfondo e posizione iniziale.
Lo span "close" non è altro che il pulsante di chiusura della finestra, che realizzeremo interamente con jQuery.

Per lanciare le finestre abbiamo bisogno di un elenco di link, ad esempio:

<ul>
 <li><a id="a1" class="btn" href="#">finestra1</a></li>
 <li><a id="a2" class="btn" href="#">finestra2</a></li>
 <li><a id="a3" class="btn" href="#">finestra3</a></li>
</ul>

Vediamo adesso quali sono le caratteristiche fondamentali da attribuire ai blocchi finestra tramite CSS.

2. Proprietà CSS

Quello che segue è il CSS che ho utilizzato sul mio sito:

.window{
 z-index: 10;
 position: absolute;
 width: 500px;
 height: 400px;
 padding: 0;
 color: #000;
 opacity: .95;
 filter: alpha(opacity=95);
 border: 3px solid #555;
 -moz-border-radius-topright: 12px;
 -webkit-border-top-right-radius: 12px;
 border-top-right-radius: 12px;
 -moz-border-radius-bottomleft: 12px;
 -webkit-border-bottom-left-radius: 12px;
 border-bottom-left-radius: 12px;
}

Di tutto questo, le uniche proprietà realmente fondamentali sono position: absolute e z-index. Quest'ultima ci risolverà il problema dell'impilazione delle finestre.
È interessante sfruttare delle capacità avanzate come la trasparenza e i bordi stondati, che migliorano molto l'estetica con un minimo sforzo.
La trasparenza è bene sia minima per non confondere i contenuti con lo sfondo. Riguardo ai bordi stondati, ne accenno in quest'altro post.

Ciascuna finestra, poi, avrà delle caratteristiche personali, come la posizione iniziale all'apertura, lo sfondo, le dimensioni, e quant'altro; ad esempio:

#finestra1{
 background: #fff url(images/bg1.gif) bottom right no-repeat;
 width: 400px;
 top: 120px;
 left: 40px;
}

Per avere una disposizione elegante delle finestre l'una rispetto alle altre, ho scalato di 40px per volta la posizione di ciascuna finestra:

#finestra1{
 top: 120px;
 left: 40px;
}
#finestra2{
 top: 80px;
 left: 80px;
}
#finestra3{
 top: 40px;
 left: 120px;
}

Sono andato "verso l'alto" per ridurre rischi di comparsa di scrollbar con dimensioni ridotte della finestra del browser.

Per il pulsante di chiusura delle finestre, ho usato l'immagine di una "X", con questo CSS:

.close{
 cursor: pointer;
 position: absolute;
 top: 3px; right: 3px;
 background: transparent url(images/close.png) center center no-repeat;
 height: 25px; width: 25px;
 display: block;
 text-indent: -999em;
}

I titoli delle finestre sono stati tutti trattati con una tecnica qualsiasi di image replacement per renderli conformi allo stile generale della pagina.

Adesso si deve stendere il codice jQuery per consentire l'azionamento delle finestre, e conferirgli tutte le caratteristiche d'interattività desiderate.

3. Codice jQuery

3.0 Preliminari: nascondere e pulsante chiusura
A monte di tutto, si devono nascondere le finestre:

$('.window').hide();

Poi, diamo funzionalità al pulsante di chiusura:

$('.close').click(
 function(){
  $(this).parent('.window').hide('normal');
 }
);

3.1 Attivazione delle finestre e funzione "on top"
L'idea è questa: cliccando su un link si apre la finestra corrispondente, ma se essa è già visibile, viene chiusa (i link sono degli switch, in sostanza).
Poiché in seguito renderemo le finestre trascinabili, la loro posizione finale potrà essere differente da quella iniziale, ma io voglio che esse si aprano sempre nello stesso punto deciso sopra tramite CSS.
Inoltre, l'ultima finestra attivata deve passare in cima a tutte le altre.

La prima cosa da fare è capire quale finestra vuole attivare l'utente, e salvarne l'ID in una variabile per identificare la finestra "attiva".

/* window popups launch */
$('.btn').click(function(){
 var active = '#finestr' + $(this).attr("id");
});

Ogni volta che si clicca su un link, nella variabile active si va a memorizzare l'ID della finestra corrispondente, con tanto di "#", perché utilizzeremo subito questa variabile a fini di selezione.
Ad esempio, cliccando sul secondo link (ID = a2) avremo: #finestr + a2 = #finestra2.

Adesso, controlliamo se la finestra è già visibile. Se lo è, la nascondiamo, e finiamo lì. Altrimenti ci sono due cose da fare prima di mostrarla:

  1. Resettare la sua posizione a quella iniziale;
  2. Attribuirgli uno z-index maggiore di tutte le altre finestre per farla andare "on top".

Ecco come si fa:

/* window popups launch */
$('.btn').click(function(){
 var active = '#finestr' + $(this).attr("id");

if($(active).is(':visible')){
 $(active).hide('normal');
 } else {
 switch(active){
  case '#finestra1':
  $(active).css({
   'top' : '120px',
   'left' : '40px'
  });
  break;
  
  case '#finestra2':
  $(active).css({
   'top' : '80px',
   'left' : '80px'
  });
  break;
  
  case '#finestra3':
  $(active).css({
   'top' : '40px',
   'left' : '120px'
  });
  break;
 }

 $('.window').not(active).css('z-index', '10');
 $(active).css('z-index', '100').show('normal');
}
});

Il ciclo switch - case serve per assegnare istruzioni diverse a seconda della finestra, ed è qui che avviene il reset alle condizioni iniziali (che possono anche coinvolgere più proprietà oltre alla posizione).

Le ultime due istruzioni servono per mandare in cima la finestra cliccata e visualizzarla.
Si attribuisce lo z-index di default a tutte le finestre che non siano quella cliccata, poi si dà indice massimo a quella attiva e infine la si visualizza.

Per mandare in cima una finestra già visibile che sia sotto ad altre, basta scambiare i valori di z-index tra le finestre alla pressione del pulsante del mouse:

/* on top windows */
$('.window').mousedown(
 function() {
  $(this).css('z-index', '100');
  $('.window').not(this).css('z-index', '10');
 }
);

L'evento è bene che sia mousedown (esatto, senza l'"on"!) perché ci tornerà utile in congiunzione con l'effetto di trascinabilità che implementeremo subito.

3.2 Trascinabilità
Qui siamo fortunati, perché la trascinabilità è un effetto già presente nella libreria jQuery UI: basta aggiungere due file nello <head> della pagina web:

<script src="http://jqueryui.com/latest/ui/ui.core.js" type="text/javascript"></script>
<script src="http://jqueryui.com/latest/ui/ui.draggable.js" type="text/javascript"></script>

Poi, per rendere le finestre trascinabili, è sufficiente questa dichiarazione:

$('.window').draggable();

3.3 Ridimensionabilità
Io non ho implementato la ridimensionabilità sul mio sito, perché nel mio caso superflua e necessitante un altro approccio di design. Però anche per questa proprietà il passo è breve: basta allegare i file necessari, e dichiarare le finestre "resizable".

I file da aggiungere in questo caso sono:

<link type="text/css" href="http://jqueryui.com/latest/themes/base/ui.all.css" rel="stylesheet" />
<script src="http://jqueryui.com/latest/ui/ui.core.js" type="text/javascript"></script> <!-- già visto sopra -->
<script src="http://jqueryui.com/latest/ui/ui.resizable.js" type="text/javascript"></script>

A questo punto, conviene combinare tre istruzioni in una, e dichiarare subito all'inizio dello script:

$('.window').draggable().resizable().hide();

Adesso si potranno trascinare i bordi delle finestre per ridimensionarle, e comparirà (nell'angolo in basso a destra) una piccola icona il cui trascinamento ridimensionerà la finestra lungo i due assi X e Y contemporaneamente - come una qualsiasi finestra da sistema operativo.

4. Script completo

$(document).ready(function(){

$('.window').draggable().resizable().hide();

/* close button */
$('.close').click(
 function(){
  $(this).parent('.window').hide('normal');
 }
);

/* window popups launch */
$('.btn').click(function(){

 var active = '#finestr' + $(this).attr("id");
 
 if($(active).is(':visible')){
  $(active).hide('normal');
 } else {
  switch(active){
   case '#finestra1':
   $(active).css({
    'top' : '120px',
    'left' : '40px'
   });
   break;
   
   case '#finestra2':
   $(active).css({
    'top' : '80px',
    'left' : '80px'
   });
   break;
   
   case '#finestra3':
   $(active).css({
    'top' : '40px',
    'left' : '120px'
   });
   break;
  }

  $('.window').not(active).css('z-index', '10');
  $(active).css('z-index', '100').show('normal');
 
 }
 
});


/* on top windows */
$('.window').mousedown(
 function() {
  $(this).css('z-index', '100');
  $('.window').not(this).css('z-index', '10');
 } 
);

});

Nota sull'impilabilità
Il semplice metodo proposto per portare "on top" una finestra ha il pregio dell'estrema concisione: due brevissime istruzioni e siamo a posto qualunque sia il numero delle finestre.

Però questa semplicità tralascia un dettaglio: l'ordine delle finestre in secondo piano. Esso infatti non rimane inalterato, ma si ripristina a quello di default a cascata del codice HTML: in sostanza, tra le finestre in secondo piano, l'ultima in ordine di inserimento nel codice della pagina sarà quella che starà sopra a tutte, direttamente sotto a quella "on top".
Ciò accade perché attribuiamo a tutte le finestre in secondo piano lo stesso z-index, a parità del quale vige la regola dell'ordine nel codice.

La soluzione è concettualmente semplice: determinare di volta in volta qual è la finestra con z-index più elevato ed attribuire a quella da mandare "on top" uno z-index maggiorato di una unità, lasciando tutti gli altri inalterati.
Questo però richiede un po' più di scripting, perché si deve iterare tra gli attributi delle finestre ed eseguire le necessarie operazioni.

Magari lo farò nei prossimi giorni...