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

xToDo - Gestiamo i nostri progetti con Ajax e JQuery

Un completo tutorial passo per passo per la creazione di un'applicazione dinamica basata su Ajax e sul framework JQuery
Un completo tutorial passo per passo per la creazione di un'applicazione dinamica basata su Ajax e sul framework JQuery
Link copiato negli appunti

Introduzione

Dopo aver introdotto il mese scorso JQuery, uno dei framework Javascript oggi più apprezzati, in questo lungo articolo/tutorial metteremo in pratica le nozioni teoriche apprese realizzando un'applicazione che permetterà di gestire la creazione di progetti e dei relativi task, dando la possibilità all'utente di aggiungere contenuti e di flaggare un determinato task come completato o meno.

Il tutorial vuole in primo luogo presentare da un punto di vista meno scolastico il framework in questione ma anche offrire ai lettori un metodo di sviluppo Javascript abbastanza strutturato che permette di separare in maniera corretta i vari strati dell'applicazione in diversi componenti, garantendo una facilità non solo di progettazione ma anche di modifiche future.

Questo pattern di sviluppo ricalca alcuni pattern spesso usati nella tecnologia server-side, in cui l'organizzazione e l'ordine del codice sono sicuramente fattori vincenti non solo per le applicazioni di grandi dimensioni. Rispetto a quelle di qualche anno fa, nelle applicazioni web moderne sono molti i compiti che vengono delegati dal server al client; questo rende quasi obbligatoria una maggior cura nell'organizzazione del codice anche sulla macchina client. La poca tipizzazione e la dinamicità di Javascript possono infatti essere armi pericolose date in mano allo sviluppatore inesperto che rischia di realizzare applicazioni magari funzionanti, ma notevolmente disordinate e quindi poco manutenibili e scalabili.

Potete sin da ora verificare il funzionamento dell'applicazione che creeremo utilizzando questa demo funzionante. Per scaricare i sorgenti potete invece fare riferimento a questa pagina. Sul forum di HTML.it è anche aperto un thread creato appositamente per discutere dei più svariati aspetti legati al tutorial e all'applicazione.

La struttura dell'applicazione

L'applicazione in realtà può essere scomposta, come tutte le applicazioni client-server, in due sotto-applicazioni. Essendo questo tutorial redatto tenendo conto principalmente della parte client, non ci soffermeremo molto sulla componente server ma sposteremo il focus appunto sull'utilizzo pratico di JQuery.

La prima di queste applicazioni che analizzeremo sarà appunto la parte client.

La comunicazione tra client e server

Le due macro-componenti dell'appliacazione comunicano tramite file XML: il client invia i parametri tramite POST e il server risponde con documenti XML che verranno correttamente interpretati dal client. I file XML sono facilmente leggibili e comprensibili quindi non dedicherò ad essi molto tempo ora: definirò la loro struttura all'interno di ciascun caso d'uso.

Il modello dei dati

I modelli di dati che verranno gestiti sono relativamente semplici. Esistono due oggetti: il progetto e il task.
Ciascun progetto ha un nome, un testo con note, una data di inizio e un elenco di task; ciascun task presenta un nome, un testo con note, una data di inizio e una di chiusura (questo permette di capire se il task è completato o ancora attivo) e un livello di priorità (alta o normale). Avere chiara in mente questa modellazione tornerà utile nel corso del tutorial.

Il componente client-side

Il pattern di sviluppo client-side

L'applicazione a livello client viene scomposta in diversi strati (o layer), ognuno delegato ad un determinato compito. Questi strati sono organizzati in maniera gerarchica partendo da strati che si occupano di generare l'interfaccia grafica e recepire gli eventi scatenati dall'utente fino ad arrivare a strati interni che si occupano di inviare le richieste al server e di capire ed eseguire le sue risposte.
Uno schema della struttura di questi layer potrebbe essere questo:

Figura 1 - Diagramma dei layer dell'applicazione
diagramma

Il componente di più alto livello è il DOM layer che si occupa principalmente di generare la pagina HTML tramite costrutti esposti dal framework Javascript e di associare a determinati eventi una funzione particolare appartenente al layer dei controller.

L'arco in uscita A caratterizza appunto l'unica funzionalità di questo componente che è quella di generare costrutti HTML che andranno a comporre o modificare la pagina visualizzata dal client.

Esiste un secondo arco (H) che riguarda invece eventi che, invocati, permettono semplicemente di modificare l'aspetto grafico (apertura di finestre, zoom, ecc...).

Una volta che la pagina HTML viene costrutita e renderizzata dal browser, l'unico modo che l'utente ha per interagire con l'applicazione sono gli eventi (B). Essi sono impostati dal componente DOM e permettono di invocare una determinata funzione controller quando l'utente esegue una particolare azione (click su un elemento, submit di un form ...).

Le funzioni controller, una volta invocate dal browser, andranno ad ottenere, tramite l'oggetto event, maggiori informazioni sull'evento che le ha scatenate, per esempio l'oggetto target sul quale l'evento è "scattato" (comodo per poter usare la stessa funzione su diversi elementi HTML senza differenziare la funzione). Esse saranno incaricate quindi di chiamare (C) la relativa funzione service passandogli i parametri adeguati ottenuti dall'interfaccia utente.

Le funzioni di servizio sono la parte core dell'applicazione: rappresentano l'insieme dei casi d'uso disponibili all'utente finale. In un'applicazione client-server come la nostra sarà all'interno di queste funzioni che verranno create le connessioni con il server. Una funzione di servizio potrà quindi invocare un'altra funzione di servizio privata più specifica per una determinata azione senza ripassare da un controller in quanto è una sorta di redirect interno (D) o aprire una connessione Ajax con il server per ottenere nuove informazioni o per causare un cambiamento di stato di qualche oggetto (E).

Il caso sicuramente più interessante è quello nel quale il service layer invoca un metodo del layer Ajax per inviare una richiesta al server. La funzione di servizio andrà quindi a chiamare una funzione Ajax impostandogli una funzione callback (in caso di richiesta asincrona) o attendendo in maniera sincrona la risposta del server. Una volta ottenuta la response dal server, sarà il componente ajax a parsare i dati ricevuti per renderli disponibili alla funzione callback (e alla stessa funzione di servizio invocante) in maniera trasparente alla funzione di servizio. Ricevuta la risposta dal server la funzione callback potrà a sua volta invocare un ulteriore funzione di servizio (F) eseguendo un ulteriore redirect interno oppure tramite i componenti DOM modificare la pagina visualizzata dall'utente (G).

La descrizione teorica dei meccanismi di funzionamento può senza dubbio lasciare perplesso lo sviluppatore alle prime armi che di fronte a tanti "maccanismi" potrebbe non capire i vantaggi di un simile approccio. Utilizzando invece un pattern di questo tipo la nostra applicazione seguirà uno schema logico ben preciso e ci permetterà di evitare errori procedurali e semplificherà non di poco eventuali interventi successivi allo sviluppo (anche di terzi) che troveranno uno schema chiaro e ordinato. Ammetto di aver esagerato un po' con la teoria... Ora infatti passiamo ai fatti!

Partiamo con lo sviluppo

L'applicazione è composta principalmente da un file HTML che sarà il punto di riferimento centrale del browser. Sarà infatti il file HTML che includerà tutti i file necessari all'applicazione: dal file CSS che descrive l'aspetto grafico alle librerie JavaScript utilizzate sia proprie dell'applicazione che di terzi. Le librerie proprie della nostra applicazione sono composte da 6 file:

  • todo.js - rappresenta il componente core che inizializza il namespace utilizzato, l'applicazione e alcune variabili comuni ai layer;
  • todo.dom.js - implementa il DOM-layer;
  • todo.controller.js - implementa il controller-layer;
  • todo.service.js - implementa il service-layer;
  • todo.ajax.js - implementa l'Ajax-layer;
  • util.js - espone alcune funzioni di utilità Javascript.

Questa suddivisione risulta comoda dal punto di vista dello sviluppo in quanto avere file separati non crea la stessa confusione che si avrebbe lavorando su un unico lungo file, ma poco performante in un ambiente di sviluppo in quanto il client è obbligato ad aprire 6 diverse richieste al server (una per ogni file). Essendo questo un tutorial a scopo didattico ho preferito puntare di più sulla comprensione e la facilità. Nessuno vieta di creare un unico file composto da tutti i componenti. Tutte le funzioni faranno capo ad un unico namespace identificato da una stringa identificatrice dell'applicazione (todo) e una stringa identificatrice del layer di riferimento.

Per esempio la funzione todo.service.getAllProjects() apparterrà al service-layer mentre la funzione todo.ajax.getData() all'Ajax-layer. Una naming convention chiara e precisa come questa non può che agevolare lo sviluppo soprattutto quando si è in diverse persone a lavorare sulle stesse funzioni.

Le componenti JQuery utilizzate e la libreria XML2JSON

Il framework JQuery espone moltissime funzioni ma spesso non tutte vengono utilizzate nella medesima applicazione. Ho cercato di utilizzarne il più possibile evitando però di perdere la rotta rispetto all'applicazione finale. Le componenti del framework utilizzate, oltre alla core, sono:

  • selettori - per ottenere elementi del DOM a partire dal loro tagname o da attributi particolari;
  • gestore attributi - per modificare a run-time gli attributi degli elementi del DOM;
  • gestore manipolazione - per creare in modo programmatico gli elementi del DOM in caso di modifica della pagina;
  • css - per modificare l'aspetto grafico di alcuni elementi;
  • gestore eventi - per associare le funzioni del controller-layer ad un comportamento dell'utente;
  • animatore - per creare animazioni standard;
  • gestore Ajax - per creare connessioni con il server.

Oltre a questi componenti di JQuery, ho utilizzato una comoda libreria, chiamata XML2JSON che permette un rapido parsing di file XML ottenendo come output un oggetto JSON.

I casi d'uso dell'applicazione

Caso numero 0 - L'inizializzazione dell'applicazione

Il file index.html non presenta nient'altro che i riferimenti agli script necessari all'applicazione e la creazione di un markup di base che verrà mano a mano riempito dal DOM layer. Una volta caricata la pagina viene invocata la funzione todo.init() tramite l'helper messo a disposizione da JQuery.

$(todo.init); 

Il file todo.js ha un duplice scopo. Da un lato definisce la funzione todo.init() che si occupa di inizializzare i vari livelli dell'applicazione e di invocare i primi servizi necessari per l'avvio, e dall'altro definisce alcune costanti fondamentali per l'applicazione (nel nostro caso solamente i vari url di comunicazione con il server).

var todo = {
	init : function() {
		todo.ajax.init();
		todo.dom.init();
		todo.service.loadMenu();
		todo.service.getHomepage();
	},
	url : {
		countAllObject: "server/countAllObjects",
		getAllProjects : "server/getAllProjects",
		getAllTasks : "server/getAllTasks",
		getProject : "server/getProject",
		changeTaskStatus: "server/changeTaskStatus",
		removeTask: "server/removeTask",
		newTask: "server/newTask"
	}	
};

La funzione todo.ajax.init() configura l'oggetto globale $.ajax impostando alcuni comportamenti comuni a tutte le successive richieste Ajax

todo.ajax = {
	init: function() {
		$.ajaxSetup({
			dataType: "text",
			beforeSend : todo.ajax.events.onStartLoading,
			complete : todo.ajax.events.onStopLoading,
			error : todo.ajax.events.onError
		});		
	}
}

Viene impostato il dataType della response come "text" e non come "xml" perché utilizziamo il parser esposto dalla libreria XML2JSON e non quello integrato in JQuery perché meno "navigabile" soprattutto senza l'ausilio di plugin. Inoltre settiamo tre callback in base agli eventi collegati all'Ajax manager: beforeSend, complete ed error. Tutti e tre questi callback sono inclusi, ovviamente, dentro l'Ajax layer.

Grazie a JQuery è possibile configurare questi comportamenti solo una volta per tutta l'applicazione delegando la loro invocazione al framework:

todo.ajax = {
	events : {
		onStartLoading : function() {
			if(todo.ajax.status.concurrentConnections==0) {
				todo.ajax.status.isLoading = true;   
				$('img#loadImg').css({display:'block'});										
			}
			todo.ajax.status.concurrentConnections++;	         
			
		},
		onStopLoading : function() {
			todo.ajax.status.concurrentConnections--;	 
			if(todo.ajax.status.concurrentConnections==0) {
				todo.ajax.status.isLoading = false;   
				$('img#loadImg').css({display:'none'});										
			}
		},
		onError : function(xhr, status, error) {
			isLoading = false;  
			todo.ajax.status.concurrentConnections--;
			alert("ERROR:nstatus: "+status+"nerror: "+error);
		}
	},
	status : {
		concurrentConnections : 0,
		isLoading : false
	}
}

Nonostante i metodi possano sembrare complessi, analizzandoli bene si capisce che semplicemente si occupano di mostrare (o nascondere) l'immagine con id="loadImg" tramite un comodo selettore JQuery e le funzioni per la manipolazione del CSS in base al numero di connessioni avviate in quel momento, salvando in due variabili lo stato attuale delle cose (todo.ajax.status.concurrentConnections come intero e todo.ajax.status.isLoading come booleano). Questo perché c'è la possibilità che esistano contemporaneamente più connessioni avviate con il server e ovviamente il messaggio di caricamento deve interrompersi solamente quando tutte le connessioni sono state chiuse.

Un discorso a parte lo merita la funzione todo.ajax.events.onError(), invocata in caso di problemi di connessione con il server, che oltre a stampare la stringa ERROR mostra anche alcune variabili di debug interne a JQuery. Una volta impostate queste funzioni non ci dovremo più preoccupare della gestione delle connessioni Ajax in quanto sarà JQuery ad occuparsi di tutto in maniera trasparente.

L'inizializzazione vera a propria della pagina HTML avviene grazie alla funzione todo.dom.init().

todo.dom = {
	init : function() {
		var elems = new Array();
		var li = $("<li class='general selected'>Homepage</li>");
		li.click(todo.controller.getHomepage);
		var ul = $("<ul id='tabs'></ul>");
		ul.append(li);
		var cont = $("div#container")
		cont.append( ul );
		cont.append( $("<br style='clear:both'>") );
		cont.append( $("<div id='content'></div>") );
		$("body").append("<img src='opt/load.gif' id='loadImg'/>" );
	} 
}

La funzione costruisce una lista di item (gestiti come tab grazie ai CSS) inserendo come unico item, per il momento, il collegamento alla pagina iniziale associando come callback dell'evento onclick la funzione todo.controller.getHomepage() (che vedremo successivamente). Oltre alla lista definisce un div con id="content" utilizzato come container per le diverse pagine e l'immagine loadImg che, posizionata assolutamente nei CSS, si occupa di migliorare la user experience nei momenti di comunicazione tra il client e il server. Per la costruzione delle componenti DOM viene utilizzata sempre la funzione helper $() che si occupa di inizializzare il DOM a partire da una stringa HTML.

Viene utilizzato un metodo personalizzato invece del semplice append() esposto da JQuery perché questa versione accetta anche altri parametri come un vettore di elementi da appendere e un booleano che indica se eliminare o meno il vecchio contenuto del nodo padre.

Una volta definito il markup di base della pagina vengono invocate due funzioni di service che si occupano rispettivamente di costruire le tab in base ai progetti salvati sul server (todo.service.loadMenu()) e di evidenziare il numero di oggetti (progetti e task) precedentemente creati (todo.service.getHomepage()). Queste due funzioni, rappresentando dei casi d'uso specifici, verranno descritte successivamente.

Caso 1 - L'inizializzazione delle tabs

Una delle prime componenti dell'applicazione che saltano subito all'occhio come dinamiche sono le tab che, oltre alla prima generata di default, rappresentano ciascun progetto avviato. Per questo motivo la loro costruzione viene considerata un servizio (e quindi un caso d'uso) a sé stante.

Il processo collegato a questo caso d'uso è abbastanza semplice: l'invocazione della funzione todo.service.loadMenu() (invocata in automatico senza interventi dall'utente - quindi senza nessun controller) svuota il menu precedente (mantenendo però la tab principale identificata da un className particolare) e definisce una funzione callback come risposta ad una chiamata asincrona verso il server puntando a todo.url.getAllProjects tramite una funzione esposta dall'Ajax layer: todo.ajax.getData(). Rispetto al grafo iniziale ci stiamo occupando dell'arco E: quando la funzione service invoca una funzione Ajax passandogli una nuova funzione callback.

todo.service = {
	loadMenu : function(selected) {
		$("ul#tabs li:not(.general)").remove();
		todo.ajax.getData(todo.url.getAllProjects, null, function() {
			var projects = util.getAsArray(this.projects.project);	
			for(i in projects) todo.dom.createTab(projects[i]);
			if(selected) todo.dom.selectTab(selected);
		});
	}
}

Il selettore "ul#tabs li:not(.general)" permette appunto di selezionare tutti gli elementi li con un className diverso (not) da general utilizzato apposta per identificare la tab principale dagli altri. Viene poi invocata la funzione dell'Ajax layer passando come terzo parametro una funzione che grazie alla funzione util.getAsArray() permette di ottenere un vettore di projects. Ciascuno di essi poi verrà passato come parametro a todo.dom.createTab(). Inoltre, se esiste il parametro selected, la tab corrispondente viene selezionata grazie all'helper todo.dom.selectTab().

todo.dom = {
	selectTab : function(target) {
		$('ul#tabs li.selected').removeClass("selected");
		$(target).addClass("selected");		
	}
}

Essendo banale come funzione, ritorniamo subito a todo.service.loadMenu(). La funzione todo.ajax.getData() viene utilizzata in maniera simile da tutti i servizi quindi necessita un approfondimento.

todo.ajax = {
	getData: function(url, params, handler) {
		$.post(url, params, function(data) {
			var response = xml2json.parser(data);
			if(response.error) alert("ERROR:n"+response.error);
			else handler.call(response);
		});
	}
}	

Questa funzione rappresenta il cuore di tutte le richieste e si occupa non solo di aprire una connessione verso il server, ma anche di parsare l'XML ricevuto tramite xml2json.parser(), di controllare eventuali errori ricevuti dal server (da non confondere con la gestione errori precedente che gestiva errori di connessione) e di invocare la funzione callback passandogli come unico parametro l'oggetto JSON parserizzato dall'XML. Il protocollo applicativo definito per questa applicazione prevede che eventuali errori dal server veongono comunicati tramite l'invio di un XML di questo tipo:

<?xml version="1.0" encoding="utf-8"?>
<error>Error message</error>

Per questo motivo, una volta costruito l'oggetto JSON response controllo se esiste o meno response.error e in caso affermativo viene interrotta l'esecuzione dello script informando tramite alert l'errore ricevuto dal server; viceversa si continua invocando il callback.

Dopo questa parentesi necessaria torniamo al nostro todo.service.loadMenu(). La struttura dell'XML contenente l'elenco dei progetti è simile a questo:

<?xml version="1.0" encoding="utf-8"?>
<projects>
	<project>
		<id>1</id>
		<name>Project name</name>
		<note>Project notes</note>
		<startdate>2007-01-01</startdate>
	</project>
	...
</projects>

Per questo motivo per accedere al vettore dei progetti utilizziamo this.projects.project. A questo punto ci torna utile la funzione util.getAsArray() in quanto non siamo per niente sicuri di ricevere sempre un vettore: può darsi che non ci siano ancora progetti salvati o che ce ne sia solamente uno. Per evitare controlli scomodi e difficilmente gestibili ci affidiamo alla seguente funzione:

var util = {
	getAsArray: function(node) {
		if(!node) return new Array();
		if(!util.isArray(node)) return new Array(node);
		return node;
	},
	isArray: function(array) {
		return !( !array || (!array.length || array.length == 0) || typeof array !== 'object' || !array.constructor || array.nodeType || array.item ); 
	},
} 

util.getAsArray() ritorna sempre un vettore di lunghezza dipendente dal nodo passato come parametro. Infatti se il nodo non esiste (nel nostro caso nessun progetto salvato) la funzione ritorna un vettore vuoto, se il noto esiste ma non è un vettore (nel nostro caso un solo progetto salvato) la funzione crea un vettore unitario, altrimenti ritorna semplicemente il nodo ricevuto come parametro.

In questo caso siamo certi di avere sempre un vettore da poter comodamente iterare per invocare più volte la funzione todo.dom.createTab() come mostrato dall'arco G invocando una funzione del DOM layer dalla callback:

todo.dom = {
	createTab : function(project) {
		var name = project.name;
		var tab = $('<li>'+name+'</li>');
		tab.bind("click", project.id, todo.controller.getProjectDetails);
		tab.css({display:'none'});
		$('ul#tabs').append(tab);
		tab.fadeIn();
	}
}

La funzione del DOM layer è abbastanza semplice: una volta ottenute le informazioni necessarie dall'oggetto project (in questo caso nome e id) si occupa di appendere al menu la nuova tab ed associargli all'evento onclick l'esecuzione di todo.controller.getProjectDetails() (che vedremo come terzo caso d'uso) e di attivare l'effetto fade sul tab tramite l'animatore JQuery.

In questo caso per associare l'evento al tab abbiamo usato la funzione bind associata alla stringa "click" in quanto era necessario inviare alla funzione callback anche un parametro ovvero l'id del progetto selezionato in quanto la funzione todo.controller.getProjectDetails() è comune a tutti i progetti

Non lasciatevi spaventare dalla lunghezza di questo primo caso d'uso: gli altri avranno moltissimo in comune con esso e la loro implementazione sarà quasi banale.

Caso 2 - La creazione della homepage

Una volta capito il meccanismo service/Ajax analizziamo con più sicurezza la funzione todo.service.getHomepage().

todo.service = {
	getHomepage : function() {
		todo.ajax.getData(todo.url.countAllObject, null, function() {
			todo.dom.homepage(this.counts.projects, this.counts.tasks);
		});
	}
}

L'XML in questione è davvero semplice:

<?xml version="1.0" encoding="utf-8"?>
<counts>
	<projects>10</projects>
	<tasks>100</tasks>
</counts>

La funzione non fa altro che aprire una connessione con il server puntando a todo.url.countAllObject e passare i dati da esso ricevuti (this.count.projects e this.count.tasks) alla funzione del DOM layer che si occuperà di costruire la pagina (todo.dom.homepage()).

todo.dom = {
	homepage : function(countProjects, countTasks) {
		var h1 = $("<h1>xToDo - Gestire progetti con ajax</h1>");
		var div = $("<div>Attualmente ci sono <b>"+countProjects+"</b> progetti e <b>"+countTasks+"</b> task inseriti nell'applicazione.</div>");
		var box = $("<div class='box'></div>");
		var newProjectLink = $("<a href='#'>New project</a>");
		newProjectLink.click(todo.dom.projectForm);
		box.append(newProjectLink);
		var cont = $('div#content');
		cont.empty();
		cont.append(h1);
		cont.append(div);
		cont.append(box);
	}
}

La funzione si occupa semplicemente di costruire alcuni elementi HTML inserendo nei punti opportuni i numeri ricevuti come parametri che rappresentano il numero totale di progetti e di task salvati nel sistema. Inoltre aggiunge un link, associato alla funzione todo.controller.projectForm() per creare un nuovo progetto (caso d'uso successivo). Una volta capiti i meccanismi generali, implementare un nuovo caso d'uso è davvero semplice.

Caso 3 - Il dettaglio di un progetto

I precedenti casi d'uso non implementavano in maniera completa il pattern descritto in precedenza ma partivano direttamente dalla funzione service per poi invocare una richiesta Ajax (tramite l'arco E) e finalmente modificare il DOM della pagina (tramite l'arco G e poi A). Questo caso d'uso, cosi come i successivi, seguirà in maniera più completa il pattern partendo da un evento scatenato dall'utente che invocherà un controller (arco B) che una volta recuperati alcune informazioni sull'evento invocherà a sua volta il servizio relativo all'azione (arco C). Ricordiamoci di proseguire nella lettura mantenendo sempre il focus sul grafo e sulla meccanica dell'applicazione.

L'evento che fa scatenare la visualizzazione di un progetto è il click sulla tab corrispondente e la relativa invocazione di todo.controller.projectDetails() passando come parametro l'id del progetto.

todo.controller = {
	getProjectDetails : function(event) {
		var id = event.data;
		var target = event.target;
		todo.dom.selectTab(target);
		todo.service.getProjectDetails(id);
	}
}	

Una volta ottenuto l'oggetto target (tramite event.target) e il parametro passato alla callback (tramite event.data) selezioniamo con l'helper visto in precedenza la tab e invochiamo il relativo servizio todo.service.getProjectDetails() fornendogli l'id del progetto. In questo modo la funzione service non sa assolutamente nulla di quale evento l'ha scatenata; è infatti compito del controller fare da ponte tra l'evento utente e la funzione di servizio associata. Questo è uno dei punti chiave del pattern applicativo utilizzato: la divisione efficace dei compiti.

todo.service = {
	getProjectDetails : function(id) {
		todo.ajax.getData(todo.url.getProject, {id:id}, function() {
			var project = this.project;
			var tasks = util.getAsArray(project.task);
			todo.dom.projectDetails(project,tasks);
		});
	}
}	

La funzione di servizio invoca, come già visto nelle funzioni precedenti, todo.ajax.getData() passandogli, differentemente dalle funzioni precedenti che utilizzavano null, un parametro da inviare al server. Una volta ricevuta la risposta dal server vengono separati dalla response il progetto e il vettore dei task (notare anche qua l'utilizzo della comoda util.getAsArray()) e vengono passati alla funzione del DOM layer todo.dom.projectDetails() che si occuperà di mostrare i dati ricevuti all'utente.

L'XML relativo a questa richiesta è il piu complesso dell'applicazione in quanto presenta nello stesso documento sia informazioni relative al progetto sia ai suoi task.

<?xml version="1.0" encoding="utf-8"?>
<project>
	<id>1</id>
	<name>Project name</name>
	<note>Project notes</note>
	<startdate>2007-11-24</startdate>
	<task>
		<id>11</id>
		<project_id>1</project_id>
		<name>Task name</name>
		<note>Task notes</note>
		<user>Task user</user>
		<priority>0</priority>
		<startdate>2007-01-01</startdate>
		<enddate>2007-02-01</enddate>
	</task>
	...
</project>

L'unico campo che può non esserci è enddate: in questo caso significa che il task è ancora attivo e non è stato chiuso.

todo.dom = {
	projectDetails : function(project,tasks) {
		var h1 = $("<h1>Dettaglio progetto "+project.name+"</h1>");
		var box = $("<div class='right box'>Progetto creato il "+project.startdate+"</div>");
		var note = $("<em>"+project.note+"</em>");
		var brs = $("<br class='clear'/><br class='clear'/>");
		var box2 = $("<div class='box'></div>");
		var removeProjectLink = $("<a class='right' href='#'>Remove project</a>");
		removeProjectLink.bind( "click", project.id, todo.controller.removeProject );
		box2.append(removeProjectLink);
		var newTaskLink = $("<a href='#'>New task</a>");
		newTaskLink.bind( "click", project.id, todo.dom.taskForm );
		box2.append(newTaskLink);		
		
		var table = $("<table></table>");
		var th = $("<tr><th></th><th>Task</th><th>User</th><th>Data creazione</th><th>Data chiusura</th></tr>");
		table.append(th);
		for(var t = 0; t<tasks.length; t++) {
			var status = tasks[t].enddate ? "open" : "close";
			var priority = tasks[t].priority == 1;
			var tr = $("<tr class='"+status+"'></tr>");		
			var tds = new Array();	
			if(priority) tds.push($("<td align='center' width='2%'><img src='opt/priority.gif' alt='priority'/></td>"));
			else tds.push($("<td width='2%'></td>"));
			tds.push($("<td>"+tasks[t].name+"<br/><em>"+tasks[t].note+"</em></td>"));
			tds.push($("<td width='10%'>"+tasks[t].user+"</td>"));
			tds.push($("<td width='10%'>"+tasks[t].startdate+"</td>"));
			if(tasks[t].enddate) {
				tds.push($("<td width='10%'>"+tasks[t].enddate+"</td>"));
				tds.push($("<td width='2%'></td>"));
				var changeStatusImg = $("<img src='opt/opentask.png' alt='opentask' class='link'/>");
				changeStatusImg.bind("click", {status:0, id:tasks[t].id}, todo.controller.changeTaskStatus );
			} else {
				tds.push($("<td width='10%'></td>"));
				tds.push($("<td width='2%'></td>"));
				var changeStatusImg = $("<img src='opt/closetask.png' alt='closetask' class='link'/>");
				changeStatusImg.bind("click", {status:1, id:tasks[t].id}, todo.controller.changeTaskStatus );
			}		
			tds[5].append(changeStatusImg);
			tds.push($("<td width='2%'></td>"));
			var removeTaskImg = $("<img src='opt/removetask.png' alt='removetask' class='link'/>");
			removeTaskImg.bind("click", tasks[t].id, todo.controller.removeTask);
			tds[6].append(removeTaskImg);
			for(i in tds) tr.append(tds[i]);
			table.append(tr);
		}		
		table.css({display:"none"});
		var cont = $('div#content');
		cont.empty();
		cont.append(h1);
		cont.append(box);
		cont.append(note);
		cont.append(brs);
		cont.append(box2);
		cont.append(table);
		table.slideDown("slow");
	}

La funzione è abbastanza lunga ma non perchè complessa. Infatti, le parti importanti non sono molte.

Innanzitutto notiamo l'assegnazione delle funzioni todo.controller.removeProject() e todo.dom.taskForm() al click su due link removeProjectLink e newTaskLink. La prima si occuperà di rimuovere il progetto attivo mentre la seconda di mostrare il form per la creazione di una nuova task per il progetto corrente (arco H del grafo iniziale).

Andando avanti con la funzione troviamo un semplice if che mostra o meno l'immagine "opt/priority.gif" in base alla priorità del task. In base poi al fatto che il task sia o meno chiuso (quindi in base all'esistenza della proprietà enddate) viene mostrata la data di chiusura e un'immagine con associata la funzione todo.controller.changeTaskStatus() che riceverà come parametro un oggetto JSON contenente oltre all'id del task anche un identificatore del nuovo status (0 o 1). Per ultima, all'immagine "opt/removetask.png" verrà associata la funzione todo.controller.removeTask() sempre passandogli l'id del task.

La costruzione della pagina si concluderà con un effetto di slideDown sulla tabella contenente i task.

Caso 4 - La creazione di un progetto

Il caso d'uso numero 4 viene avviato dalla pressione del link presente nella home page che richiama, come visto in precedenza, la funzione todo.dom.projectForm(). Questa azione ricalca, nel grafo iniziale, l'arco H, ovvero quando un evento richiama una funzione del DOM layer per semplicemente modificare l'aspetto grafico dell'applicazione, senza effettivamente influenzare niente di strutturale. In questo caso infatti la funzione non fa nient'altro che mostrare in primo piano un form per l'aggiunta di un nuovo progetto:

todo.dom = {
	projectForm : function(){
		var div = $("<div id='projectForm'></div>");
		div.append($("<h2>New Project</h2>"));
		var form = $("<form action='#' method='post'></form>");
		form.submit(todo.controller.projectFormSubmit);
		var elems = new Array();
		elems.push($("<label for='project_name'>Name</label>"));
		elems.push($("<input type='text' name='project_name'/>"));
		elems.push($("<br/><br/>"));
		elems.push($("<label for='project_note'>Note</label>"));
		elems.push($("<textarea name='project_note'></textarea>"));	
		elems.push($("<br/><br/>"));
		elems.push($("<input type='submit' class='button' value='Save!'/>"));
		var closeButton = $("<input type='button' class='button' value='Close!'/>");
		closeButton.click( todo.dom.projectFormClose );
		elems.push(closeButton);
		todo.dom.appendTo(elems, form);
		div.append(form);
		todo.dom.appendTo(div, $('div#content'), true);
	}
}

Le parti interessanti sono fondamentalmente tre. Innanzitutto viene assegnato al div creato l'id "projectForm" per poterlo posizonare assolutamente con i CSS. Poi vengono impostati due callback rispettivamente per il submit del form (todo.controller.projectFormSubmit()) e per il bottone di chiusura della finestra (todo.dom.projectFormClose()).

todo.dom = {
	projectFormClose : function() {
		 $("div#projectForm").remove();
	}
}

Non fa nient'altro infatti che rimuove il div creato in precedenza.

Sicuramente più interessante è la funzione del controller layer incaricata di gestire il submit del form:

todo.controller = {
	projectFormSubmit : function() {
		var name = $.trim($("input[name='project_name']").val());
		var note = $.trim($("textarea[name='project_note']").val());
		if(name.length == 0 || note.length == 0) {
			alert("Project invalid!");
		} else {	
			todo.service.projectFormSubmit(name, note);
			todo.dom.projectFormClose();
		}
		return false;		
	}
}

Il callback infatti esegue una minima validazione dei campi e in caso di successo invoca la funzione di servizio todo.service.projectFormSubmit() passandogli come parametro appunto i due campi del form e successivamente chiude la finestra. Da notare è senza dubbio il return false: questo impedisce la propagazione dell'evento che in caso positivo farebbe inviare al browser il form in maniera standard, quindi con una richiesta HTTP normale, richiesta da evitare se stiamo realizzando un'applicazione Ajax.

todo.service = {
	projectFormSubmit : function(name, note) {
		todo.ajax.getData(todo.url.newProject, {name: name, note: note}, function() {
			alert("Project created!");
			todo.service.loadMenu($('ul li:last'));			
			todo.service.getProjectDetails(this.project.id);
		});
	}
}

La funzione di servizio non è molto diversa da tutte le altre: apre infatti una connessione con il server e, in caso di risposta positiva, modifica la struttura della pagina inserendo un nuovo tab posizionandosi sulla pagina inerente il progetto appena creato (grazie al selettore 'ul li:last'). La struttura dell'XML ritornato dal server è simile a quelle viste in precedenza riguardo al dettaglio del progetto.

L'unica nota particolare di questa funzione è il fatto che come callback della richiesta ajax non ci sia la diretta modifica del DOM della pagina, ma l'invocazione di una nuova funzione di servizio: si tratta infatti dell'arco F del grafo iniziale.

Caso 5 - La rimozione di un progetto

La rimozione di un progetto rappresenta forse il caso d'uso più semplice dell'applicazione. L'evento che lo fa scatenare è il click sul link presente nel dettaglio di un progetto che invoca la funzione todo.controller.removeProject() passandogli come parametro l'id del progetto.

todo.controller = {
	removeProject : function(event) {
		if(window.confirm("Are you sure??")) {
			var id = event.data;
			todo.service.removeProject(id);
		}
	} 
}

La funzione non fa altro che chiedere conferma all'utente e propagare l'id ricevuto alla funzione di servizio todo.service.remoreProject().

todo.service = {
	removeProject : function(id) {
		todo.ajax.getData(todo.url.removeProject, {id:id}, function() {
			alert("Project removed!");
			todo.service.loadMenu($('ul#tabs li.general'));
			todo.service.getHomepage();
		});			
	}
}

Il servizio apre una connessione verso todo.url.removeProject e in caso di successo mostra la homepage iniziale utilizzando servizi già definiti precedentemente.

Caso 6 - La creazione di un task

Questo caso d'uso è molto simile al caso 4 riferito alla creazione di un nuovo progetto. Anche la creazione di un task viene scatenata dal click sul link presente nel dettaglio di un progetto che richiama la funzione todo.dom.taskForm() incaricata di mostrare all'utente il form.

todo.dom = {
	taskForm : function(event){
		var div = $("<div id='taskForm'></div>");
		div.append($("<h2>New Task</h2>"));
		var form = $("<form action='#' method='post'></form>");
		form.bind("submit", event.data, todo.controller.taskFormSubmit );
		var elems = new Array();
		elems.push($("<label for='task_name'>Name</label>"));
		elems.push($("<input type='text' name='task_name'/>"));
		elems.push($("<br/><br/>"));
		elems.push($("<label for='task_user'>User</label>"));
		elems.push($("<input type='text' name='task_user'/>"));		
		elems.push($("<br/><br/>"));
		elems.push($("<label for='task_note'>Note</label>"));
		elems.push($("<textarea name='task_note'></textarea>"));	
		elems.push($("<br/><br/>"));
		elems.push($("<label for='task_priority'>Priority</label>"));
		elems.push($("<select name='task_priority'><option value='0'>Normal</option><option value='1'>High</option></select>"));
		elems.push($("<br/><br/>"));
		elems.push($("<input type='submit' class='button' value='Save!'/>"));
		var closeButton = $("<input type='button' class='button' value='Close!'/>");
		closeButton.click( todo.dom.taskFormClose );
		elems.push(closeButton);
		todo.dom.appendTo(elems, form);
		div.append(form);
		todo.dom.appendTo(div, $('div#content'), true);
	}
}

La funzione associa al submit del form il controller todo.controller.taskFormSubmit() a alla pressione del closeButton il callback todo.dom.taskFormClose(). L'unico aspetto sul quale soffermarci è la propagazione del parametro (l'id del progetto padre) impostato dal DOM layer alla funzione responsabile di gestire il submit.

todo.dom = {
	taskFormClose : function() {
		 $("div#taskForm").remove();
	}
}

Il controller quindi esegue la solita validazione e propaga i dati letti dagli input del form alla funzione di servizio todo.service.taskFormSubmit().

todo.controller = {
	taskFormSubmit : function(event) {
		var project_id = event.data;
		var name = $.trim($("input[name='task_name']").val());
		var note = $.trim($("textarea[name='task_note']").val());
		var user = $.trim($("input[name='task_user']").val());
		var priority = $("select[name='task_priority']").val();
		if(name.length == 0 || note.length == 0 || user.length == 0) {
			alert("Task invalid!");
		} else {	
			todo.service.taskFormSubmit(project_id, name, note, user, priority);
			todo.dom.taskFormClose();
		}
		return false;
	}
}

Guardiamo ora la funzione di servizio.

todo.service = {
	taskFormSubmit : function(project_id, name, note, user, priority) {
		todo.ajax.getData(todo.url.newTask, {project_id: project_id, name: name, note: note, user: user, priority: priority}, function() {
			alert("Task created!");
			todo.service.getProjectDetails(this.task.project_id);
		});	
	}
}

L'XML ricevuto dal server ricalca l'oggetto task appena creato e permette di ottenere l'id del progetto da passare come parametro al servizio todo.service.getProjectDetails() (redirect interno - arco D del grafo).

Una volta capiti i meccanismi e i processi interni, aggiungere nuovi casi d'uso è davvero semplice, e questi ultimi casi d'uso lo dimostrano.

Caso 7 - La modifica dello stato di un task

Una task può assumere due stati in base alla presenza o meno di una data di chiusura dell'attività. L'apertura e la chiusura di un task condividono le stesse funzioni che gestiscono lo status in base ad un parametro che può assumere un valore booleano 0 o 1.

La funzione che gestisce l'evento è todo.controller.changeTaskStatus che riceve come parametro, oltre all'id della task, appunto anche un identificatore del nuovo status che dovrà essere comunicato al server.

todo.controller = {
	changeTaskStatus : function(event) {
		var status = event.data.status;
		var id = event.data.id;	
		todo.service.changeTaskStatus(id, status);		
	}
}

In questo caso il controller non fa nient'altro che estrapolare da event.data i due parametri e invocare il servizio relativo passadoglieli.

todo.service = {
	changeTaskStatus : function(id, status) {
		todo.ajax.getData(todo.url.changeTaskStatus, {id:id, status:status}, function() {
			todo.service.getProjectDetails(this.task.project_id);
		});	
	}
}

Il servizio una volta ricevuti i parametri li invierà tramite todo.ajax.getData() al server e in caso di successo non farà nient'altro che refreshare la pagina corrente tramite l'invocazione di un altro servizio, todo.service.getProjectDetails passandogli come parametro l'id del progetto corrente. Tutto molto chiaro e lineare!

Caso 8 - La rimozione di un task

Come per la creazione di una task, anche la rimozione segue il processo già realizzato in fase di rimozione di un progetto. Tutto prende il via dal controller todo.controller.removeTask attivato al click sull'immagine.

todo.controller = {
	removeTask : function(event) {
		if(window.confirm("Are you sure??")) {
			var id = event.data;
			todo.service.removeTask(id);
		}
	}
}

La funzione, che viene invocata con un parametro rappresentante l'id della task, dopo un apposita conferma all'utente, delega il servizio corrispondente a comunicare con il server.

todo.service = {
	removeTask : function(id) {
		todo.ajax.getData(todo.url.removeTask, {id:id}, function() {
			todo.service.getProjectDetails(this.task.project_id);
		});	
	}
}

Il servizio non fa nient'altro che inviare una richiesta puntando a todo.url.removeTask passandogli come parametro l'id ricevuto dal controller e a refreshare la pagina tramite un redirect interno di servizi.

Conclusioni

Nonostante i primi casi d'uso abbiano necessitato di un lungo approfondimento, lo sviluppo dei successivi è stato banale potendo contare su una struttura consolidata, ben organizzata e ordinata. Il tempo "perso" all'inizio è stato ripagato senza dubbio dalla velocità con la quale gli ultimi casi d'uso sono stati implementati e con la quale verranno eventualmente realizzati nuovi servizi. Nella prossima e ultima parte del tutorial verrà introdotta, seppure in maniera superficiale, la componente server-side che si occupa di eseguire le operazioni su DB e a comunicare con il client tramite file XML.

Conclusioni

Nonostante i primi casi d'uso abbiano necessitato di un lungo approfondimento, lo sviluppo dei successivi è stato banale potendo contare su una struttura consolidata, ben organizzata e ordinata. Il tempo "perso" all'inizio è stato ripagato senza dubbio dalla velocità con la quale gli ultimi casi d'uso sono stati implementati e con la quale verranno eventualmente realizzati nuovi servizi. Nella prossima e ultima puntata del tutorial verrà introdotta, seppure in maniera superficiale, la componente server-side che si occupa di eseguire le operazioni su DB e a comunicare con il client tramite file XML.

Il componente server-side

Introduzione e struttura dati

Il componente server-side è stato realizzato con il linguaggio PHP che permette di realizzare in maniera indolore file XML e a comunicare in maniera trasparente con un DB server come MySQL. La struttura dei dati è abbastanza semplice. Sono state implementate solamente due tabelle, una per la persistenza dei progetti e una per le task.

CREATE TABLE `projects` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `note` text NOT NULL,
  `startdate` date NOT NULL,
  PRIMARY KEY  (`id`)
)

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL auto_increment,
  `project_id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `note` text NOT NULL,
  `user` varchar(255) NOT NULL,
  `priority` tinyint(4) NOT NULL,
  `startdate` date NOT NULL,
  `enddate` date default NULL,
  PRIMARY KEY  (`id`)
)

.htaccess e funzionalità di dispatcher di index.php

La parte server utilizza il mod_rewrite di Apache per generare url user-friendly e per utilizzare il file index.php come dispatcher di qualsiasi azione. Questo è possibile tramite alcune direttiva inserite nel file .htaccess salvato nella root del server.

RewriteEngine On
RewriteBase /server
RewriteRule ^([a-zA-Z_]+)$ index.php?action=$1

Con queste regole, qualsiasi url che contenga lettere (maiuscole e non) e il carattere di underscore (_) viene ridirezionato come index.php che avrà a disposizione, nella variabile globale $_GET['action'] l'url appena invocato. In questo modo è possibile centralizzare le operazioni comuni a tutte le azioni in un unico file (come per esempio la gestione degli errori, la connessione con il database e la modifica dell'header HTTP).

<?php

/* configurazione DB */
define("DB_HOST","****");
define("DB_USER","****");
define("DB_PASS","****");
define("DB_NAME","****");
/* fine configurazione DB */

$db = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_select_db(DB_NAME, $db);

$actions = array();
$actionsDir = "./actions";
$dir = opendir("./actions");
while(false !== ($file = readdir($dir))) if(is_file($actionsDir."/".$file)) $actions[] = substr($file,0,strpos($file,"."));

header("Content-type: text/xml");
echo "<"."?xml version="1.0" encoding="utf-8"?>";

if(isset($_GET['action'])) {
    if(in_array($_GET['action'], $actions)) include($actionsDir."/".$_GET['action'].".php");
    else $error = "Action ".$_GET['action']." does not exists";    
} else $error = "No action defined";

if(isset($error)) {
    echo "<error>$error!</error>";
}

mysql_close($db);

>

Dopo aver definito le costanti di connessione con il database e aperto la connessione con il database, viene modificato il content-type della pagina tramite la funzione header() e viene inserito nella response il doctype XML. Successivamente vengono eseguiti controlli sul parametro $_GET['action'] (ricordo che QUALSIASI url viene accettato e che quindi è necessario controllare che l'azione esista) e nel caso sia corretto viene incluso il file che implementa specificatamente quell'azione. In caso di errori (che possono essere generati anche all'interno dell'azione corrispondente) viene stampato il tag <error>. Finalmente viene poi chiusa la connessione con il database.

In questo modo, qualsiasi file che implementa un'azione ha già a disposizione una connessione con il database e un gestore di errori. Sarà necessario solamente controllare eventuali parametri, eseguire query sul DB e stampare l'XML corrispondente alla richiesta.

Alcune azioni

Verranno analizzate solo 3 azioni delle 9 disponibili, tralasciando quelle meno importanti o meno difficili da comprendere.

Azione 1 - Elenco dei progetti

<?php
$query = "SELECT * FROM projects ORDER BY startdate";
$result = mysql_query($query, $db) or die(mysql_error());
$projects = array();
while($row = mysql_fetch_assoc($result)) $projects[] = $row;
?>

<projects>
	<?php foreach($projects as $p) : ?>
	<project>
		<?php foreach($p as $key => $value) : ?>
			<?php if(!is_null($value)) : ?>
				<<?php echo $key; ?>><?php echo utf8_encode($value); ?></<?php echo $key; ?>>
			<?php endif; ?>
		<?php endforeach; ?>
	</project>
	<?php endforeach; ?>
</projects>

La prima azione analizzata è quella più semplice: da un result set ottenuto da una query su database viene generato un XML partendo dalle colonne del database (grazie al foreach $p as $key => $value). Da notare l'utilizzo dell'encoder utf8 in quanto i dati su DB sono salvati con una collation latin mentre il formato dell'XML è appunto l'UTF-8.

Azione 2 - Dettaglio di un progetto

<?php
if(!isset($_POST['id'])) {
	$error = "No project defined";
} else {
	$query = "SELECT * FROM projects WHERE id = '".mysql_real_escape_string($_POST['id'])."'";
	$result = mysql_query($query) or die(mysql_error());
	$count = mysql_num_rows($result);
	if($count == 0) $error = "No project with id ".$_POST['id'];
	elseif($count > 1) $error = "More than one project with id ".$_POST['id'];
	else {
		$project = mysql_fetch_assoc($result);
		$query = "SELECT * FROM tasks WHERE project_id = '".$project['id']."' ORDER BY enddate ASC, priority DESC, startdate ASC";
		$result = mysql_query($query) or die(mysql_error());
		$tasks = array();
		while($row = mysql_fetch_assoc($result)) $tasks[] = $row;
	}
}
?>

<?php if(!isset($error)) : ?>
	<project>
		<?php foreach($project as $key => $value) : ?>
			<?php if(!is_null($value)) : ?>
				<<?php echo $key; ?>><?php echo utf8_encode($value); ?></<?php echo $key; ?>>
			<?php endif; ?>
		<?php endforeach; ?>
		<?php foreach($tasks as $t) : ?>
			<task>
				<?php foreach($t as $key => $value) : ?>
					<?php if(!is_null($value)) : ?>
						<<?php echo $key; ?>><?php echo utf8_encode($value); ?></<?php echo $key; ?>>
					<?php endif; ?>
				<?php endforeach; ?>
			</task>
		<?php endforeach; ?>	
	</project>
<?php endif; ?>

Rispetto all'azione precedente, quest'ultima presenta una minima validazione server-side sul parametro $_POST['id'] ricevuto dal client. Innanzitutto lo script si assicura che quest'ultimo esiste e in caso positivo verifica che sia effettivamente l'id di un progetto salvato su DB. Se tutto procede senza intoppi viene generato l'XML contenente, oltre al dettaglio del progetto, anche l'elenco dei task ad esso associati.
In questo caso prima di generare l'XML viene effettuato un controllo sulla variabile $error, controllo non necessario nell'azione 1 in quanto non ci sarebbero potuti essere errori specifici dell'azione ma solo errori generali (che però sarebbero stati gestiti dal dispatcher). Nell'azione corrente invece eventuali errori specifici si potrebbero verificare.

Azione 3 - Creazione di un task

<?php
if(!isset($_POST['name']) || strlen(trim($_POST['name'])) == 0 ) {
	$error = "No name defined";	
} elseif(!isset($_POST['note']) || strlen(trim($_POST['note'])) == 0 ) {
	$error = "No note defined";	
} elseif(!isset($_POST['user']) || strlen(trim($_POST['user'])) == 0 ) {
	$error = "No user defined";	
} elseif(!isset($_POST['priority']) || strlen(trim($_POST['priority'])) == 0 ) {
	$error = "No priority defined";			
} elseif(!isset($_POST['project_id']) || strlen(trim($_POST['project_id'])) == 0 ) {
	$error = "No project_id defined";			
} elseif($_POST['priority'] != 1 && $_POST['priority'] != 0) {
	$error = "Wrong priority code";
} else {
	$query = "SELECT * FROM projects WHERE id = '".mysql_real_escape_string($_POST['project_id'])."'";
	$result = mysql_query($query) or die(mysql_error());
	$count = mysql_num_rows($result);
	if($count == 0) $error = "No project with id ".$_POST['id'];
	elseif($count > 1) $error = "More than project task with id ".$_POST['id'];
	else {
		$query = "INSERT INTO tasks(project_id, name, note, user, priority, startdate) VALUES('".
				 $_POST['project_id']."','". 	
				 mysql_real_escape_string(htmlentities($_POST['name']))."','".
				 mysql_real_escape_string(htmlentities($_POST['note']))."','".
				 mysql_real_escape_string(htmlentities($_POST['user']))."','".
				 $_POST['priority']."',NOW())";
		mysql_query($query)  or die(mysql_error());		
		$last_id = mysql_insert_id();		  
		$query = "SELECT * FROM tasks WHERE id = '".$last_id."'";
		$result = mysql_query($query) or die(mysql_error());
		$task = mysql_fetch_assoc($result);
	}
}
?>

<?php if(!isset($error)) : ?>
	<task>
		<?php foreach($task as $key => $value) : ?>
			<?php if(!is_null($value)) : ?>
				<<?php echo $key; ?>><?php echo utf8_encode($value); ?></<?php echo $key; ?>>
			<?php endif; ?>
		<?php endforeach; ?>	
	</task>
<?php endif; ?>

L'azione presenta rispetto alla precedente un più complesso processo di validazione sui diversi parametri necessari all'esecuzione dell'azione. Da notare l'utilizzo delle funzioni mysql_real_escape_string() e htmlentities() per armonizzare il contenuto delle stringhe con il salvataggio su database (aggiunta di eventuali slash e conversione di caratteri HTML).

Conclusioni

La tecnica di dispatcher utilizzata dalla componente server-side è un aspetto molto interessante e avrebbe meritato sicuramente più attenzione di quella fornita in questo articolo che pone il focus sulla componente client-side.

Conclusioni generali

Come per tutte le applicazioni sviluppate con scopi didattici, l'attenzione non viene posta su alcuni aspetti come l'aspetto grafico o la ricchezza di interazioni con l'utente, ma sull'aspetto formativo e sulla pulizia e chiarezza del codice.

Si sono affrontate diverse tecniche di sviluppo, dall'utilizzo di un framework completo come JQuery (argomento portante dell'articolo), al design di un pattern di sviluppo client-side, all'utilizzo di un'architettura basata su dispatcher per quanto concerne l'aspetto server-side. Giustamente i fari sono stati puntati verso ciò che doveva essere il tema dell'articolo, quindi l'integrazione di una applicazione con un framework applicativo, ma spero di aver suscitato nei lettori l'interesse anche verso altre tematiche e verso dei metodi di sviluppo a prima vista più complessi e forse "scomodi" ma che garantiscono stabilità, sicurezza e manutenibilità anche in applicazioni medio-grandi.

Ricordo che per discutere su tutti gli aspetti affrontati nel tutorial è stato aperto un thread sul forum di HTML.it: per discutere, proporre idee e chiedere maggiori spiegazioni in maniera condivisa. Per una dimostrazione dell'applicazione si può fare riferimento a questa pagina, mentre da qui è possibile scaricare i sorgenti. Il prossimo articolo dedicato a JQuery riguarderà una delle sue caratteristiche fondamentali non ancora affrontate: l'estendibilità tramite plugin!
Alla prossima!

Ti consigliamo anche