Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Estendere il DOM con Javascript

Funzioni per estendere il DOM di IE 4.x e Opera 5.x
Funzioni per estendere il DOM di IE 4.x e Opera 5.x
Link copiato negli appunti

Vediamo come JavaScript possa essere usato per estendere il DOM dei vecchi browsers (in particolare Explorer 4.x e Opera 5.x) ed includere quindi anche nuovi metodi e nuove proprietà prima non esistenti.

Grazie ad alcune semplici tecniche utilizzeremo i metodi W3C getElementById e getElementsByTagName anche nei browser menzionati, che originariamente non li supportano in maniera standard.

Gli scopi di questa lezione sono quindi:

  • scrivere un codice che sia unico per tutti i browser e tutte le versioni
  • aumentare la longevità dei vecchi browsers

La stessa filosofia la si può poi applicare anche in senso inverso ai più recenti browsers ed estendere il loro DOM "all'indietro", ad esempio per far girare anche vecchi script.

I passi fondamentali da seguire per far digerire anche alle vecchie versioni i metodi del W3C sono due:

  1. Capire se il browser ha la capacità di poter essere esteso alle nostre finalità
  2. Trovare un modo per estendere effettivamente le potenzialità del browser

Il primo passo è di gran lunga il più importante. Infatti solo una volta note le possibilità del DOM di un browser, è possibile intuirne le capacità di "espansione" e i limiti invalicabili. Consideriamo i principali browser di "vecchia generazione": Explorer 4.x, Opera 5.x, Netscape 4.x.

Netscape 4.x

Per Netscape 4.x, non c'è molto da fare. Infatti il suo DOM non mette a disposizione alcun metodo o collezione in grado di accedere ad ogni elemento della pagina, ma solo ad un numero limitato, come i form (document.form[ ]), le immagini (document.images[ ]), o i layer (document.layers[ ]).

Non ci resta che scegliere se continuare a scrivere codici crossbrowser o... "abbandonarlo", come pure d'altronde ha fatto la Netscape stessa, che nelle nuove versioni non supporta molte delle collezioni proprietarie, presenti nelle versioni 4.x.

Explorer e Opera

Per quanto riguarda Explorer ed Opera le cose stanno diversamente. Non solo Opera supporta già i metodi getElementById e getElementsByTagName, ma solo per gli elementi, ma entrambi mettono a disposizione la collezione document.all[ ], che permette di raggiungere tutti (o quasi) gli elementi nella pagina.

In realtà, nei due browser, le collezioni possono contenere un numero di elementi differente, per il fatto che, ad esempio, Explorer memorizza in questa collezione anche i commenti, e il tag HTML, esclusi da Opera.

Quel che più importa è però riuscire a raggiungere tutti gli elementi contenuti nella pagina, per poter simulare l'azione svolta dai metodi W3C getElementById e getElementsByTagName.

Simulare getElementById()

Definiamo una nostra funzione con gli stessi input (parametri) e output (valore di ritorno) della funzione "originale".

Ricordando che il metodo getElementById ritorna l'elemento con uno specificato ID, quello che dovremmo fare sarà semplicemente restituire l'elemento della collezione all con l'attributo ID desiderato:

function getById(attr_id) {
 return(document.all[attr_id])
}

Una volta definita la nostra funzione, non resta che aggiungere un nuovo metodo (chiamato ovviamente getElementById) al document.

Visto che questo metodo è già supportato da Opera, interessiamoci solo di Explorer 4. Il modo migliore per "sniffare" le capacità del browser è rilevare contemporaneamenteper la presenza di document.all e l'assenza di document.getElementById:

if(document.all && !document.getElementById)
  document.getElementById = getById

Verifichiamo, riferendoci al livello sottostante il cui attributo ID è "livello".

Utilizziamo la sintassi W3C per cambiare la visibilità del livello, dopo ogni pulsante mostriamo il codice utilizzato per crearlo:

livello con id="livello"

<form action="">

<input type="button" value="Nascondi il livello" onclick= "document.getElementById('livello').style.visibility= 'hidden'" />

</form>


<form>

<input type="button" value="Visualizza il livello" onclick= "document.getElementById('livello').style.visibility= 'visible'">

</form>

Simulare getElementsByTagName

Come prima, definiamo una nostra funzione con le stesse caratteristiche della "vera" getElementsByTagName.

In questo caso dovremo scandire la collezione all alla ricerca di tutti gli elementi caratterizzati dallo stesso tag, che dovremmo sistemare in un nuovo array che andremo poi a restituire:

function getByTagName(tag) {
/*---------------------------------------------
Prima convertiamo in maiuscole il parametro
tag (la proprietà tagName, che andremo ad
analizzare è infatti espressa in maiuscole).
---------------------------------------------*/
tag = tag.toUpperCase();

/*---------------------------------------------
Creiamo poi, un nuovo array relativo all'elemento
che chiama il metodo, qualora non sia già
presente
---------------------------------------------*/
if(!this["lista"+tag]) {
this["lista"+tag] = new Array()

/*---------------------------------------------
A questo punto scandiamo la collezione all
alla ricerca di quegli elementi caratterizzati
dall'avere il tag voluto (quello passato come
parametro della funzione), e li sistemiamo
nell'array appena creato.
---------------------------------------------*/
for(jtag=0,itag=0; jtag<this.all.length; jtag++) {
if(this.all[jtag].tagName == tag)
this["lista"+tag][itag++] = this.all[jtag]
}
}
/*---------------------------------------------
Restituiamo infine l'array
---------------------------------------------*/
return(this["lista"+tag])
}

Nota: Importante è dare alle variabili che scandiscono la collezione all (in questo caso jtag e itag), nomi che non verranno poi usati in altre funzioni; questo perchè ogni volta che la funzione simulata getElementsByTagName viene chiamata, queste variabili saranno usate e potrebbero quindi creare problemi di collisione con altre variabili con lo stesso nome. Perciò consiglio, nelle operazioni di espansione, di utilizzare variabili con nomi descrittivi e non comuni.

In questo caso dobbiamo definire il nuovo metodo getElementsByTagName per document, e per element (cioè per tutti gli elementi), qualora non sia già supportato:

if(document.all && !document.getElementsByTagName)
document.getElementsByTagName = getByTagName

for(ind=0; ind<document.all.length; ind++)
if(!document.all[ind].getElementsByTagName)
document.all[ind].getElementsByTagName = getByTagName

Verifichiamolo. Costruiamo due tabelle e includiamo la seconda, quella con lo sfondo rosso, in un layer con un ID="livello2". Il codice per includere la tabella in un layer è:

<div ID="livello2" style="background:red;width:200px;">
 <table border=1 width="200px">
   ...
 </table>
</div>

cella 0 cella 1
cella 2 cella 3
cella 0 cella 1
cella 2 cella 3

Dapprima recuperiamo il numero di elementi TD presenti in tutta questa pagina:


<form action="">

<input type="button" value="Recupera le celle" onclick = "alert(document.getElementsByTagName('TD').length)" />

</form>

Poi recuperiamo il numero di celle contenute in "livello2":


<form action="">

<input type="button" value="Numero di celle nel livello2" onclick="alert(document.­getElementById('livello2').­getElementsByTagName('TD').length)" />

</form>

­

Simulare item

Volendo, possiamo simulare anche questo metodo W3C per recuperare un elemento da una lista di nodi. Ripercorrendo gli stessi passi logici, possiamo scrivere la nostra funzione, appoggiandoci alla sintassi "relativa" degli oggetti:

function itm(x){
 return(this[x])
}

Considerando che useremo questo metodo per recuperare gli elementi dagli array restituiti dal metodo getElementsByTagName, possiamo appoggiarci alle proprietà e ai metodi del costruttore usato nel suddetto metodo, ovvero new Array().

In particolare useremo la proprietà prototype che ci consente di definire prototipi di proprietà o metodi che saranno ereditati da ogni array creato, per l'appunto, dal costruttore new Array():

if(document.all && !document.getElementsByTagName)
  Array.prototype.item = itm

Utilizziamo questo nuovo metodo per cambiare il colore del testo della seconda cella (indice 1 dell'array) della tabella precedente contenuta nel "livello2":


<form action="">

<input type="button" value="Colora di giallo" onclick="document.­getElementById('livello2').­getElementsByTagName('TD').­item(1).style.color='yellow'" />

</form>

Nota:I metodi che abbiamo appena creato per Explorer 4 e Opera 5, non sono delle traduzioni fedeli dei relativi metodi W3C, perciò possono essere migliorati e personalizzati per i propri scopi. Quello che è utile evidenziare è la capacità (offerta dal Javascript) di poter estendere il DOM di un browser oltre i limiti predefiniti. Questo vale anche in senso inverso. Per esempio, seguendo la stessa filosofia, è possibile estendere il DOM W3C per includere anche la collezione all, il che aumenterebbe retroattivamente la compatibilità di un codice W3C.

Passiamo ora a considerare i metodi e le proprietà degli elementi. Sia Opera 5 che Explorer 4, supportano i metodi getAttribute e setAttribute, con l'accortezza, nel caso di Explorer, di recuperare l'elemento o con document.all[ ] o con i metodi simulati getElementById e getElementsByTagName.

Per quanto riguarda il metodo removeAttribute, questo è supportato, tra i due, solo da Explorer, percui occupiamoci di estendere il metodo anche ad Opera.

Simulare removeAttribute

Seguiamo sempre lo stesso procedimento, considerando che gli attributi di un elemento, sono proprietà dell'elemento stesso.

Se consideriamo che il metodo dovrebbe eliminare l'attributo passato come parametro e assegnare, qualora sia presente, il valore di default per l'attributo stesso, una prima e immediata soluzione può essere:

function remAttr(attr){
 this.setAttribute(attr,"");
}

Sappiamo infatti che Opera supporta il metodo setAttribute. A questo punto non ci resta che definire il nuovo metodo per tutti gli elementi della pagina.

if(document.all && !document.all[0].removeAttribute) {
 for(ind=0; ind<document.all.length; ind++)
  document.all[ind].removeAttribute = remAttr
}

È da notare il controllo che eseguiamo sul primo elemento della lista all, che è sempre presente (tag HTML per Explorer, HEAD per Opera).

Nota: Opera 5 pone dei vincoli sull'effettivo utilizzo dei metodi per gestire gli attributi degli elementi, fornendo un numero limitato di attributi che si possono manipolare dinamicamente.

Questo evidenzia il fatto che, anche se a volte i browser supportano le specifiche, la semantica dei vari attributi e metodi può non sempre coincidere con quella del W3C.

È utile sottolineare che quando si compiono queste operazioni di "espansione", quello che si deve tenere sempre a mente, è lo scopo che si vuole raggiungere. Infatti niente nega di riscrivere, ad esempio, il metodo remAttr per considerare i vari valori di default di tutti i possibili attributi. Quella da me proposta è solo la soluzione più semplice.

Ecco come scrivere tutto il codice relativo alla definizione dei metodi, occorre evidenziare l'importanza delle "precedenze" (nella rilevazione degli oggetti):

function getById(attr_id) {
 return(document.all[attr_id])
}

function getByTagName(tag) {
 tag = tag.toUpperCase();
 if(!this["lista"+tag]) {
  this["lista"+tag] = new Array();
  for(jtag=0,itag=0; jtag<this.all.length; jtag++) {
   if(this.all[jtag].tagName == tag)
    this["lista"+tag][itag++] = this.all[jtag];
  }
 }
 return(this["lista"+tag]);
}

function itm(x){
 return(this[x]);
}

function remAttr(attr){
 this.setAttribute(attr,"");
}

function init() {

 if(document.all && !document.getElementById)
  document.getElementById = getById;

 if(document.all && !document.getElementsByTagName)
  Array.prototype.item = itm;

 if(document.all && !document.getElementsByTagName)
  document.getElementsByTagName = getByTagName;

 for(ind=0; ind<document.all.length; ind++)
  if(!document.all[ind].getElementsByTagName)
   document.all[ind].getElementsByTagName = getByTagName;

 if(document.all && !document.all[0].removeAttribute) {
  for(ind=0; ind<document.all.length; ind++)
   document.all[ind].removeAttribute = remAttr;
 }
}

if(document.all && !document.getElementsByTagName)
 window.onload = init;

È da notare l'uso dell'evento onload per inizializzare i nuovi metodi, che garantisce che la collezione all si riempia completamente.

Ti consigliamo anche