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...

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'); ?>" />
    


12 agosto 2009

CSS + jQuery: checkout progress bar per siti e-commerce

Ecco un'altra miglioria apportata al negozio I Tesori del Drago durante il revamp: un indicatore che rende subito evidente al cliente a che punto del processo di acquisto si trova.

Si tratta di una progress-bar che fa la sua comparsa nella pagina del carrello, e indica quanti step sono ancora necessari prima di concludere l'acquisto. Su ogni pagina viene evidenziato lo step attuale.
In realtà questa non è solo una miglioria, ma anche e soprattutto una caratteristica richiesta in base alle norme di chiarezza e trasparenza che regolano il commercio elettronico.



Questa progress-bar è realizzata interamente con CSS e un pizzico di jQuery, più quattro icone formato PNG (o anche GIF), una per ogni step.
Si potrebbe usare anche soltanto del CSS, ma voglio utilizzare anche un po' di jQuery per mostrare la potenza dei suoi selettori.


1. Markup HTML

Per prima cosa è necessario creare gli elementi della barra, inserendoli nella pagina con dell'HTML valido e accessibile:

<div id="checkoutbar">
<ol>
<li>Carrello</li>
<li>Inserimento dati</li>
<li>Riepilogo ordine</li>
<li>Ordine completo</li>
</ol>
</div>


Una lista ordinata è l'ideale per una sequenza logica di fasi.

2. Aggiungere CSS q.b.
Adesso serve dello stile per creare quattro blocchi allineati e non delle righe tipiche delle liste, più altro stile di base per rendere la barra più attraente ed in linea col design del sito.

#checkoutbar{
margin: 0 auto;
padding: 0;
height: 50px;
width: 700px;
}
#checkoutbar ol{
list-style-type: none;
margin: 0 auto;
}
#checkoutbar li{
float: left;
font-size: .6em;
margin: 1em;
padding: 1em 2em;
background: #abdbff;
border: 3px solid #abdbff;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
opacity: .7;
filter: alpha(opacity=70);
}


La proprietà float: left conferita agli elementi-lista fa sì che essi si dispongano l'uno accanto all'altro. La spaziatura si ottiene con margini e padding opportuni.

Da notare i bordi stondati (proprietà border-radius, CSS3) per i browser che li supportano (come Firefox e Safari... niente IE, come al solito!): la prima dichiarazione, quella con -moz è per Firefox, la seconda per Safari.
La terza è la dichiarazione ufficiale come da linee guida CSS3.
Vedi questo articolo per una spiegazione sulla natura di queste dichiarazioni.

Altro attributo interessante è l'opacità, che necessita di due settaggi: il primo (aderente alle linee guida di CSS3) non è valido per il solito IE, che vuole l'attributo filter : alpha.
La trasparenza ci serve per concentrare ancora di più l'attenzione dell'utente sullo step in cui egli si trova, che sarà reso in seguito del tutto opaco (opacity: 1).

3. Stile dello step corrente
A questo punto le scelte sono due, piuttosto equivalenti:
  • attribuire in ciascuna pagina una classe all'elemento lista attivo, ad esempio con un class="current" e creare uno stile solo su quella classe.
  • utilizzare i selettori di jQuery e creare uno stile attraverso di essi.


Oggi andiamo per la seconda strada, giusto per vedere un paio di selettori all'opera.

Ecco il codice:

$(document).ready(function(){

$('#checkoutbar li:not(:last)').append(' &gt;&gt;');

$('#checkoutbar li:eq(0)').css({
'opacity' : '1',
'padding-left' : '40px',
'background' : '#0081e4 url(images/checkoutbar1.png) center left no-repeat',
'color' : '#fff',
'border' : '3px double #abdbff'
});

});



La prima istruzione serve per attaccare a tutti i nomi degli step due simboli "maggiore di" (>), a fare le veci di una freccia che indica un percorso. Ho preferito inseririli tramite jQuery perché non hanno alcun significato semantico in senso stretto, ma sono solo un semplice artificio di design, e il markup della nostra lista rimane pulito.
Il selettore not() è un esclusore, cioè un NOT logico, mentre last identifica l'ultimo elemento di una serie, in questo caso la serie dei tag <li>.
In questo modo l'ultimo step, che è la fine del persorso, non ha alcuna freccia associata.

Il resto è lo stile dell'elemento corrente, identificato con il selettore eq(index): esso identifica un elemento di una serie in base al suo indice, che parte da 0. In questo caso, essendo il primo elemento quello corrente, abbiamo .eq(0): questo valore va cambiato su ogni pagina su cui figura la nostra barra.

L'immagine cui si fa riferimento nell'attributo background è un'icona che identifica la fase corrente, anch'essa individuale per ciascuna fase.

11 agosto 2009

jQuery: bollino sconti per siti e-commerce

In questi giorni ho lavorato sodo alla nuova versione del negozio I Tesori del Drago, che vende principalmente prodotti naturali per la salute ed il benessere, oltre ad altri articoli interessanti.
Questo negozio era stato lanciato a Novembre 2008, e mi ero occupato praticamente di tutto l'aspetto creativo e tecnico. Si è deciso di cambiare l'impostazione grafica da "dark" a "light", e ne ho approfittato per aggiungere alcune migliorie.

Tra le varie cosette carine implementate, oggi voglio parlare del bollino che ho creato per i prodotti in offerta (un bollino che indica lo sconto percentuale applicato sull'articolo), perché rende bene l'idea del potenziale di jQuery anche a livelli di web design quasi puro.

L'idea era quella di avere, nella homepage e nella pagina dedicata all'articolo in offerta, una funzionalità completamente automatica che generasse una grafica mostrante lo sconto percentuale, da abbinare alla foto del prodotto.


Ho usato esclusivamente jQuery per realizzare questa piccola miglioria di design.
In pratica, si calcola lo sconto percentuale tramite i tag che contengono il valore del prezzo di listino e di quello di vendita, e poi si immette quel valore percentuale in un elemento blocco opportunamente stilato con del CSS (anch'esso generato con jQuery). L'unica cosa esterna al file .html di cui si ha bisogno è un PNG24 con il bollino, da usare come sfondo del blocco creato.
La grafica deve essere in formato .png a 24bit perché abbiamo bisogno di una trasparenza perfetta, ovviamente, visto che andremo a sovrapporre il bollino sugli sfondi più svariati.

L'HTML della sezione che ci interessa ha una struttura di questo tipo:

<div class="offerte">
<h1>nome articolo</h1>
<p>descrizione articolo</p>
<img alt="foto articolo" />
<span class="list_price">14.00€</span>
<span class="sell_price">12.60€</span>
</div>


Ecco il codice jQuery per calcolare la percentuale di sconto applicata:

$('.offerte').each(function (i) {
var lp=$(this).children('.list_price').text();
lp=lp.substr(0, lp.length - 1);
var sp=$(this).children('.sell_price').text();
sp=sp.substr(0, sp.length - 1);
var sconto=Math.round((100-((sp/lp)*100)));
});


Il metodo each( callback ) serve per iterare su tutti gli elementi aventi la classe "offerte" presenti nella pagina, in quanto non è detto che lo sconto percentuale applicato sia lo stesso su tutti gli articoli.
Prima di tutto si memorizzano il prezzo vecchio e quello scontato ciascuno in una propria variabile con il metodo text().
Poi con l'espressione che segue si tronca l'ultimo carattere della stringa memorizzata in ciascuna variabile. Questo per eliminare il simbolo finale dell'euro, che ci renderebbe il dato inutilizzabile in operazioni matematiche.
Occhio che il simbolo separatore dei decimali sia un punto (.) e non una virgola (,), perché jQuery nel secondo caso non riconoscerebbe come numeri i valori memorizzati.

Nella variabile sconto si calcola lo sconto percentuale: il metodo round(x) dell'oggetto Math arrotonda il valore dello sconto all'intero più vicino.
Questo è molto utile dal punto di vista grafico, visto che una o più cifre decimali "strizzerebbero" troppo il testo nel nostro bollino.

A questo punto, si costruisce un elemento blocco <div> nel quale immettere come testo il valore della variabile sconto, per poterlo visualizzare formattato a piacere:

$('.offerte').each(function (i) {
var lp=$(this).children('.list_price').text();
lp=lp.substr(0, lp.length - 1);
var sp=$(this).children('.sell_price').text();
sp=sp.substr(0, sp.length - 1);
var sconto=Math.round((100-((sp/lp)*100)));
$(this).append("<div class='bollino'></div>");
$(this).children('.bollino').css(bollino).html(sconto).prepend('-').append('%');
});


In sostanza, si appende un div al blocco "offerte" e gli si attribuisce dello stile precedentemente creato, tramite una variabile (usando css(properties), che vedremo tra poco), gli si inserisce come contenuto HTML la variabile sconto (html(val)) e si guarnisce con i necessari simboli "-" (visto che è uno sconto) e "%", rispettivamente prima e dopo il testo inserito.

Per l'immagine del bollino ho semplicemente usato una delle shapes predefinite di Photoshop:


A questo punto dobbiamo solo creare il CSS necessario a formattare e posizionare il nostro bollino.
Anche in questo caso si usa jQuery, creando una variabile da passare comodamente al proprio blocco col metodo css(properties).

var bollino = {
'background' : 'transparent url(images/bollino.png) top center no-repeat',
'width' : '50px',
'height' : '50px',
'z-index' : '100',
'text-align' : 'center',
'padding-top' : '1em',
'font' : 'italic bold 1em "century gothic", century, sans-serif',
'color' : '#fede60',
'position' : 'absolute',
'top' : '70px',
'left' : '125px'
}


Il position: absolute serve per poter piazzare il bollino parzialmente sovrapposto alla foto del prodotto.
L'unica cosa da notare in questo caso è che per consentire un posizionamento assoluto del bollino all'interno del blocco "offerte", è necessario che quest'ultimo abbia la proprietà CSS position: relative. Se così non fosse, il posizionamento sarà rispetto al primo blocco antenato posizionato relativamente, e quasi sicuramente ciò farà sì che tutti i bollini si sovrappongano.
E questo non è ciò che vogliamo, giusto?

Alla fine, quello che si ottiene sarà qualcosa di questo tipo:


Ecco lo script completo autosufficiente:
$(document).ready(function(){

var bollino = {
'background' : 'transparent url(images/bollino.png) top center no-repeat',
'width' : '50px',
'height' : '50px',
'z-index' : '100',
'text-align' : 'center',
'padding-top' : '1em',
'font' : 'italic bold 1em "century gothic", century, sans-serif',
'color' : '#fede60',
'position' : 'absolute',
'top' : '70px',
'left' : '125px'
}

$('.offerte').each(function (i) {
var lp=$(this).children('.list_price').text();
lp=lp.substr(0, lp.length - 1);
var sp=$(this).children('.sell_price').text();
sp=sp.substr(0, sp.length - 1);
var sconto=Math.round((100-((sp/lp)*100)));
$(this).append("<div class='bollino'></div>");
$(this).children('.bollino').css(bollino).html(sconto).prepend('-').append('%');
});

});

[Aggiunta: sul Problema della Trasparenza in IE6]
La questione è fin troppo vecchia per doverne sempre stare a discutere: IE6 non supporta la trasparenza alpha dei PNG24, e il nostro bollino su tale browser degrada ad un ingombro grafico sgradevole.
Nessuno oggigiorno dovrebbe mai navigare in internet con un simile programma! Ma per coloro che sono obbligati, c'è sempre una qualche scappatoia parziale.

Un metodo è usare lo script PNG Fix di Andreas Eberhard, che fa generalmente un ottimo lavoro nel ripristinare la corretta trasparenza dei PNG della pagina web su cui è invocato.

L'altra soluzione è il vecchio metodo delle immagini GIF di sostituzione a quelle PNG, visto che almeno la trasparenza GIF ad un solo bit è supportata da questo browser.
In questo caso, se usate un bollino "a tinta unita" (solid color) come il mio sopra, conviene creare una GIF a soli due colori: uno per il colore del bollino, l'altro per la trasparenza, e senza alcun dithering. In questo modo, quando il bollino si sovrappone ad altri elementi, i risultati sono decisamente più accettabili.

Una maniera di implementare questo ultimo sistema nel nostro script senza stravolgere alcunché è sfruttare jQuery per riconoscere il modello e la versione del browser che interpreta la pagina web, e agire di conseguenza.

Ad esempio aggiungendo in fondo allo script, semplicemente:
if($.browser.msie && $.browser.version<="6.0"){
$('.bollino').css('background', 'transparent url(images/bollino.gif) top center no-repeat');
}
In realtà l'uso del metodo $.browser.version è deprecata perché l'utilizzo di jQuery concerne più le caratteristiche di un browser, che la sua versione, come si spiega nella pagina della documentazione dedicata al metodo jQuery.support.

04 agosto 2009

first time Holga?

ecco le prime foto pubblicate tra quelle fatte con l'Holga 120N: sono tutte fatte con pellicola B/N Ilford Delta400 scaduta, trovate su Ebay, e poi scansionate con un Epson V500+Vuescan.

(ho perso una mattinata a capire quali erano i settaggi per me più efficaci per scansionare quei negativi... alla fine ho trovato una mia configurazione partendo da questo thread sul gruppo Fotografia Analogica Italia su Flickr e poi personalizzando un po' e creando dei file .DNG da esportare con Lightroom. tutto questo potrà cambiare nell'immediato futuro comunque...)

prima di tutto è venuta fuori una serie abbastanza eterogenea di tre foto (un fotohaiku):


  1. I dreamt of Duchamp he and I in a meadow blue

  2. whilst our little bodies twisted in carousel swerves

  3. and then the world oh the world all fall down


Get the flash player here: http://www.adobe.com/flashplayer


evocare Duchamp come nume tutelare nella prima Holgafoto pubblica mi pare quantomeno calzante... quel librone è una retrospettiva sulla produzione di Duchamp su un lato e sull'altro una raccolta di effemeridi. la pala che si vede è il famoso readymade che chiamò "in previsione di un braccio rotto" (in advance of a broken arm), e anche questo mi pare molto calzante.

la seconda foto è la giostra in Piazza della Repubblica a Firenze. l'avevo chiamata provvisoriamente "carosello", e questo mi ha fatto venire in mente un verso di Hullo Angel di Death In June, che fa:

Well, Hullo Angel
A Gift and a Smile
Well, Hullo Angel
As We walk a Crooked mile

And a Twisted Man
Leans on twisted sticks
With children's laughter
Hanging from swings

Well, Hullo Angel
And the skipping rope turns
Whilst little bodies twist
In carousel swerves

Well, Hullo Angel
It's the End of The World
Well, Hullo Angel
At the End of your Tether

Well, Hullo Angel
Time for Sleep
Well, Hullo Angel
Time for Prayers

That which is Falling
Should also be Pushed
That which is Crawling
Should also be Crushed!


anche questo mi è sembrato davvero calzante.

l'ultima foto è uno specchio spezzettato, visto lo stesso giorno in una installazione nel cortile di Palazzo Strozzi, ad opera di Yves Netzhammer, chiamata Inventories of abstraction, nel cortile della Strozzina; ci si intravede il Palazzo a pezzi spezzati. il titolo è in pratica la versione anglofona del "girogirotondo".

e sì, questo poi, calzava proprio fino in fondo...
tutto mi torna, perché, per onorare al meglio la qualità surreale e onirica delle immagini che escono fuori da una Holga, non c'è proprio niente di meglio di un fotohaiku che inizia evocando il Dadaismo in qualità di Araldo dell'Anti-arte e termina nientemeno che con la fine del mondo. e nel mezzo ci abbiamo messo anche una bella canzone.
missione compiuta!

e poi altre due, slegate tra di loro:

double voyage




zeitmaschine




la prima è una doppia esposizione in treno. l'idea era quella di fare un'aureola a Silvia, ma poi, grazie all'utilissimo mirino della cara Holga, sono riuscito solo a centrarle gli occhiali; non che l'effetto sia male.
il "doppio viaggio", comunque sia, è sia quello nello spazio, del treno, che quello nella propria mente, che accompagna il primo.
guardi fuori dal finestrino, i paesaggi scorrono come fotogrammi, non si riesce a fissarli che qui il pulsante di pausa non esiste, e intanto un pensiero segue l'altro, si costruiscono castelli in aria sulle colline che si muovono all'orizzonte, il serpente si morde la coda, e ci si perde sia dentro che fuori.

l'ultima foto è una "macchina del tempo", perché pare una foto degli anni '70, e non qualcosa di contemporaneo.
e anche questo fa molto Holga.

01 agosto 2009

poladroid graveyard

nuovo progettino!
trattasi di una sorta di fotoblog, dedicato esclusivamente a poladroid.
(Poladroid è un software che simula l'utilizzo di una buona vecchia Polaroid, consentendo di tramutare file immagine in pola(d)roid. estremamente divertente e con rilevanti possibilità creative. specialmente ora che le cartucce polaroid sono fuori produzione! ...a meno che non si riesca nell'impossibile)
il potenziale creativo di Poladroid è notevole: ciò è dovuto principalmente alla mancanza di controllo del processo creativo dell'immagine finale, e alla sua parziale casualità.
le alterazioni più importanti dell'immagine originale sono tre:
  1. Taglio quadrato: nonostante sia semplicemente centrato nella foto originale, l'effetto è psicologicamente forte perché assolutamente spietato nell'amputare un'immagine pensata per un rapporto 3:2 o comunque rettangolare.
  2. Variazioni tonali: essendo casuali, la dominante non può essere predetta e (insieme ad eventuali texture) rende ogni poladroid un esemplare unico. L'effetto poi cambia molto al variare della saturazione dell'immagine di partenza.
  3. Frame Polaroid: ovviamente! l'incorniciatura nella famosa carta polaroid aggiunge un'altra grossa componente psicologica, aiutata anche dalla vignettatura.
conscio di tutto questo, mi ritrovo con un sacco di poladroid "scartate" perché incapaci di comunicare la senzazione che ricercavo in quel momento, ma comunque degne di nota di per sé, in quanto poladroid e istantanee senza compromessi di un istante di vita passata.

ed ecco perché "poladroid graveyard", cioè cimitero: un luogo dove far riposare con dignità tutte le poladroid in qualche modo "scartate". in questo senso sarà anche un progetto aperto.

ho anche fatto il logo: disegnato, scannerizzato e poladroid-zato.