Ajaxowy system edycji danych

W artykule przedstawię jak stworzyć ajaxowy system edycji danych zawartych w tabeli. Będzie on wykonany w php, mysql i jQuery. Taki system można wykorzystać np. w panelu administracyjnym strony. Można pobrać cały kod ajaxowego systemu edycji. Załóżmy, że zaprojektujemy system newsów. Najpierw tworzymy bazę danych (np. poniższym poleceniem SQL).

CREATE DATABASE system_newsow;
Następnie tworzymy tabelę newsy. Jej struktura przedstawia się następująco.
CREATE TABLE newsy (
id INT PRIMARY KEY AUTO_INCREMENT,
tytul VARCHAR(255) NOT NULL,
data INT NOT NULL,
autor VARCHAR(30) NOT NULL,
tresc TEXT NOT NULL
);
Wypełnijmy tabelę jakimiś newsami. Polecenie SQL wstawiające wiersz do naszej tabeli jest takie jak poniżej. Wstawmy w ten sposób kilka wierszy, aby mieć dane z którymi będziemy pracować.
INSERT INTO `newsy` (`tytul`, `data`, `autor`, `tresc`) VALUES
('news1', 1220729416, 'gosia', 'treść newsa');
Gdy mamy już utworzoną bazę danych stwórzmy plik index.php. Będzie to główna strona naszego panelu. Jej kod przedstawia się następująco.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery Ajax Panel</title>

<link rel="stylesheet" type="text/css" media="screen" href="styl.css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"></script>
<script src="custom.js" type="text/javascript"></script>

</head>
<body>
<?php
define('DB_HOST','localhost');
define('DB_USER','user');
define('DB_PASS','haslo');
define('DB_NAME','system_newsow');
$con=mysql_connect(DB_HOST,DB_USER,DB_PASS,true);
$select_db = mysql_select_db(DB_NAME,$con);
if($select_db)
{
    mysql_query("SET NAMES utf8",$con);
}
else
{
    die('Nie mozna sie polaczyc z baza danych');
}
$query = "SELECT * FROM newsy";
$result = mysql_query($query,$con);
if($result===false && trim(mysql_error($con)))
{
    echo("Błędne zapytanie: \n");
}
while($row = mysql_fetch_assoc($result))
{
    $out[]=$row;
}
echo '<table id="tab">
<tr><th>Id</th><th>Tytuł</th><th>Treść</th><th>Akcje</th></tr>';
foreach($out as $art)
{
     echo '<tr>';
     echo '<td>'.$art['id'].'</td>';
     echo '<td>'.htmlspecialchars($art['tytul'],ENT_QUOTES).'</td>';
     echo '<td>'.htmlspecialchars($art['tresc'],ENT_QUOTES).'</td>';
echo '<td>
<form method="post"><input type="hidden" name="id" value="'.$art['id'].'" />
<input class="sub" type="submit" value="Edytuj" />
</form>
     <form method="post" action="">
<input type="hidden" name="id" value="'.$art['id'].'" />
<input class="del" type="submit" value="" title="Usuń" />
</form>
</td>';
     echo '</tr>';
}
echo '</table>
<form action="" method="post" id="add_form">
<label>Tytuł:</label><input type="text" name="tytul" value="" /><br />
<label>Treść:</label><input type="text" name="tresc" value="" /><br />
<input class="sub2" type="submit" value="Dodaj" />
</form>';
?>
</body>
</html>
Pokrótce go omówię. Na początku sekcja head strony, w której załączamy odnośnik do zewnętrznego pliku styl.css oraz do biblioteki jQuery i pliku custom.js (to w nim będzie kod Javascript dla naszej strony). Następnie wewnątrz znaczników php łączymy się z bazą danych, wykonujemy zapytanie pobrania wszystkich newsów i wiersz po wierszu zapisujemy je w tablicy $out. Za pomocą pętli foreach wyświetlamy tabelę złożoną z wcześniej pobranych danych. W ostatniej komórce każdego z wierszy tabeli znajdują się dwa formularze, jeden do edycji, a drugi do usuwania newsa. Za tabelą umieszczamy jeszcze formularz służący do dodawania newsów. Dla poprawienia wyglądu tabeli i formularzy dodajmy następujące style.
body
{
	font: 12px Arial, Helvetica, sans-serif;
}
table
{
	border-collapse:collapse;
}
table td
{
	border: 1px solid #4192AF;
	border-bottom-color: #6FC2DF;
	border-left-color: #6FC2DF;
	background: #DFF7FF;
}
table th
{
	border: 1px solid #4192AF;
	background: #1D7D9F;
	color:white;
}
form
{
	float:left;
}
input.sub, input.sub2
{
	background: url("button1.png") no-repeat;
	border: none;
	color: white;
	font-size: 10px;
	cursor:pointer;
	padding: 1px 5px 5px 2px;
	font-weight:bold;
	margin: 3px;
}
input.del
{
	background: url("delete.png") no-repeat;
	border: none;
	margin-top: 5px;
	cursor:pointer;
}
input
{
	font-size: 12px;
	border:1px solid #1D7D9F;
}
label
{
	width: 40px;
	float:left;
}
#add_form
{
	margin-top:10px;
}
Pliki button1.png i delete.png są zawarte w paczce wraz z kodem źródłowym. Zaczniemy od edycji danych. Plik php zajmujący się tym zadaniem (edit.php) jest następujący.
<?php
if(isset($_POST['tytul']) && isset($_POST['tresc']) && isset($_POST['id']))
{
  $tytul = mysql_real_escape_string($_POST['tytul']);
  $tresc = mysql_real_escape_string($_POST['tresc']);
  $id = intval($_POST['id']);
  $query = "UPDATE newsy SET tytul = '$tytul', tresc = '$tresc' WHERE id = '$id'";
  $result = mysql_query($query,$con);
  echo json_encode(array('tytul'=>$_POST['tytul'],'tresc'=>$_POST['tresc']));
}
?>
Kod odpowiedzialny za połączenie z bazą danych został na powyższym listingu pominięty. Działanie tego kodu jest bardzo proste. Polega na złapaniu odpowiednich danych znajdujących się w tablicy $_POST i wstawieniu ich do zapytania aktualizacji wiersza (update). Zanim dane zostaną wstawione do zapytania są jeszcze zabezpieczone za pomocą funkcji mysql_real_escape_string(), a id przez intval(). Na koniec wyświetlamy reprezentację JSON tablicy zawierającej przesłane dane za pomocą funkcji json_encode(). Te dane w formacie JSON odbierzemy później w Javascript. Stwórzmy teraz plik custom.js. W nim będziemy pisać skrypty jQuery. Na początku dodamy następujący kod. Po załadowaniu drzewa dokumentu wykonaj zdarzenie click na przycisku o klasie sub. Zwracamy false, aby formularz nie został wysłany. Czemu przypisujemy zdarzenie za pomocą funkcji live? Dlatego, że w przyszłości (czyli już za chwilę) będziemy usuwać ten przycisk i wstawiać zamiast niego formularz do właściwej edycji, następnie po zakończonej edycji wstawimy przycisk z powrotem. Chcemy aby ponownie wstawiony przycisk miał już przypisane zdarzenie click.
$(document).ready(function() {
	$('input.sub').live('click',function(){
		return false;
	});
});
Następnie zapisujemy pod zmiennymi identyfikator, tytuł i treść artykułu, który aktualnie chcemy edytować.
var id_art = $(this).parent().find('input[type=hidden]').val();
var title = $(this).parent().parent().parent().find('td:eq(1)').text();
var content = $(this).parent().parent().parent().find('td:eq(2)').text();
Następnie utworzymy element div o klasie box, zawierający formularz do edycji danych. Element ten zapisujemy pod zmienną a. Patrząc na poniższy listing widzimy do czego potrzebne są zmienne id_art, title i content.
var a = $('<div/>', {
class: 'box',
html: '<form action="" method="post"><input type="hidden" name="id" value="'+id_art+'" /><label>Tytuł:</label><input type="text" name="tytul" value="'+title+'" /><br /><label>Treść:</label><input type="text" name="tresc" value="'+content+'" /><br /><input class="sub2" type="submit" value="Edytuj" /></form>'
});
Tworzymy teraz dwie zmienne pomocnicze. Pierwsza zawiera rodzica inputa o klasie sub czyli formularz. Druga następny element względem formularza czyli formularz usuwania newsa.
var thi = $(this).parent();
var next_thi = thi.next();
Poniższy fragment wstawia do komórki tabeli zmienną a, czyli div zawierający formularz właściwej edycji. Następnie usuwamy formularz (rodzica klikniętego inputa).
$(this).parent().parent().append(a);
$(this).parent().remove();
Poniższy najbardziej rozbudowany listing zawiera zasadniczą część kodu. Najpierw dla formularza znajdującego się wewnątrz diva o klasie box (zmienna a) wykonujemy metodę submit. Blokujemy wysłanie formularza zwracając false i wysyłamy ajaxowe żądanie post do pliku edit.php. Kod $(this).serialize() oznacza, że wraz z żądaniem wysyłane są wszystkie dane z formularza. Trzecim parametrem funkcji post jest funkcja odbierająca dane. Dane są w formacie JSON (tekst). W celu przekształcenia ich w obiekt JSON wystarczy otoczyć ciąg okrągłymi nawiasami, a następnie przekazać go do funkcji eval. Następnie pod zmienną tr zapisujemy wiersz tabeli aktualnie edytowanego newsa.
a.find('form').submit(function(){
	$.post('edit.php', $(this).serialize(), function(dane) {
	  dane = eval('('+dane+')');
	  tr = a.parent().parent();
	  tr.find('td:eq(1)').text(dane['tytul']).css('background-color', 'yellow').animate({
	  opacity: 1
	  }, 3000, function() {
	  $(this).css({'background-color' : '#DFF7FF', 'opacity' : '1'});
	  });
	  tr.find('td:eq(2)').text(dane['tresc']).css('background-color', 'yellow').animate({
	  opacity: 1
	  }, 3000, function() {
	  $(this).css({'background-color' : '#DFF7FF', 'opacity' : '1'});
	  });
	  a.remove();
	  next_thi.before(thi);
	});
      return false;
});
Poniższy fragment odpowiada za wstawienie bez przeładowania strony nowego tytułu do drugiego elementu td określonego wiersza oraz zmianę jego tła na żółte. Potem uruchamiana jest animacja, która przywraca poprzedni kolor tła po 3 sekundach.
tr.find('td:eq(1)').text(dane['tytul']).css('background-color', 'yellow').animate({
opacity: 1
}, 3000, function() {
$(this).css({'background-color' : '#DFF7FF', 'opacity' : '1'});
});
Na koniec usuwamy zmienną a i wstawiamy ponownie przycisk, który był na początku. Poniżej pełny listing.
$(document).ready(function() {
	$('input.sub').live('click',function(){
	var id_art = $(this).parent().find('input[type=hidden]').val();
	var title = $(this).parent().parent().parent().find('td:eq(1)').text();
var content = $(this).parent().parent().parent().find('td:eq(2)').text();
	var a = $('<div/>', {
			class: 'box',
		html: '<form action="" method="post"><input type="hidden" name="id" value="'+id_art+'" /><label>Tytuł:</label><input type="text" name="tytul" value="'+title+'" /><br /><label>Treść:</label><input type="text" name="tresc" value="'+content+'" /><br /><input class="sub2" type="submit" value="Edytuj" /></form>'
	});
	var thi = $(this).parent();
	var next_thi = thi.next();
	$(this).parent().parent().append(a);
	$(this).parent().remove();
	a.find('form').submit(function(){
	$.post('edit.php', $(this).serialize(), function(dane) {
		dane = eval('('+dane+')');
		tr = a.parent().parent();
tr.find('td:eq(1)').text(dane['tytul']).css('background-color', 'yellow').animate({
					opacity: 1
					}, 3000, function() {
$(this).css({'background-color' : '#DFF7FF', 'opacity' : '1'});
						});
tr.find('td:eq(2)').text(dane['tresc']).css('background-color', 'yellow').animate({
				opacity: 1
				}, 3000, function() {
$(this).css({'background-color' : '#DFF7FF', 'opacity' : '1'});
					});
				a.remove();
				next_thi.before(thi);
			 });
			return false;
		});
		return false;
	});
});
Teraz zajmiemy się usuwaniem newsów. Stwórzmy plik delete.php. Jego zawartość przedstawia poniższy listing (kod połączenia z bazą został pominięty). Wykorzystując przekazany parametr id newsa wykonujemy zapytanie do bazy usuwające newsa o tym id.
if(isset($_POST['id']))
{
  $id = intval($_POST['id']);
  $query = "DELETE FROM newsy WHERE id = '$id'";
  mysql_query($query,$con);
  echo ' ';
}
Teraz spójrzmy na kod jQuery.
$('input.del').live('click',function(){
	var form_del = $(this).parent();
	form_del.submit(function(){
		$.post('delete.php', form_del.serialize(), function(dane) {
			tr = form_del.parent().parent();
			tr.remove();
		});
		return false;
	});
});
Do inputa o klasie del przypisujemy zdarzenie click i zapisujemy pod zmienną jego rodzica czyli formularz. Potem do formularza przypisujemy zdarzenie submit. Wysyłamy żądanie do pliku delete.php, a po odebraniu odpowiedzi usuwamy wiersz tabeli (tr) zawierający usuwany news. Ostatnim elementem aplikacji będzie dodawanie newsów. Za dodawanie odpowiada formularz umieszczony za tabelką. Ma on identyfikator add_form. Stwórzmy plik add.php dodający nowy news do bazy danych.
if(isset($_POST['tytul']) && isset($_POST['tresc']))
{
  $tytul = mysql_real_escape_string($_POST['tytul']);
  $tresc = mysql_real_escape_string($_POST['tresc']);
  $query = "INSERT INTO newsy (tytul,tresc) VALUES ('$tytul','$tresc')";
  mysql_query($query,$con);
  $lid = mysql_insert_id($con);
  $query2 = "SELECT * FROM newsy WHERE id = '$lid'";
  $result = mysql_query($query2,$con);
  $row = mysql_fetch_assoc($result);
  echo json_encode(array('id'=>$row['id'],'tytul'=>$row['tytul'],'tresc'=>$row['tresc']));
}
W powyższym pliku przesłane dane wstawiamy do zapytania INSERT. Używając funkcji mysql_insert_id() sprawdzamy jaki był identyfikator ostatnio wstawionego wiersza i wykorzystujemy go potem w zapytaniu SELECT. Na koniec wyświetlamy dane (w formacie JSON) dotyczące wstawionego wiersza. Pozostało dodać jeszcze kod jQuery.
$('form#add_form').submit(function(){
	$.post('add.php', $(this).serialize(), function(dane) {
		dane = eval('('+dane+')');
		tabela = $('table#tab');						tabela.append('<tr><td>'+dane['id']+'</td><td>'+dane['tytul']+'</td><td>'+dane['tresc']+'</td><td><form method="post"><input type="hidden" name="id" value="'+dane['id']+'" /><input class="sub" type="submit" value="Edytuj" /></form><form method="post" action=""><input type="hidden" name="id" value="'+dane['id']+'" /><input class="del" type="submit" value="" title="Usuń" /></form></td></tr>');
	});
	return false;
});
W momencie wysłania formularza żądanie ajaxowe przekazujemy do pliku add.php, który dodaje newsa. Następnie odebrane dane przekształcamy na obiekt JSON i dodajemy do tabeli wiersz z przed chwilą dodanym newsem.