jQuery, MySQL и полноценное веб-приложение
Спонсор поста: Интернет магазин детских игрушек лего.
Итак, сегодня представляю второй урок по использованию jQuery на примере скрипта Todo-листа.
В первую части мы реализовали с помощью jQuery и плагина jqModal возможность добавлять / редактировать / удалять записи из списка, но при этом каждый элемент списка был всего лишь элементом странички. Читайте здесь - “jQuery + jqModal - обучение на конкретном примере“.
Чем этот пример не дотягивает до веб-приложения? Естественно тем, что наши записи нигде не сохраняются. Это не хорошо. Но мы можем решить эту проблему с помощью базы данных: пользователь добавляет / редактирует / удаляет записи ? мы отправляем запрос на сервер и совершаем это действие с записью в базе данных ? возвращаем пользователю результат (успешно выполнено или ошибка). При этом состояние списка всегда синхронизировано с состоянием записей в базе данных.
Наращивать функционал будет постепенно, в принципе его здесь можно придумать очень много.
То, что у нас в итоге получится вы можете посмотреть здесь: “jQuery, jqModal и MySQL - обучение на конкретном примере“. В исходном коде странички можно ознакомиться с JavaScript кодом и CSS-стилями.
Приготовимся к битве
Что нам нужно в первую очередь? Давайте для начала определимся со структурой данных, который мы будем хранить в базе.
Итак, создаем таблицу `todo`, содержащюю три поля:
- `id` - для хранения уникального идентификатора
- `text` - здесь будет хранится текст записи из todo-списка
- `data` - для хранения даты задания
Пока ограничимся этими данными, хотя добавить сюда можно еще очень много, например, степень выполнения задания, категория, статус важности и т.д. Но, имхо, это все не добавит в приложение ничего принципиально нового - если разберетесь с этим, до добавлять сами сможете что угодно
Теперь о пользовательском интерфейсе.
Для отображения списка мы будем использовать таблицу с тремя столбцами:
- Текст задания
- Дата
- Управляющие элементы (для вызова интерфейсов редактирования и удаления соответствующей записи)
Все остальное наполнение странички аналогично предыдущему уроку. Единственное нововведение связано с тем, что мы будем использовать ajax для общения с сервером, поскольку это занимает время - нужен элемент, который будет сообщать пользователя “Не надо нервничать, ваш запрос обрабатывается”. Таким элементом будет слой
<div class="load"><img src="load.gif" align="absmiddle"> <span></span></div>
Изначально этот слой будет скрыт. Он будет демонстрироваться только во время выполнения ajax-запросов. Изображение вращающейся стрелки, понятное дело, для визуализации. А span мы будем использовать для вывода сообщения, сопровождающего запрос и комментирующее его результат.
Теперь давайте подружим страничку с сервером. Для этого создадим файл “mysql_connect.php”. Этот файл будет принимать пост-протоколом действие, которое необходимо совершить, а также текст задания и его дату. Подробнее в комментариях к коду:
<?php function listEntry(){ /************************************************************* ** Функция получает из базы данных список записей ** функционал этой функции можно наращивать ** добавив, например, возможность фильтрованного отбора записей *************************************************************/ $sql = "SELECT * FROM `todo` WHERE 1"; if($result = mysql_query($sql)){ $list = array(); while($todo = mysql_fetch_array($result, MYSQL_ASSOC)) $list[] = $todo; return $list; } return false; } function editEntry($id, $text){ /************************************************************* ** Функция редактирования записи в базе данных ** Сейчас предназначена только для редактирования текста задания ** Можно расширить возможности, добавив возможность редактировать другие поля *************************************************************/ $sql = "UPDATE `todo` SET text='".$text."' WHERE id='".$id."'"; if($result = mysql_query($sql)){ $sql = "SELECT * FROM `todo` WHERE `id`='".$id."'"; return mysql_fetch_array(mysql_query($sql), MYSQL_ASSOC); } return false; } function delEntry($id){ /************************************************************* ** Функция удаляет запись из базы *************************************************************/ $sql = "DELETE FROM `todo` WHERE `id`='".$id."'"; return mysql_query($sql); } function addEntry($text, $data){ /************************************************************* ** Функция добавляет новую запись в базу данных *************************************************************/ $sql = "INSERT INTO `todo` VALUES ('', '".$text."', '".$data."')"; return (mysql_query($sql) ? mysql_insert_id() : false); } /* ** Параметры базы данных */ $mysql_database="database_name"; $mysql_username="database_user"; $mysql_password="database_password"; $mysql_host="localhost"; //Соединяемся с базой данных $mysql_connect = mysql_connect($mysql_host, $mysql_username, $mysql_password); //Выбираем базу данных для работы mysql_select_db($mysql_database); /* ** Определяем, какое действие запрошено пользователем ** Вызываем соответствующую функцию и print`им ответ */ switch($_POST['action']){ case 'edit': if($edit = editEntry($_POST['id'], $_POST['text'])) print "YES|".$_POST['id']."|".$_POST['text']; break; case 'delete': print (delEntry($_POST['id']) ? 'YES' : 'NO'); break; case 'add': if($result = addEntry($_POST['text'], $_POST['data'])) print $result.'|'.$_POST['text'].'|'.$_POST['data']; break; case 'list': if($list = listEntry()){ foreach($list as $key => $value){ $list[$key] = autoencode(implode('|', $value)); } print implode("\n", $list); } break; } ?>
Ну что ж, вроде все готово. Можно двигаться дальше.
Вывод списка при загрузке страницы
Первое, что нужно сделать загруженной странице - это обратиться к серверу и получить список заданий из Todo-списка. Выполняется это следующим образом:
$.post( "mysql_connect.php", "action=list", function(data){ ans = data.split(/\n/); for (var i = 0; i <= ans.length - 1; i++) { entry = ans[i].split(/\|/); $('.result table').find('tbody').append("<tr id='"+entry[0]+"'><td class='text'>"+entry[1]+"</td><td class='data'>"+entry[2]+"</td><td align='center'><img src='edit.png' class='edit'> <img src='del.png' class='erase'></td></tr>"); set_action(entry[0]); } } )
Получая ответ сервера, мы делим его на составляющие элементы и добавляем в таблицу новыми строками также, как заносили новые пункты списка в предыдущем уроке. Здесь нужно обратить внимание на несколько тонкостей:
- каждую новую строку мы помечаем id равным `id` записи
- ячейки для вывода текста и даты мы помечаем class=’text’ и class=’data’ соответственно
- функция set_action() занимается “развешением” обработчиков на различные элементы страницы. Она принимает id добавленной записи и определяет “поведение” всех новых элементов. Давайте внимательнее проанализируем код этой функции.
Обработчики событий редактирования и удаления элементов
Итак, код нашей функции (пояснения закомментированы):
function set_action(tr_id){ $('tr#'+tr_id+' img.edit').click(function() { // обработчик клика на изображении "редактировать" // находит текст текущей записи и меняет его на поле ввода // также создает новую ссылку "сохранить" tr_text = $('tr#'+tr_id).find('td.text').text(); $('tr#'+tr_id).find('td.text').html("<input type='text' value='"+tr_text+"'> <a href='#' class='save'>→</a>"); $('tr#'+tr_id+' a.save').click(function(){ // теперь к этой самой ссылке "сохранить" "приделываем" обработчик // который при клике на ссылку берет текст, введенный в поле ввода // и отправляет его серверу с запросом "редактировать" text = $(this).parents('td').find('input').attr('value'); id = $(this).parents('tr').attr('id'); // меняем запись возле анимированные стрелочки // чтобы пользователь знал, что происходит $('.load span').text('Сохранение элемента'); // собственно, сам запрос $.post( "mysql_connect.php", "action=edit&id="+id+"&text="+text, function(data){ ans = data.split(/\|/); if(ans[0]=='YES'){ $('.load span').text('Успешно сохранено'); $('tr#'+ans[1]+' td.text').text(ans[2]); } else { $('.load span').text('Не удалось сохранить элемент'); } } ); }); return false; }); $('.erase').click(function() { // обработчик удаления текущей записи // его действие с модальным окном подробно было разобрано в первой части урока tr = $(this).parents("tr"); todo = tr.find('td.text').text(); $('.load span').text('Удаление элемента'); confirm('Вы попросили удалить : '+todo+'!', function(){ $.post( "mysql_connect.php", "action=delete&id="+tr.attr('id'), function(data){ if(data=='YES'){ $('.load span').text('Успешно удалено'); // анимируем процесс визуального удаления элемента tr.css('background-color', 'red'); tr.fadeOut('slow'); } else { $('.load span').text('Не удалось удалить элемент'); } } ); return false; }); return false; }); return false; }
Эта фукнция будет вызываться всякий раз, когда на страничке появляется новая строка.
Добавление новых элементов
Для ввода нового задания предусмотрено два текстовых поля (для текста и для даты).
<p>Задание: <input type="text" id="todo"> Дата: <input type="text" id="date"> <br><a href="#" class="add">Добавить запись →</a></p>
За обработку клика на ссылку “добавить” отвечает этот обработчик:
$('.add').click(function(){ // считываем введенные данные todo = $('#todo').val(); date = $('#date').val(); // сообщаем пользователю то, что происходит $('.load span').text('Добавление нового элемента'); if(todo.length&&date.length){ $.post( "mysql_connect.php", "action=add&text="+todo+"&data="+date, function(data){ if(data!=''){ entry = data.split(/\|/); $('.result table').find('tbody').append("<tr id='"+entry[0]+"'><td class='text'>"+entry[1]+"</td><td class='data'>"+entry[2]+"</td><td align='center'><img src='edit.png' class='edit'> <img src='del.png' class='erase'></td></tr>"); set_action(entry[0]); } } ); } });
Косметические дополнения
Для удобства работы с полями ввода давайте предусмотрим их очищении при получении фокуса:
$('#todo').focus(function(){ $(this).attr('value', ''); return false; }); $('#date').focus(function(){ $(this).attr('value', ''); return false; });
Ну и напоследок, обеспечим отображение слоя с анимированным вращающейся стрелочкой и надписью о текущем действии как раз в момент отправки запроса:
$(".result").ajaxStart(function(){ $('.load').fadeIn("slow"); }); $(".result").ajaxStop(function(){ $('.load').fadeOut("slow"); });
Что дальше?
Фуух.. Много сделали. Можно передохнуть. Конечно, НЕ СДЕЛАЛИ мы больше, но все же, система получилась достаточно функциональной.
Что можно добавить:
- валидирование данных веденных пользователем
- отображение данных об ошибках в той же строке, где о выполняемом действии
- сортировка и фильтрация записей в таблице
- блокирование действий с записью при выполнении ajax-запроса (чтобы пользователь не намудрил и не отправил два разных запроса на обработку одной и той же записи)
- расширение данных, характеризующих задание - добавление степени выполнения и статуса важности записи
- подсветка тех заданий, срок по которым истекает сегодня
- возможность собирать задания в категории и отображать в разрезе категорий
- возможность писать к заданию комментарии, пояснения и т.д.
- введение даты не вручную, а с помощью календаря
- и т.д. придумать можно еще много чего
Так чем займемся в следующем уроке?
Спонсор поста:
Дизайн сайтов, разработка фирменного стиля.
Купить хостин.
Понравился пост? Будь в курсе последних событий: подпишись на RSS-ленту.!
Также читайте по теме:
- 27 ноября

Леша, всегда с удовольствием читаю твои статьи по jQuery. Очередной раз было интересно.
Предложение:
Я думаю стоит добавлять в файл mysql_connect.php какую-то проверку, что его вызывает именно localhost, а не сторонний брайзер. И делать проверку что вызов скрипта идет именно ч-з XMLHttpRequest.
В целом же было интересно, кое-что новое узнал для себя.
*брайзер = браузер
А, секунду, ведь $.post выполняет именно браузер, потому проверки на localhost не сделаешь. Какие варианты защиты php скриптов есть?
Вообще-то или я что-то пропустил, или Алексей не сообщил всю информацию. Как минимум необходимо указать чарсет, кодировку и тому подобное. Всё так хорошо только при использовании utf-8, шаг вправо, шаг влево - начинаются грабли. Потому надо бы указывать кодировку исходной страницы, в какой кодировке данные хранятся в базе и т.п.
На счёт защиты скриптов… что один человек сделал, другой всегда сломать сможет… но проверить соостветствиее введённого ожидаемому, убрать теги и управляющие символы, ну там экранировать по желанию - избавит от очень большого количества возможных проблем
Да, в mysql_connect.php еще очень много чего добавлять надо
Но, это уже история PHP и MySQL, а не jQuery. Хотя планирую и об этом написать урок.
Я так понял, что ты хочешь удостовериться, что переменные в массиве $_POST переданы в скрипт именно браузером, а не каким-нибудь скриптом? Или я что-то путаю?
2none,
спасибо за подсказку! просто пропустил кусок кода с определением функции autoencode()! хотя в коде она использована. Сейчас исправлю.
Ждем урока по PHP и MySQL ))
Алексей, спасибо огромное!!! тока не получается чот.
прописал все верно. но в таблицу значения не выдаются, пишет undefined и так три строки подряд.
пробую добавить значение в таблицу- data передается, а поле id остается пустым.
подскажи, пожалуйста, в чем может быть дело(((( три часа голову ломаю- по всякому пробовал
Я так понял что в случае с case ‘list’ вызывается несуществующая функция autoencode. Это так специально сделано чтобы мануал был нерабочий или что?
а всё. нашел. хоть бы ссылку сделали что мол мануал не работает, читай следующий пост(
Здравствуйте Алексей!
У меня вопрос может не совсем в тему.
Но у меня на форме 2 кнопки Submit.
Как отловить их события, т.е. у каждой кнопки разные события.
Хорошая статья
Статейка оч.полезная, но за autoindex() обидно.
Спасибо, тем не менее. Много нового узнал
А можно поинтересоваться, зачем в SQL запросе присутствует “WHERE 1″?