Алексей Качаев | Web-developer, фрилансер, менеджер

PHP, jQuery, AJAX, CodeIgniter, ZendFramework, Web2.0, блоггинг, Wordpress, бизнес, StartUp, Инветоры, web-проекты, бизнес-идеи, фриланс, интерфейсы

jQuery, MySQL и полноценное веб-приложение

Опубликовано: Алексей Качаев |

Спонсор поста: Интернет магазин детских игрушек лего.

Алексей Качаев. jQueryИтак, сегодня представляю второй урок по использованию 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'>&rarr;</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">Добавить запись &rarr;</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-ленту.!

Также читайте по теме:

13 комментариев на “jQuery, MySQL и полноценное веб-приложение”

  1. Леша, всегда с удовольствием читаю твои статьи по jQuery. Очередной раз было интересно.

    Предложение:
    Я думаю стоит добавлять в файл mysql_connect.php какую-то проверку, что его вызывает именно localhost, а не сторонний брайзер. И делать проверку что вызов скрипта идет именно ч-з XMLHttpRequest.

    В целом же было интересно, кое-что новое узнал для себя.

  2. *брайзер = браузер

  3. А, секунду, ведь $.post выполняет именно браузер, потому проверки на localhost не сделаешь. Какие варианты защиты php скриптов есть?

  4. Вообще-то или я что-то пропустил, или Алексей не сообщил всю информацию. Как минимум необходимо указать чарсет, кодировку и тому подобное. Всё так хорошо только при использовании utf-8, шаг вправо, шаг влево - начинаются грабли. Потому надо бы указывать кодировку исходной страницы, в какой кодировке данные хранятся в базе и т.п.
    На счёт защиты скриптов… что один человек сделал, другой всегда сломать сможет… но проверить соостветствиее введённого ожидаемому, убрать теги и управляющие символы, ну там экранировать по желанию - избавит от очень большого количества возможных проблем

  5. Да, в mysql_connect.php еще очень много чего добавлять надо :) Но, это уже история PHP и MySQL, а не jQuery. Хотя планирую и об этом написать урок.

    Я так понял, что ты хочешь удостовериться, что переменные в массиве $_POST переданы в скрипт именно браузером, а не каким-нибудь скриптом? Или я что-то путаю?

  6. 2none,

    спасибо за подсказку! просто пропустил кусок кода с определением функции autoencode()! хотя в коде она использована. Сейчас исправлю.

  7. Ждем урока по PHP и MySQL ))

  8. Алексей, спасибо огромное!!! тока не получается чот.
    прописал все верно. но в таблицу значения не выдаются, пишет undefined и так три строки подряд.
    пробую добавить значение в таблицу- data передается, а поле id остается пустым.
    подскажи, пожалуйста, в чем может быть дело(((( три часа голову ломаю- по всякому пробовал

  9. Я так понял что в случае с case ‘list’ вызывается несуществующая функция autoencode. Это так специально сделано чтобы мануал был нерабочий или что? :)

  10. а всё. нашел. хоть бы ссылку сделали что мол мануал не работает, читай следующий пост(

  11. Здравствуйте Алексей!
    У меня вопрос может не совсем в тему.
    Но у меня на форме 2 кнопки Submit.
    Как отловить их события, т.е. у каждой кнопки разные события.
    Хорошая статья

  12. Статейка оч.полезная, но за autoindex() обидно.
    Спасибо, тем не менее. Много нового узнал

  13. А можно поинтересоваться, зачем в SQL запросе присутствует “WHERE 1″?

Оставьте комментарий