Для многих остается трудной задачей сделать правильную организацию учета данных в админчасти или просто красивый вывод, решил сделать для примера и наглядности на JQuery и Dojo Toolkit. Хочу заметить, что мне сильно импонирует dojo, потому попытаюсь как можно детальней все описывать, объяснять и провести аналогию в логике.
Это первая часть поста, с примером на jQuery, вторая часть на Dojo Toolkit.
Скачать рабочий пример можно тут
Функционал одинаковый для двух способов – добавление, редактирование, удаление и приятный внешний вид.
Для примера возьмем учет пользователей.
Структура и данные таблицы
CREATE TABLE users (
id_user int(11) NOT NULL auto_increment,
surname tinytext,
patronymic tinytext,
`name` tinytext,
phone varchar(12) default NULL,
email tinytext,
url tinytext,
`status` enum('active','passive','lock','gold') default 'active',
`created` timestamp default current_timestamp,
PRIMARY KEY (id_user)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO users VALUES (1,'Иванов','Валерьевич','Александр','58-98-78','ivanov@email.ru',NULL,'active',now());
INSERT INTO users VALUES (2,'Лосев','Иванович','Сергей','9057777777','losev@email.ru',NULL,'passive',now());
INSERT INTO users VALUES (3,'Симдянов','Вячеславович','Игорь','9056666100','simdyanov@site.ru','http://www.site.ru/','active',now());
INSERT INTO users VALUES (4,'Кузнецов','Валерьевич','Максим',NULL,'kuznetsov@site.ru','http://www.site.ru','active',now());
INSERT INTO users VALUES (5,'Нехорошев','Юрьевич','Анатолий',NULL,NULL,NULL,'lock',now());
INSERT INTO users VALUES (6,'Корнеев','Александрович','Александр','89-78-36','korneev@domen.ru',NULL,'gold',now());
Набросаем класс упрощающий выборку данных:
/php/database.php
class db{
private $_host = 'localhost';
private $_login = 'root';
private $_password = 'root';
private $_database = 'test';
// в конструкторе коннектимся к БД
public function __construct(){
mysql_connect($this->_host, $this->_login, $this->_password);
mysql_select_db($this->_database);
}
public function __destruct(){
mysql_close();
}
public function sql_query($query){
return mysql_query($query);
}
public function sql_fetch_object($query){
$result = $this->sql_query($query);
$rows = array();
while ($row = mysql_fetch_object($result)){
$rows[] = $row;
}
return $rows;
}
public function sql_fetch_array($query){
$result = $this->sql_query($query);
$rows = array();
while ($row = mysql_fetch_assoc($result)){
$rows[] = $row;
}
return $rows;
}
// вставляем не все данные, так как некторые
// могут быть пропущенны при валидации
public function sql_insert_data($data){
foreach ($data as $k=>$v){
$strFields .= '`' . $k . '`,';
$strValues .= "'$v',";
}
$strFields = substr($strFields,0,-1);
$strValues = substr($strValues,0,-1);
$this->sql_query("insert into users ($strFields) values ($strValues)");
return mysql_insert_id();
}
public function sql_delete_by_id($ids){
foreach ($ids as $id) {
$str .= intval($id) . ',';
}
$str = substr($str,0,-1);
$this->sql_query('delete from users where id_user in (' . $str . ')');
}
public function sql_save_by_id($data, $id){
$query = "
update users set
`surname` = '{$data['surname']}',
`patronymic`= '{$data['patronymic']}',
`name` = '{$data['name']}',
`phone` = '{$data['phone']}',
`email` = '{$data['email']}',
`url` = '{$data['url']}',
`status` = '{$data['status']}',
`created` = '{$data['created']}'
where id_user = $id
";
echo $query;
$this->sql_query($query);
}
}
Небольшой шаблонизатор, который будет заменять данные по соответствующим тегам
/php/template.php
class template {
public function parseByData($data, $template){
$str = $template;
foreach ($data as $key=>$val){
$str = preg_replace('/{'.$key.'}/', $val, $str);
}
return $str;
}
public function parceByTag($var, $tag, $template){
return preg_replace("/\{$tag\}/", $var, $template);
}
}
Сделаем шаблоны, которые послужат базой для примера. Разобьем всю страницу на шапку(header), часть шапки где будут подключаться скрипты(headerIncludes), шапка грида(grid_header), его нижняя часть(grid_footer), и шаблон с строкой(grid_body).
Подключение скриптов будет выглядеть следующим образом
// общие стили <link type="text/css" rel="stylesheet" href="/css/index.css" /> // стили пользовательского интерфейса jQuery UI <link type="text/css" href="/resources/smoothness/jquery-ui-1.7.1.custom.css" rel="Stylesheet" /> <script type="text/javascript" src="/lib/jquery/jquery-1.3.2.min.js"></script> <script type="text/javascript" src="/lib/jquery-ui/jquery-ui-1.7.1.custom.min.js"></script> // скрипт с базовым кодом <script type="text/javascript" src="/lib/init.jquery.js"></script>
Ряд таблицы, id строки совпадает с id записи в БД, а поля таблицы с классом div’a. Таким образом, всегда можно найти нужные нам данные.
<tr class="row_{id_user}" id="{id_user}" >
<td><div class="id_user">{id_user}</div></td>
<td><div class="surname">{surname}</div></td>
<td><div class="patronymic">{patronymic}</div></td>
<td><div class="name">{name}</div></td>
<td><div class="phone">{phone}</div></td>
<td><div class="email">{email}</div></td>
<td><div class="url">{url}</div></td>
<td><div class="status">{status}</div></td>
<td><div class="created">{created}</div></td>
<td><img class="edit" id="{id_user}" src="/img/edit.png" /></td>
</tr>
Шапка таблицы:
<thead> <tr> <th class="id"><div>id</div></th> <th class="sname"><div>Фамилия</div></th> <th class="pname"><div>Отчество</div></th> <th class="fname"><div>Имя</div></th> <th class="phone"><div>Телефон</div></th> <th class="email"><div>E-mail</div></th> <th class="url"><div>URL</div></th> <th class="status"><div>Статус</div></th> <th class="created"><div>Создан</div></th> <th class="action"></th> </tr> </thead> <caption><div class="caption">Данные пользователей</div><div id="indicator"></div></caption>
Нижняя часть грида:
<tfoot> <tr> <td colspan="10"> <img src="/img/remove.png" class="delete" /> <img src="/img/add.png" class="add" /> </td> </tr> </tfoot>
Индексный файл который будет все это компановать и выводить:
/index.php
require 'php/database.php';
require 'php/template.php';
$db = new db();
$db->sql_query('set names utf8');
$template = new template();
$data = $db->sql_fetch_array('select * from users');
$header = file_get_contents('templates/parts/header.html');
$headerIncludes = file_get_contents('templates/parts/headerIncludes.html');
$footer = file_get_contents('templates/parts/footer.html');
$grid_header = file_get_contents('templates/parts/grid_header.html');
$grid_body = file_get_contents('templates/parts/grid_body.html');
$grid_footer = file_get_contents('templates/parts/grid_footer.html');
$body = file_get_contents('templates/index.html');
foreach($data as $k=>$v){
$htmlGrid .= $template->parseByData($v,$grid_body);
}
$content = $htmlGrid;
$html = $template->parceByTag($header,'header',$body);
$html = $template->parceByTag($grid_header,'grid_header',$html);
$html = $template->parceByTag($grid_footer,'grid_footer',$html);
$html = $template->parceByTag($headerIncludes,'headerIncludes',$html);
$html = $template->parceByTag($footer,'footer',$html);
$html = $template->parceByTag($content,'grid_data',$html);
header('Content-type: text/html; charset=utf-8');
echo $html;
А такеже скрипты для CRUD операций:
Добавления /insert.php
require 'php/database.php';
require 'php/template.php';
$db = new db();
$db->sql_query('set names utf8');
$template = new template();
$data['surname'] = htmlspecialchars($_POST['surname']);
$data['patronymic'] = htmlspecialchars($_POST['patronymic']);
$data['name'] = htmlspecialchars($_POST['name']);
$data['phone'] = htmlspecialchars($_POST['phone']);
$data['email'] = htmlspecialchars($_POST['email']);
$data['url'] = htmlspecialchars($_POST['url']);
$data['status'] = htmlspecialchars($_POST['status']);
$data['created'] = htmlspecialchars($_POST['created']);
$isDate = preg_match('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/',$data['created']);
$lastId = ($isDate == 1) ? $db->sql_insert_data($data) : 'not valid';
if ($lastId == 'not valid'){
echo $lastId;
die();
}
$grid_body = file_get_contents('templates/parts/grid_body.html');
$data['id_user'] = $lastId;
$html = $template->parseByData($data,$grid_body);
header('Content-type: text/html; charset=utf-8');
echo $html;
Редактирование /save.php
require 'php/database.php';
require 'php/template.php';
$db = new db();
$db->sql_query('set names utf8');
$id = intval($_POST['id_user']);
$data['surname'] = htmlspecialchars($_POST['surname']);
$data['patronymic'] = htmlspecialchars($_POST['patronymic']);
$data['name'] = htmlspecialchars($_POST['name']);
$data['phone'] = htmlspecialchars($_POST['phone']);
$data['email'] = htmlspecialchars($_POST['email']);
$data['url'] = htmlspecialchars($_POST['url']);
$data['status'] = htmlspecialchars($_POST['status']);
$data['created'] = htmlspecialchars($_POST['created']);
$isId = ($id != 0) ? $id : false;
$isDate = preg_match('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/',$data['created']);
if ($isId && $isDate == 1) {
$db->sql_save_by_id($data, $id);
}
echo 'ok';
Удаление /delete.php
require 'php/database.php';
$db = new db();
$db->sql_query('set names utf8');
$db->sql_delete_by_id($_GET['ids']);
echo 'ok';
Теперь, когда есть реализованный бекенд функционал, сначала реализуем интерфейс на jQuery, поместим весь функционал в один файл, назовем его init.jquery.js
Не думаю, что этот тот случай, когда стоит мудрить, сделаем все линейно и наглядно.
// выполнение любого функционала jQuery начинаем с ready
$(document).ready(function () {
// текущий id редактируемой записи
var editebleElementId;
// массив выделенных строк
var selectedRows = [];
// функция которая будет разкрашивать через строчку наш грид
// в конце скрипта будет ее вызов
var evenGrid = function(){
$('.grid tr:even').addClass('even');
};
// будем подсвечивать строку таблицы, на которую наведен курсор
// выносим эту и предыдущую функции в отдельные, так как еще ни раз их вызовем
var hoverGrid = function(){
$('.grid tbody tr').hover(
function(){
$(this).addClass('hover');
},
function(){
$(this).removeClass('hover');
}
);
};
// делаем запоминание выделенной строки, что б обойтись без чекбоксов
// привязываем события с помощью live, это новая возможность jQuery 1.3
// которая позволяет следить за всеми новодобавленными или измененными
// элементами, которые подходят по требованию.
// Если б мы использовали .click - событие не реагировало на
// новае элементы.
$('.grid tbody tr').live('click',
function(){
// получаем id строки по которой клацнули
var id = parseInt(this.id);
// смотрим есть ли такой элемент в массиве
var index = selectedRows.indexOf(id);
if (index == -1) {
// если нет - добавляем
selectedRows.push(id);
} else {
// если есть удаляем
selectedRows.splice(index, 1);
}
// добавляем или убираем класс выделения
$(this).toggleClass('clicked');
}
);
// для удобства
var reinitGrid = function(){
evenGrid();
hoverGrid();
};
// будем сообщать для интерактивности, что происходит
$("#indicator").ajaxStart(function(){
$(this).html('В процессе').show();
$('#loader').clone().attr('style','').appendTo(this);
});
$("#indicator").ajaxSuccess(function(evt, request, settings){
$(this).html("Выполнено");
});
$("#indicator").ajaxError(function(event, request, settings){
$(this).html("Возникла ошибка при выполнении");
});
// функция редактирования
$('.grid .edit').live('click',
function(){
// запоминаем что мы меняем
editebleElementId = this.id;
// собственно, сам диалог
$("#dialog").dialog({
height: 385,
width: 400,
modal: true,
resizable: false,
buttons: {
/* кнопки диалога */
"Сохранить": function(){
/* объект который будет хранить измененные данные */
var formData = {};
/* применяем к каждому текстовому полю функцию которая ... */
$("#dialogForm input").map(function(){
/* .. собирает хеш ( название элемента => его значение)
что то подобное асоциативному массиву php
напомню, что в javascript массивы не ассоциативные */
formData[$(this).attr('name')] = $(this).val();
});
// передаем собранные данные
$.post(
// скрипту который все сохранит
"save.php",
// сами данные
formData,
// а так же, определим функцию которая сработает в случае
// успешного запроса.
// Напомню, что надо обязательно (что б не было проблем)
// указать, какого типа данные ждать от сервера
// в нашем случае это простой текст
function(data){
// стандартный метод фреймворка, который перебирает
// элементы массива или объекта
jQuery.each(formData, function(i, val) {
// в нашем случае заполняем каждый див ячейки, по очереди
// измененными данными формы
$('.row_' + editebleElementId + ' td div.' + i).html(val);
});
},
"text"
);
// и удаляем диалог, в конце объясню зачем
$(this).dialog("destroy");
},
"Отменить": function(){
$(this).dialog("destroy");
}
},
// при инициализации диалога
open: function(event, ui) {
// перебираем каждый див ячейки строки
$('.row_' + editebleElementId + ' td div').slice(0,9).map(function(){
// и заполняем форму диалога данными по соответствующим inupt'ам по его имени
valueName = $(this).attr('class');
$('#dialogForm #'+valueName).val($(this).html());
});
},
close: function(event, ui) {
$(this).dialog("destroy");
}
});
}
);
// функция добавление, я уже не буду так расписывать, все по аналогии
$('.grid .add').live('click',
function(){
// окрываем
$("#dialog").dialog({
height: 385,
width: 400,
modal: true,
resizable: false,
buttons: {
"Сохранить": function(){
var formData = {};
// собираем заполненные данные
$("#dialogForm input").map(function(){
formData[$(this).attr('name')] = $(this).val();
});
// отсылаем
$.post(
"insert.php",
formData,
function(data){
if (data == 'not valid'){
// если вдруг что то не так заполнено
// в моем примере, мы строго не следили за валидностью
// вводимых данных, но все же
$("#indicator").html('Не верно заполненные данные');
} else {
// если все ок - находим последнюю строку грида
// и вставляем после него новую строку, принятую с сервера
$('.grid tbody tr:last').after(data);
// заново окрашиваем грид через строчку
reinitGrid();
}
},
// не забываем указать, что ждем от сервера
"text"
);
// удаляем диалог
$(this).dialog("destroy");
},
"Отменить": function(){
$(this).dialog("destroy");
}
},
open: function(event, ui){
// если мы сначала, что то редактировали, то данные
// так и остаются в форме, потому очищаем ее
$('#dialogForm input').val('');
},
close: function(event, ui) {
$(this).dialog("destroy");
}
});
}
);
// удаление
$('.grid .delete').live('click',
function(){
$("#confirm").dialog({
height: 200,
width: 400,
modal: true,
resizable: false,
buttons: {
"Подтвердить": function(){
// делаем запрос get протоколом
$.get(
"delete.php",
{'ids[]':selectedRows},
function(data){
// при успешном ответе ищем все строки с собранными
// id строк
selectedRows.forEach(function(key,val){
// и удаляем
$('#' + key).remove();
// не забываем удалить с массива выделенных элементов
// ищем место где он находится
var index = selectedRows.indexOf(key);
// и удаляем
selectedRows.splice(index, 1);
});
// заново разрисовуем таблицу
evenGrid();
}
),'text';
$(this).dialog("destroy");
},
"Отменить": function(){
$(this).dialog("destroy");
}
},
open: function(event, ui){
// при открытии окна показываем сколько рядов выделено
$('#confirm .dialogBody').html('Выделено: ' + selectedRows.length);
},
close: function(event, ui) {
$(this).dialog("destroy");
}
});
}
);
// как и говори в начале разрисовуем
// и соеденяем события наведения мыши к таблице
reinitGrid();
});
Пробуйте на работоспособность
Скачать рабочий пример можно тут
Выглядеть должно вот так:
Хочу еще раз напомнить, что это только первая часть.


