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

HIJAX: un Ajax più accessibile

Realizzare siti e applicazioni web che possono essere visualizzati anche su browser che non supportano Ajax
Realizzare siti e applicazioni web che possono essere visualizzati anche su browser che non supportano Ajax
Link copiato negli appunti

L'avvento di Ajax ha sicuramente rivoluzionato una gran parte di siti e applicazioni web. Non esiste più sito che non faccia uso di script JavaScript nonostante questo linguaggio sia stato disprezzato da molti in passato. 

Realizzare un sito web in Ajax significa modificare la vecchia concezione di sito web composto principalmente da link (il classico tag <a>) e form per inviare dati al server. Con questo, ormai non più nuovo, approccio non esiste più il concetto di pagina: tutto lo scambio dei dati avviene in maniera nascosta all'utente che spesso non si rende conto nemmeno di effettuare ulteriori richieste al server in quanto la pagina non cambia praticamente mai. Questo sicuramente è un notevole vantaggio in termini di interattività e usabilità della pagina: i tempi di attesa, una volta inizializzato l'engine, sono ridotti all'osso e l'applicazione risulta più reattiva agli eventi dell'utente rispetto al passato.

Tutta questa dinamicità però presenta alcuni difetti che dopo l'euforia iniziale per questo nuovo modo di fare web stanno piano piano emergendo e si fanno sempre più sentire. Il problema principale che presentano i siti realizzati con Ajax è l'impossibilità per i motori di ricerca di indicizzare i contenuti in quanto essi utilizzano browser testuali che, per giusti motivi di performance e rapidità delle richieste e del parsing, non presentano tutte le funzioni alle quali siamo abituati di trovare un un browser standard. Tradotto in parole povere significa che un sito che presenta Ajax non verrà mai indicizzato nella maniera corretta in quanto non si tratta di un sito “classico” ma presenta funzionalità avanzate e un uso massiccio di JavaScript.

Spesso la soluzione per ovviare a questo problema è quella del cloacking ovvero quella di fornire delle pagine differenti in base alla natura del client richiedente. Praticamente si tratta di rispondere con pagine web facenti uso di Ajax per i browser classici e con pagine web povere per gli spider di Google e company. Questa tecnica, oltre ad essere abbastanza scomoda da implementare (si tratta di realizzare due siti al posto di uno) è molto rischiosa in quanto vietata dai motori di ricerca. È ipotizzabile infatti che se uno spider riuscisse a rendersi conto di queste differenze, il nostro sito verrebbe inserito nella black list del motore di ricerca rovinando tutto il lavoro realizzato in precedenza dai nostri SEO.

Scartata questa soluzione, l'unica possibilità che rimane è quella del cosiddetto degradable Ajax ovvero quella di realizzare un sito o un'applicazione che funzioni in maniera simile sia se il browser del client presenta funzionalità JavaScript sia se si ha a che fare con un browser testuale tipico degli spider dei motori di ricerca.

L'approccio HIJAX

HIJAX è una tecnica appunto di degradable Ajax e permette di realizzare siti e applicazioni web che possono essere visualizzati senza problemi anche su browser web che per un motivo o per l'altro non supportano Ajax. Utilizzando HIJAX è necessario anche utilizzare l'ormai diffuso paradigma dell'Unobtrusive JavaScript che prevede di mantenere separati i vari componenti che compongono una pagina web (quindi HTML per definire la struttura, CSS per il layout grafico e JavaScript per la parte di interattività e gestione degli eventi).

Utilizzando HIJAX la realizzazione di un sito segue i canoni tradizionali ovvero l'utilizzo in maniera standard di link e form. È però presente nella pagina uno script in grado di intercettare queste  chiamate al server deviandole da un canale tradizionale ad un canale Ajax tramite l'oggetto XmlHttpRequest. In questo modo, se il browser non esegue lo script in questione la pagina si comporterà in maniera tradizionale non alterandone la struttura e i meccanismi comunicativi.

Ovviamente è necessario un ulteriore accorgimento anche dal punto di vista server-side in quanto nel caso di chiamate “classiche” esso dovrà rispondere, una volta elaborati eventualmente i dati ricevuti, con una pagina completa in quanto essa sostituirà completamente la pagina precedente. Nel caso invece di chiamate Ajax esso dovrò rispondere in maniera diversa. Utilizzando HIJAX è necessaria un'architettura completamente modulare che possa adattarsi in maniera rapida a questa doppia possibilità di risposta, da un lato “globale” e dall'altro “specifica”. Spesso l'approccio più utilizzato per comunicare al server questa differenza è l'utilizzo di un particolare parametro booleano che identifica o meno la presenza di un motore JavaScript all'interno del browser richiedente.

Implementiamo HIJAX

Per implementare nel pratico questa tecnica è necessario sviluppare componenti sia client-side che server-side.

Client

Dal punto di vista del client è necessario creare una classe che permetta di intercettare tutte le chiamate remote e trasformarle in richieste tramite XmlHttpRequest. Per implementare tutto ciò è necessario modificare il comportamento degli elementi che permettono di invocare una pagina remota: i link e i form.

La prima funzione che andremo a definire si occupa di alterare il comportamento dei tag a della nostra pagina andando a inserire una callback per l'evento onclick che viene attivato quando l'utente clicca sul link appropriato:

function interceptAElements() { 
	var aEls = document.getElementsByTagName("A"); 
	var aEl = null;	 
	for(var i = 0; i<aEls.length; i++) { 
		aEl = aEls[i]; 
		aEl.onclick = function() { 
			var t = this.href.split("?"); 
			var url = t[0]; 
			var queryString = t[1] || ""; 
			if(queryString) queryString += "&"; 
			queryString += "ajax=1"; 
			Ajax.request("get", url, queryString); //non esiste
		} 
	} 
}

Grazie al metodo getElementsByTagName recuperiamo tutti i tag di tipo a e modifichiamo il comportamento dell'evento onclick. Grazie al metodo split aggiungiamo il parametro ajax impostandolo uguale a 1 e gestiamo eventuali parametri già presenti. All'interno del metodo viene poi invocato il metodo statico Ajax.request non ancora definito ma, ora, di poca importanza che permette di eseguire una richiesta verso il server. In questo caso si utilizza il metodo GET in quanto è il metodo utilizzato dai link.

Inviare dati al server: i form

La funzione che intercetta il submit di un form è assolutamente più complessa rispetto alla precedente soprattutto per il fatto che all'interno di un form possono essere presenti diversi sotto elementi complessi (per esempio le SELECT) che devono essere gestiti durante la creazione della queryString da inviare al server:

function interceptFormElements() { 
	var formEls = document.getElementsByTagName("FORM"); 
	var elementsToSkip = [ 'submit' ]; 
	for(var i = 0; i<formEls.length; i++) { 
		formEl = formEls[i]; 
		formEl.onsubmit = function() { 
			var els = this.elements; //lista degli elementi
			var method = this.method; 
			var url = this.action; 
			var queryString = ""; 
			for(var i = 0; i<els.length; i++) { 
				el = els[i]; 
				switch(el.type) { 
					case "text": 
					case "hidden": 
					case "password": 
					case "textarea": 
					case "select": 
					case "select-one":					 
						queryString += el.name + "=" + el.value + "&"; 
						break; 
					case "radio": 
					case "checkbox": 
						if(el.checked) { //i checkbox e i radio non flaggati non vengono inviati
							queryString += el.name + "=" + el.value + "&"; 
						} 
						break; 
				} 
			} 
			queryString += "ajax=1"; 
			return Ajax.request(method, url, queryString); //non esiste
		} 
	} 
}

Rispetto alla funzione precedente, in questo caso è stato necessario ciclare all'interno del vettore elements dell'oggetto form che presenta al suo interno la lista degli elementi che appartengono al form. In base al tipo è possibile reperire il valore e costruire la variabile queryString per poi invocare il metodo request passando i parametri appena ottenuti. In questo secondo caso il metodo HTTP invocato non è forzato come in precedenza ma è letto dall'attributo method dell'oggetto form.

Creiamo un componente completo

Una volta analizzate nel dettaglio le funzioni “core” dell'intera logica di Hijax possiamo unire i codici all'interno di un componente che sarà organizzato come un oggetto che rappresenta un client a sé stante che può essere utilizzato in più contesti differenti e che implementa la parte protocollare che si occupa di eseguire la richiesta Ajax:

var HijaxClient = function(opt) { 
	this.onResponse = opt.onResponse; 
	this.initInterceptors();
} 
HijaxClient.prototype = { 
	interceptAElements: function() { [...] }, 

	interceptFormElements: function() { [...] }, 

	initInterceptors: function() {
		interceptAElements();
		interceptFormElements();
	},

	request: function(method, url, data) { 
		var xhr = this.getXHR(); 
		if(!xhr) return true; 
		var client = this; 
		xhr.onreadystatechange = function(a,b,c) { 
			if(this.readyState == 4) { 
				if(this.status == "200") { 
					client.handleResponse(this); 
				} 
			} 
    	}; 
		if(method == "post") { 
			xhr.open(method, url, true );			 
			xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); 
			xhr.send(data); 
		} else { 
			xhr.open(method, url+"?"+data, true );	 
			xhr.send(null); 
		}
		return false; 
	}, 

	getXHR: function() { 
		var xhr = false;	 
		if (window.ActiveXObject) { 
			try { 
				xhr = new ActiveXObject("Msxml2.XMLHTTP"); 
			} catch(e) { 
				try { 
					xhr = new ActiveXObject("Microsoft.XMLHTTP"); 
			  	} catch(e) { 
					xhr = false; 
			  	} 
			} 
		} else if (window.XMLHttpRequest) { 
			try { 
				xhr = new XMLHttpRequest(); 
			} catch(e) { 
				xhr = false; 
			} 
		} 
		return xhr; 
	}, 
	 
	handleResponse: function(xhr) { 
		var text = xhr.responseText; 
		this.onResponse(text); 
	} 

}

La classe HijaxClient rappresenta tutto il necessario per implementare questa tecnica client-side. L'oggetto viene costruito passando al suo costruttore un oggetto contenente la proprietà onResponse che rappresenta la funzione personalizzata che si occupa di gestire la risposta del server. 

Contestualizziamo il tutto

Fino ad ora abbiamo realizzato un componente astratto per implementare Hijax ma non abbiamo parlato di applicazioni d'esempio. In questo paragrafo vedremo come utilizzare la classe definita in precedenza in un caso d'uso pratico.

Andremo a realizzare un semplice sito web composto da 5 pagine ognuna composta (come ormai consuetudine) da un menu e da un blocco per i contenuti. Grazie ad Hijax saremo in grado di utilizzare Ajax, dove è possibile, per aggiornare il blocco principale senza ricaricare l'intera pagina e, nel caso opposto, inviare richieste HTTP classiche.

La struttura della pagina è alquanto banale: sarà composta principalmente da due div (rispettivamente con id menu e content). Il div con id uguale a menu conterrà un elemento ul e tanti elementi li per includere i vari link alle varie pagine mentre il secondo div conterrà il testo vero e proprio. I file contenenti l'HTML non verranno commentati in questo tutorial ma potranno essere trovati all'interno dei sorgenti dell'applicazione.

Per configurare il client basta istanziare un oggetto di classe HijaxClient impostando la callback onResponse che nel nostro caso dovrà modificare il contenuto del div:

var hijax = new HijaxClient({
	onResponse: function(text) {
		document.getElementById(“content”).innerHTML = text;
	}
});

Ovviamente la creazione dell'oggetto HijaxClient deve essere effettuata a posteriori rispetto alla creazione del DOM della pagina altrimenti alcuni elementi creati successivamente non verranno intercettati. Per questo motivo l'inizializzazione viene effettuata al load della pagina:

window.onload = function() { 
	new HijaxClient( [...] ); 
}

Una  volta inizializzato l'oggetto con la relativa funzione per la gestione delle response possiamo concentrarci sulla parte server-side.

Server

Rendiamo i componenti modulari

Il primo passo per implementare Hijax sul server è quello di rendere i vari componenti modulari tra di loro permettendo un loro assemblamento in base alle richieste del client.

Per questo motivo è necessario separare le componenti comuni ad entrambe le tipologie di richieste (Ajax e HTTP classico) da quelle che rientrano in una sola di queste possibilità. Per comodità ho suddiviso le pagine in 4 file:

  • header.php (testata della pagina - posizionato dentro la cartella inc/)
  • menu.php (menu contenente i link - posizionato dentro la cartella inc/)
  • home.php (contenuto della home page – posizionato dentro la cartella pages/)
  • footer.php (piè di pagina – posizionato dentro la cartella inc/)

Ovviamente questa architettura è condivisa da tutte le pagine del sito: basta infatti sostituire il file home.php con qualsiasi altro file contenuto dentro la cartella pages/. Oltre al file home.php infatti in questa cartella troveremo i file:

  • about-us.php
  • contatti.php
  • prodotti.php
  • servizi.php

Ovviamente ciascuna di queste pagine sarà associata ad un link nel menu del sito web.

Il Dispatcher

Il miglior modo per assemblare i diversi blocchi realizzati in precedenza è quello di creare un dispatcher, ovvero un componente che riceve tutte le richieste e in base alla presenza o meno del parametro usato come discriminatore (nel nostro caso il parametro ajax) carichi le informazioni necessarie per la risposta. Per realizzare un dispatcher in PHP si possono utilizzare due diverse tecniche. 

La prima, che è quella effettivamente più interessante e scalabile, è quella di utilizzare il mod_rewrite di Apache e il file .htaccess per definire il mapping tra le richieste e il file da caricare. Questa soluzione non è però percorribile in quanto non è sempre disponibile questo modulo all'interno del nostro server web.

La seconda soluzione prevede di forzare la richiesta della stessa pagina per qualsiasi richiesta utilizzando però un parametro (per esempio page) per identificare quale è effettivamente la pagina richiesta dal client. Questo sarà l'approccio utilizzato in questo tutorial e precisamente verrà utilizzato il parametro page e quindi degli url del tipo server.php?page=contatti.

È importante sottolineare che la parte di codice in PHP è esattamente la stessa per entrambi gli approcci. L'unica differenza è presente nel file .htaccess e nel file HTML dove vengono definiti gli url delle pagine in quanto nel primo caso sarà possibile creare degli url virtuali del tipo contatti.html mentre nel secondo caso si dovranno utilizzare url fisici con il parametro page (per esempio appunto  server.php?page=contatti).

Il dispatcher sarà contenuto nel file server.php che si occuperà di controllare la pagina richiesta e la presenza o meno di JavaScript e Ajax sul browser che ha effettuato la richiesta. In base a queste informazioni caricherà le componenti necessarie, le assemblerà e risponderà alla richiesta con una risposta “ad hoc”.

Il codice della pagina è abbastanza banale:

<?php 

$page = $_REQUEST['page']; 
$ajax = isset($_REQUEST['ajax']) && $_REQUEST['ajax'] == 1; 

$pages = array(); 
$pagesDir = "./pages"; 
$dir = opendir($pagesDir); 
while(false !== ($file = readdir($dir))) { //carico nel vettore $pages l'elenco della pagine presenti per evitare attacchi di qualsiasi tipo
	if(is_file($pagesDir."/".$file)) { 
		$pages[] = substr($file,0,strpos($file,".")); 
	} 
} 

if(!in_array($page, $pages)) { //controllo che la pagina richiesta esiste
	$page = "home"; //altrimenti forzo home
} 

if(!$ajax) { 
	include("inc/header.php"); 
	include("inc/menu.php"); 
	include("pages/$page.php"); 
	include("inc/footer.php"); 
} else { 
	include("pages/$page.php"); 
} 

?>

Da come possiamo facilmente leggere dal codice la risposta conterrà le 4 componenti nel caso il client non disponga di funzionalità Ajax (e quindi $ajax è falso). Altrimenti verrà inviato dal server solamente il contenuto della pagina richiesta tralasciando le altre parti.

L'entry point

L'ultimo aspetto mancante riguarda l'entry point dell'applicazione, ovvero la prima risposta che il server deve effettuare e che non è influenzata dalle caratteristiche del browser in quanto sarà sempre composta da tutti i blocchi.

Il file index.php ricopre questo ruolo:

<?php 
include("inc/header.php"); 
include("inc/menu.php"); 
include("pages/home.php"); 
include("inc/footer.php"); 
?>

Conclusioni

In questo articolo abbiamo introdotto una nuova tecnica web che può accompagnare Ajax per la realizzazione di pagine dinamiche ma allo stesso tempo accessibili. Grazie ad Hijax possiamo facilmente realizzare una doppia versione del sito che si adatta completamente alle caratteristiche del browser che sta richiedendo le pagine.

L'implementazione di Hijax come abbiamo visto è abbastanza banale. L'unica difficoltà è rappresentata dall'organizzazione e architettura del server che per essere funzionale e flessibile deve essere modulare e deve presentare componenti tra di loro “staccati” e che vengono assemblati in base alle esigenze. Nel nostro esempio abbiamo utilizzato un dispatcher centralizzato che aveva come compito proprio quello di “montare” la pagina di risposta.

I test sono stati effettuati sia con browser Ajax-compliant sia con Lynx, il famoso browser testuale presente in tutte le distribuzioni Linux. Ovviamente in quest'ultimo caso dove Javascript e Ajax non sono implementati, l'applicazione non ha avuto problemi di nessun tipo e le richieste effettuate sono state classiche request HTTP.

Hijax è una tecnica ancora molto giovane e non sono ancora presenti sul web framework e librerie che la implementano in maniera stabile e usabile ma non per questo significa che non è possibile utilizzarla con discreto successo anche in ambienti di produzione.

Come al solito sono stati tralasciati alcuni aspetti per il semplice motivo di rimanere legati all'argomento dell'articolo senza andare fuori tema parlando di altri argomenti.

Buon Hijax a tutti!


Ti consigliamo anche