« Frank Horvat | Main | para- seiDiTorino? »

10.02.05

Trigger Javascript

di Peter-Paul Koch (Translated with the permission of A List Apart Magazine and the author[s].) - Articolo originale Ora che nel vostro sito avete separato la struttura (XHTML) dalla sua presentazione (CSS), non sarebbe grandioso separare allo stesso modo il livello operativo (Javascript) dagli altri? ALA Peter-Paul Koch spiega come ottenere ciò utilizzando i trigger Javascript.

Trigger in Javascript

di Peter-Paul Koch

L’interfaccia utente di un sito web è composta da tre livelli. L’XHTML costituisce il livello strutturale, che contiene gli elementi strutturali e semantici, oltre al contenuto del sito. A quest’ultimo possono essere aggiunti un livello di presentazione (CSS) e uno operativo (Javascript) per rendere il vostro sito più piacevole nell’aspetto e usabile. Questi tre livelli dovrebbero rimanere nettamente separati. Ad esempio, dovrebbe essere possibile riscrivere interamente il livello di presentazione senza toccare quello strutturale e quello operativo.

Nonostante questa separazione, il livello di presentazione e l’operativo devono ricevere istruzioni da quello strutturale. Devono sapere dove andare ad aggiungere quel dato elemento stilistico o quando compiere quella determinata azione. Hanno bisogno di triggers.

I triggers dei CSS sono ben noti. Gli attributi class e id permettono il controllo completo della presentazione del vostro sito. Anche se è possibile fare a meno dei triggers, inserendo le istruzioni direttamente negli attributi style, questo modo di scrivere il codice è deprecato. Se volete aggiornare l’aspetto del vostro sito usando questa tecnica, sarete costretti a cambiare anche il livello strutturale nell’XHTML; in questo modo verrà meno la separazione tra presentazione e struttura.

Triggers Javascript

Il livello operativo dovrebbe funzionare esattamente allo stesso modo. Dovremmo separare quest’ultimo dalla struttura eliminando i gestori di eventi inseriti nel codice come ad esempio onmouseover="switchImages('fearful',6,false)". Diversamente, dovremmo utilizzare i triggers per dire allo script dove applicare un certo comportamento.

L’attributo id è il trigger Javascript più semplice


<div id="navigation">
 <ul>
  <li><a href="#">Link 1</a></li>
  <li><a href="#">Link 2</a></li>
  <li><a href="#">Link 3</a></li>
 </ul>
</div>

var x = document.getElementById('navigation');
if (!x) return;
var y = x.getElementsByTagName('a');
for (var i=0;i<y.length;i++)
 y[i].onmouseover = addBehavior;

Lo script è lanciato dalla presenza o assenza dell’id=”navigation”. Se è assente nulla accade (if(!x) return;), se invece è presente, tutti gli elementi link che discendono da esso acquistano un comportamento onmouseover. Questa soluzione è semplice ed elegante, e funziona in tutti i browser. Se l’id soddisfa le vostre esigenze non serve che andiate oltre nella lettura di questo articolo.

Triggers avanzati

Sfortunatamente ci sono delle situazioni in cui id non può essere utilizzato come trigger:

  1. un id può essere utilizzato solo una volta all’interno di un documento. In certi casi è necessario applicare lo stesso comportamento a più (gruppi di) elementi.
  2. Occasionalmente uno script ha bisogno altre informazioni, diverse dal semplice “esegui qui l’azione”

Prendiamo gli script di un form come esempio di entrambi i problemi. Potrebbe essere utile aggiungere dei triggers per la validazione del form all’XHTML, per esempio qualcosa che dica “questo campo è obbligatorio.”  Se avessimo usato un trigger avremmo avuto uno script simile al seguente:


function validateForm()
{
 var x = document.forms[0].elements;
 for (var i=0;i<x.length;i++)
 {
  if ([this field is required] && !x[i].value)
    // notify user of error
 }
}

Ma come facciamo a creare un trigger XHTML che dica allo script che un certo campo è obbligatorio? Non si può fare attraverso un id: abbiamo bisogno di una soluzione che funzioni per un numero illimitato di campi del form. Si potrebbe usare l’attributo class per lanciare quell’azione:


<input name="name" class="required" />

if (x[i].className == 'required' && !x[i].value)
  // notify user of error

L’uso proprio dell’attributo class è di definire i triggers dei CSS.  Combinando CSS e triggers JavaScript  non è impossibile, ma può confondere facilmente il codice:


<input name="name" class="largefield required" />

if (
  x[i].className.indexOf('required') != -1 &&
  !x[i].value
)

In mia opinione, l’attributo class dovrebbe essere impiegato solo per i CSS. Gli attributi class  sono i trigger primary dell’XHTML a livello di presentazione e renderli allo stesso tempo portatori di determinate azioni confonde le cose. Creare dei trigger contemporaneamente su entrambi i livelli attraverso l’attributo class viola la separazione tra livello operativo e di presentazione, di conseguenza alla fine rimane una scelta soggettiva.

Trigger informativi

Spesso i trigger posso diventare più complessi di un semplice comando “applica qui un certo comportamento”. A volte si può voler aggiungere un valore al trigger. Il valore associato al trigger rende il livello operativo molto più versatile, dal momento che può operare su ciascun elemento XHTML  individualmente, anziché eseguire uno script indistintamente.

Prendete un form con delle textarea con una lunghezza massima per i valori. Il vecchio attributo MAXLENGTH non funziona con le textarea, quindi dobbiamo scrivere uno script. Inoltre, non tutte le textarea del form hanno la stessa lunghezza massima, rendendo quindi necessario immagazzinare il valore della lunghezza massima da qualche altra parte.

In questo modo:


var x = document.getElementsByTagName('textarea');
for (var i=0;i<x.length;i++)
{
 if ([this textarea has a maximum length])
  x[i].onkeypress = checkLength;
}

function checkLength()
{
 var max = [read out maximum length];
 if (this.value.length > max)
  // notify user of error
}

Lo script ha bisogno di 2 informazioni:

  1. La textarea ha una lunghezza massima? Questo è il classico trigger che avverte lo script che qualche comportamento sta per essere attivato
  2. Qul è la lunghezza massima? Questo è il valore che lo script andrà a verificare quando l’utente inserirà dei dati nei campi.

Qui la soluzione batasa su class non è più utile. Tecnicamente è ancora possibile, ma il codice rischia di diventare troppo complesso. Prendete una textarea di cui è richiesta una lunghezza massima di 300 px:


<textarea
  class="large required maxlength=300"
>
</textarea>

Questo esempio non solo mischia la presentazione e due differenti gruppi di comportamenti, ma diventa anche complicato leggere qual è la lunghezza massima della textarea:


var max = this.className.substring(
  this.className.indexOf('maxlength')+10
);
if (this.value.length > max)
 // notify user of error

Si noti che questo codice funziona solo quando mettiamo il trigger della lunghezza massima come ultimo nel valore del class. Se vogliamo metterlo altrove (perché ad esempio vogliamo aggiungere un altro trigger con un valore) il codice diventa ancora più complicato.

Il problema

Questo è il problema che dobbiamo risolvere. Come facciamo ad aggiungere trigger JavaScript che ci permettano di inserire nello script un messaggio di avvertimento generico (“esegui qui una certa azione”)  e un valore specifico di un elemento?

Dal punto di vista tecnico, aggiungere queste informazioni all’elemento class  è possibile, ma è permesso far portare certe informazioni ad un attributo che non è stato creato per portare informazioni di quel tipo? Questo non viola la separazione tra livello di presentazipone e livello operativo? Anche se pensate che non ci siano ostacoli teorici, rimane una soluzione complicata che richiede codice Javascript complesso.

E’ anche possibile aggiungere trigger ad altri attributi, come lang o dir , ma, di nuovo, sarete costretti ad utilizzare elementi che nascono per inglobare altri tipi di informazioni.

Attributi personalizzati

Io adotto un’altra soluzione. Diamo di nuovo un’occhiata all’esempio della textarea con la lunghezza massima. Abbiamo bisogno di 2 informazioni:

  1. La textarea ha una lunghezza massima?
  2. Qual è la lunghezza massima?

Il modo naturale e semantico per portare questa informazione è attraverso l’aggiunta di un attributo alla textarea:


<textarea
  class="large" maxlength="300"
>
</textarea>

La presenza dell’attributo maxlength avverte lo script di verificare l’input dell’utente nella textarea e troverà la grandezza massima di questa specifica textarea nel valore dell’attributo. Trovandoci in questo elemento possiamo altresì applicare il trigger personalizzato “required”. required=true, per esempio, che quindi accetterà qualsiasi valore dal momento che il trigger non specifica requisiti ulteriori.

Dal punto di vista tecnico non ci sono problemi. Il metodo getAttribute() previsto dal W3C DOM   ci permette di leggere da qualsiasi tag qualsiasi attributo. Solo Opera dalla versione 7.54 non permette di leggere gli attributi esistenti (come ad esempio src) sul tag sbagliato (ad esempio <h2>).  Fortunatamente versioni successive di questo browser supportano getAttribute() in maniera completa.

Quindi, questa è la mia soluzione:


<textarea
  class="large" maxlength="300" required="true"
>
</textarea>

Technically there’s no problem. The W3C DOM getAttribute() method allows us to read out any attribute from any tag. Only Opera up to version 7.54 doesn’t allow us to read out existing attributes (like src) on the wrong tag (like <h2>). Fortunately later versions of this browser support getAttribute() fully.

So this is my solution:


function validateForm()
{
 var x = document.forms[0].elements;
 for (var i=0;i<x.length;i++)
 {
  if (x[i].getAttribute('required') && !x[i].value)
    // notify user of error
 }
}

var x = document.getElementsByTagName('textarea');
for (var i=0;i<x.length;i++)
{
 if (x[i].getAttribute('maxlength'))
  x[i].onkeypress = checkLength;
}

function checkLength()
{
 var max = this.getAttribute('maxlength');
 if (this.value.length > max)
  // notify user of error
}

A mio modo di vedere questa è la soluzione più semplice da implementare e la più vicina al modo in cui i triggers Javascript dovrebbero essere: una coppia nome/valore dove il nome innesca l’azione del trigger e il valore dà allo script ulteriori informazioni, permettendo di personalizzare le azioni per ciascun singolo elemento. Infine, aggiungere questi trigger all’XHTML è estremamente semplice anche per i webmaster alle prime armi.

DTD personalizzata

Chiunque adotti questa soluzioni e provi a sottoporre la pagina a un validatore noterà immediatamente un problema. Il validatore criticherà la presenza degli attributi requires e maxlength. Ciò è assolutamente corretto: il primo attributo non è conforme all’XHTML, mentre il secondo è valido solo all’interno dell’elemento <input>.

La soluzione è di rendere  questi attributi validi, creando un Document Type Definition (DTD) personalizzato, che estenda l’XHTML quanto basta per includere i nosti attributi con i trigger. Questa DTD personalizzata definisce gli attibuti speciali e la loro posizione nel documento, e il validatore obbedirà eseguendo la validazione del documento secondo i nostri gusti. Se il DTD dice che un attributo è valido, quell’attributo è valido.

Se non sapete come creare un DTD personalizzato leggete J. David Eisenberg in Creating Custom DTDS in questo numero, che spiega tutto ciò che è necessario sapere.

Secondo me, usando attributi personalizzati per applicare il livello operativo – e scrivendo DTD personalizzati per definire questi attributi personalizzati in maniera corretta – aiuterà a separare il livello operativo da quello strutturale e a scrivere script semplici ed efficienti. Inolte, una volta che gli attributi sono stati definiti e gli script sono stati scritti anche il webmaster  principiante sarà in grado di aggiungere questi trigger al documento XHTML.

 

Peter-Paul Koch è uno sviluppatore web freelance in Amsterdam, Olanda. Scrive e cura www.quirksmode.org un compendio con circa 180 articoli e suggerimenti sui CSS e Javascript,  oltre un sistema di raccolta dei bug e a un blog.

Posted by matteo at 10.02.05 17:21

Comments

Post a comment




Remember Me?