AJAX: Degradare gratioasa

Average: 2 (3 votes)

Una din provocarile intalnite in crearea unei aplicatii este necesitatea ca aceasta sa functioneze pentru un numar cat mai mare de utilizatori. Printre aspectele care trebuie acoperite in acest sens se numara si degradarea gratioasa a respectivei aplicatii. Pe scurt functionalitatile principale trebuie sa fie prezente indiferent de tehnologiile la care are acces utilizatorul.

Asigurarea functionarii unei aplicatii ce foloseste AJAX si in cazul in care din diferite motive Javascript nu mai functioneaza (fie datorita dezactivarii sale fie din cauza aparitiei unei erori) nu este dificila, si una din cele mai simple metode este de a proiecta initial aplicatia fara a se face uz de AJAX (dar cu planuirea atenta asa incat implementarea solutiei alternative sa se faca cat mai simplu apoi). In momentul in care totul functioneaza cum ar trebui se poate trece la schimbarea comportamentului aplicatiei pentru cazul optim.

Ca exemplu, voi prezenta o mica aplicatie pentru filtrarea unor articole prezente intr-o baza de date.

Pentru inceput, avem nevoie de structura tabelelor MySQL. Vom folosi doar doua tabele, una pentru categoriile din care vor face parte articolele, si una pentru articole:

CREATE TABLE `articole` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `id_categorie` tinyint(2) unsigned NOT NULL default '0',
  `autor` varchar(50) NOT NULL default '',
  `titlu` varchar(50) NOT NULL default '',
  `tstamp` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`id`),
  KEY `id_categorie` (`id_categorie`),
  KEY `tstamp` (`tstamp`)
)

CREATE TABLE `categorii` (
  `id` tinyint(2) unsigned NOT NULL auto_increment,
  `nume` varchar(50) NOT NULL default '',
  PRIMARY KEY  (`id`),
  KEY `nume` (`nume`)
)

Vom filtra articolele in functie de data postarii lor si categoria din care fac parte, prin urmare vom avea nevoie de o mica librarie de functii de interactiune cu MySQL. Voi numi acest fisier lib-sql.php:

function get_categorii() {
 
	$connection = sql_connect();
	$return = array();
 
	$query = mysql_query("SELECT `id`, `nume` FROM `categorii` ORDER BY `nume` ASC", $connection);
	if (mysql_num_rows($query)) {
		while ($row = mysql_fetch_assoc($query)) {
			$return[$row['id']] = $row['nume'];
			}
		}
 
	mysql_close($connection);
	return $return;
 
	}
 
function get_articole($ts_start, $ts_end, $categorii) {
 
	$connection = sql_connect();
	$return = array();
 
	$query = mysql_query("SELECT `id`, `id_categorie`, `autor`, `titlu`, `tstamp` FROM `articole` WHERE `tstamp` BETWEEN '".$ts_start."' AND '".$ts_end."' AND `id_categorie` IN ('".implode("','", $categorii)."') ORDER BY `tstamp` DESC", $connection);
	if (mysql_num_rows($query)) {			
		while ($row = mysql_fetch_assoc($query)) {
			$return[$row['id']] = $row;
			}
		}
 
	mysql_close($connection);
	return $return;
 
	}
 
function sql_connect() {	
	global $db_conf;
	$connection = mysql_connect($db_conf['host'], $db_conf['user'], $db_conf['pass']);
	mysql_select_db($db_conf['name'], $connection);
	return $connection;		
	}
 
function sql_close($connection) {	
	mysql_close($connection);	
	}

Avand aceste functii pregatite putem trece la realizarea paginii de filtrare si afisare a articolelor, index.php:

<?
	$db_conf = array();
	$db_conf['host'] = 'localhost';
	$db_conf['user'] = 'root';
	$db_conf['pass'] = 'taipan';
	$db_conf['name'] = 'test';
 
	include("lib-misc.php");
	include("lib-sql.php");
 
	$categorii_all = get_categorii();
 
	$categorii = array_keys($categorii_all);
 
	$end_zi = date("j");
	$end_luna = date("n");
	$end_an = date("Y");
 
	if (isset($_POST['action']) && ($_POST['action'] == 'filtrare')) {
 
		$categorii = array_map("intval", $_POST['categorii']);
 
		$start_zi = isset($_POST['start_zi']) ? (int)($_POST['start_zi']) : '';
		$start_luna = isset($_POST['start_luna']) ? (int)($_POST['start_luna']) : '';
		$start_an = isset($_POST['start_an']) ? (int)($_POST['start_an']) : '';
 
		$end_zi = isset($_POST['end_zi']) ? (int)($_POST['end_zi']) : $end_zi;
		$end_luna = isset($_POST['end_luna']) ? (int)($_POST['end_luna']) : $end_luna;
		$end_an = isset($_POST['end_an']) ? (int)($_POST['end_an']) : $end_an;
 
		}
 
	$ts_start = mktime(0, 0, 0, $start_luna, $start_zi, $start_an);
	$ts_end = mktime(0, 0, 0, $end_luna, $end_zi, $end_an);
 
	$articole = get_articole($ts_start, $ts_end, $categorii);
?>
<form name="filtrare" id="filtrare" action="index.php" method="post">
	<input type="hidden" name="action" value="filtrare" />
<?
	foreach ($categorii_all as $key => $item) {
?>
	<input type="checkbox" name="categorii[]" id="categorie-<?= $key; ?>" value="<?= $key; ?>"<?= in_array($key, $categorii) ? ' checked="checked"' : ''; ?> />
	<label for="categorie-<?= $key; ?>"><?= $item; ?></label>
<?
		}
?>
	<br />
	<select name="start_zi">
<?
	for ($i = 1; $i <= 31; $i++) {
?>
		<option value="<?= $i; ?>"<?= $start_zi == $i ? ' selected="selected"' : ''; ?>><?= str_pad($i, 2, "0", STR_PAD_LEFT); ?></option>
<?
		}
?>
	</select>
	<select name="start_luna">
<?
	for ($i = 1; $i <= 12; $i++) {
?>
		<option value="<?= $i; ?>"<?= $start_luna == $i ? ' selected="selected"' : ''; ?>><?= str_pad($i, 2, "0", STR_PAD_LEFT); ?></option>
<?
		}
?>
	</select>
	<select name="start_an">
<?
	for ($i = 2007; $i <= 2010; $i++) {
?>
		<option value="<?= $i; ?>"<?= $start_an == $i ? ' selected="selected"' : ''; ?>><?= $i; ?></option>
<?
		}
?>
	</select>
	-
	<select name="end_zi">
<?
	for ($i = 1; $i <= 31; $i++) {
?>
		<option value="<?= $i; ?>"<?= $end_zi == $i ? ' selected="selected"' : ''; ?>><?= str_pad($i, 2, "0", STR_PAD_LEFT); ?></option>
<?
		}
?>
	</select>
	<select name="end_luna">
<?
	for ($i = 1; $i <= 12; $i++) {
?>
		<option value="<?= $i; ?>"<?= $end_luna == $i ? ' selected="selected"' : ''; ?>><?= str_pad($i, 2, "0", STR_PAD_LEFT); ?></option>
<?
		}
?>
	</select>
	<select name="end_an">
<?
	for ($i = 2007; $i <= 2010; $i++) {
?>
		<option value="<?= $i; ?>"<?= $end_an == $i ? ' selected="selected"' : ''; ?>><?= $i; ?></option>
<?
		}
?>
	</select>
	<button type="submit">Filtreaza</button>
</form>
<hr />
<div id="articole">
<?
	if ($articole) {
		insert_html($articole, $categorii_all);
		}
?>
</div>

Observati ca pe langa libraria lib-sql.php am inclus si lib-misc.php. Aceasta contine de fapt o singura functie, cea de afisare a articolelor. Motivul pentru care folosesc o functie separata pentru acest lucru este pentru ca, dupa cum am spus, planuiesc sa folosesc AJAX, asa ca voi trimite browser-ului acelasi continut pe care il afisam momentan direct, si evit astfel cod repetitiv in fisierele mele.

<?
function insert_html($articole, $categorii_all) {
	if ($articole) {
?>
		<table cellspacing="1" cellpadding="0">
			<thead>
				<tr>
					<td>Titlu</td>
					<td>Autor</td>
					<td>Categorie</td>
					<td>Data</td>
				</tr>
			</thead>
			<tbody>			
<?
	foreach ($articole as $id => $articol) {
?>
				<tr>
					<td><?= $articol['titlu']; ?></td>
					<td><?= $articol['autor']; ?></td>
					<td><?= $categorii_all[$articol['id_categorie']]; ?></td>
					<td><?= date("d.m.Y", $articol['tstamp']); ?></td>
				</tr>
<?
		}
?>	
			</tbody>
		</table>
<?		
		} else {
?>
		<div id="status">Nu exista articole corespunzatoare filtrarii efectuate</div>
<?		
			}
	}
?>

In acest moment aplicatia functioneaza fara probleme. E timpul sa trecem la AJAX. In primul rand vom avea nevoie de Prototype.

In fisierul index.php vom introduce in "head" urmatorul cod:

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="overall.js"></script>
<script type="text/javascript">		
	onloadQueue(function() { Event.observe('filtrare', 'submit', filtrare); });
</script>

Fisierul overall.js contine codul:

function onloadQueue(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
		} else {
			window.onload = function() {
				if (oldonload) {
					oldonload();
					}
				func();
				}
			}
	}
 
function filtrare(e) {
	new Ajax.Request(
		"index.php",
		{
			method: "post",
			parameters: "ajax=da&" + $('filtrare').serialize(),
			onLoading: function() {
				$('articole').update("Se incarca ...");
				},
			onComplete: function(transport) {
				$('articole').update(transport.responseText);
				}			
		}
		);
	Event.stop(e);
	}

Este important ca Javascript-ul necesar sa interfereze cat mai putin cu restul codului, motiv pentru care am atasat un "event listener" formularului de filtrare. In acest fel evitam constructiile neelegante de genul "onsubmit". In momentul trimiterii formularului vom intercepta datele si le vom trimite prin XMLHttpRequest.

Ultima modificare pe care trebuie sa o efectuam este in fisierul index.php. Imediat dupa preluarea articolelor din baza de date vom introduce codul:

if ($_POST['ajax']) {
	insert_html($articole, $categorii_all);
	exit();
	}

O demonstratie poate fi vizualizata aici.