jQuery + плагины: сортировка и редактирование списка

Владимир | | Ajax, HTML, JavaScript, PHP, Web разработка.

jquery sortable

Довольно давно я написал статью о том, как с помощью библиотек Prototype и Scriptaculous добавить возможность редактирования и удаления записей обычному html списку (вообще-то это был цикл статей 1, 2, 3, 4, 5).

С тех пор несколько читателей просили доработать пример и добавить возможность сортировки записей.

Переделывать тот пример я не буду, т.к. Prototype сейчас практически не использую, лучше покажу, как решить задачу с помощью jQuery.

Сформулируем требования.

Необходимо создать html список с возможностями:

— изменение записей;
— удаление записей;
— изменение порядка записей.

Кроме того, необходимо предусмотреть возможность создания новых записей.

В общем, работать это будет примерно так.

Demo

Сразу же даю ссылку на архив с исходниками.

Source

Теперь разберём принцип работы.

На сегодняшний день сохранить данные html списка можно двумя способами.

1) На стороне сервера в БД. При этом, после каждого изменения (удаления, создания новой) записи будет отправляться запрос серверу.

2) В браузере, при условии, что он поддерживает HTML5 или есть доступ к какому-нибудь другому хранилищу. Этот способ работает быстрее и не требует постоянного подключения к интернету, но поддерживается не всеми браузерами. В частности, в IE работать не будет.

Поэтому я остановился на первом варианте.

Итак, наше приложение состоит из двух частей: клиентской и серверной.

Клиентская часть представляет собой HTML страницу и набор JS скриптов, которые отправляют запросы, позволяют сортировать список и т.п.

Серверная – это набор PHP скриптов, которые обрабатывают запросы и работают с базой данных. Я не использовал фреймворк в этом примере, поэтому получился набор отдельных скриптов под каждую операцию. При использовании фреймворка эти скрипты станут методами контроллера и, естественно, вы сможете использовать встроенную библиотеку для работы с БД.

Кстати, для этого примера я выбрал базу sqlite, но, т.к. доступ к ней осуществляется с помощью PDO, то вы легко сможете заменить её, например, на MySQL (достаточно будет изменить параметры подключения).

Подключение к БД

Выполняется скриптом db.php, сяостоящим всего из одной строки.

$dbh = new PDO('sqlite:data/data.sqlite');

Тут предполагается, что база находится в файле data.sqlite, расположенном в папке data.

Структура БД.

Т.к. задача у нас достаточно простая, мы обойдёмся одной таблицей с тремя полями:

id – первичный ключ;
note – текст записи;
note_order – порядковый номер записи в списке (с помощью этого поля осуществляется сортировка).

Создание страницы со списком

Выполняется скриптом index.php

<?php
try {
	//подключаемся к базе
	require_once('db.php');
	//получаем все записи
	$query = $dbh->query('SELECT * FROM notes ORDER BY note_order DESC');
	$result = $query->fetchAll();
	$dbh = null;
} catch(Exception $e) {
	$error = 'Ошибка: '.$e->getMessage();
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
	<title>jQuery Sortable Notes</title>
	
	<link rel="stylesheet" type="text/css" href="css/style.css" media="all" />
	
	<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
	<script type="text/javascript" src="js/jquery-ui-1.8.custom.min.js"></script>
	<script type="text/javascript" src="js/jquery.jeditable.mini.js"></script>
	<script type="text/javascript" src="js/jquery.json-2.2.min.js"></script>
	<script type="text/javascript" src="js/init.js"></script>
</head>
<body>
	<h1>Заметки</h1>
	<form action="addnew.php" method="post" id="addNewNote">
		<p>
		<input type="text" name="notetext" id="notetext" size="40" />
		<input type="submit" value="Добавить заметку" />
		</p>
	</form>
<?php
	//выводим сообщение об ошибке или формируем список записей
	if (isset($error)) {
		echo '<h2>'.$error.'</h2>';
	} else {
		if (count($result) > 0) {
			echo '<ul id="sortable">';
			foreach ($result as $row) {
				echo '<li id="note_'.$row['id'].'" class="editable"><span class="note" id="n_'.$row['id'].'">'.$row['note'].'</span><a href="#"><img src="images/delete.png" alt="Удалить" /></a></li>';
			}
			echo '</ul>';
?>
	<form id="changeOrder" method="post" action="changeorder.php">
		<p><input type="submit" value="Сохранить сортировку" /></p>
	</form>
<?php
		} else {
			echo '<h2>Записи отстутствуют</h2>';
		}
	}
?>
</body>
</html>

Прежде всего, мы подключаемся к базе и получаем список записей. Затем, формируем страницу. На странице размещаем:

— форму добавления новой записи;
— список записей (или сообщение о том, что они отсутствуют);
— форму обновления порядка записей.

Обратите внимание на разметку списка.

<ul id="sortable">
	<li id="note_58" class="editable">
		<span class="note" id="n_58">Заметка 1</span>
		<a href="#"><img src="images/delete.png" alt="Удалить" /></a>
	</li>
</ul>

В атрибутах id хранится значения поля id соответствующих записей в БД. Приставки note_ и n_ добавлены для совместимости со стандартом (значение атрибута id не может начинаться с цифры).

Каждый тег li содержит текст записи и ссылку «Удалить».

Сортировка списка

Для того, чтобы пользователь смог перемещать записи списка мы подключили библиотеку jQuery и плагин jQuery UI.

Сделать список сортируемым можно одной строчкой кода (в файле init.js)

$('#sortable').sortable();

Добавление новых записей

Рассмотрим скрипт addnote.php

try {
	//проверяем, пришел текст записи или нет
	if (!isset($_POST['notetext']) || '' == $_POST['notetext']) {
		throw new Exception('Не указан текст записи');
	}
	//подключаемся к БД
	require_once('db.php');
	//находим запись с максимальным значением в поле order
	$query = $dbh->query('SELECT note_order FROM notes ORDER BY note_order DESC LIMIT 1');
	$result = $query->fetchAll();
	if (0 == count($result)) {
		$order = 1;
	} else {
		$order = $result[0]['note_order'] + 1;
	}
	//вставляем новую запись, поле note_order новой записи будет на единицу больше,
	//чем у найденной записи
	$notetext = htmlspecialchars($_POST['notetext']);
	$dbh->exec('INSERT INTO notes (note, note_order) VALUES ('.$dbh->quote($notetext).', '.$order.')');
	$result = array();
	//получаем id созданной записи
	$result['id'] = $dbh->lastInsertId();
	$result['note'] = $notetext;
	$result['note_order'] = $order;
	$dbh = null;
	//отправляем результаты браузеру
	echo json_encode($result);
} catch(Exception $e) {
	echo json_encode(array('err'=>'Ошибка: '.$e->getMessage()));
}

Прежде чем добавлять в базу запись, нам нужно определиться с её номером сортировки. Форма находится над списком, поэтому будет логично, что запись появится сразу под ней, т.е. в начале списка.

Чтобы не создавать отрицательные номера сортировки, я решил выводить список в порядке убывания этих номеров (значений в поле note_order). Поэтому первая запись списка будет иметь максимальный номер.

Для того, чтобы определить номер новой записи, мы выполняем запрос, который ищет запись с максимальным номером, и добавляем к этому номеру единицу. Если записей в базе нет, то начинаем нумерацию с единицы.

Затем выполняем обычный запрос создания записи, получаем её id и отправляем его браузеру.

Теперь рассмотрим клиентскую часть.

Записи у нас будут создаваться с помощью AJAX запросов, поэтому назначаем форме обработчик события submit.

$('#addNewNote').submit(function() {
	var note = $('#notetext').val();
	if (note != '') {
		//отправка запроса
		$.post('addnew.php', {'notetext':note}, function(data) {
			var response = eval('('+data+')');
			if (response['err'] != null) {
				alert(response['err']);
			} else {
				var list = $('#sortable');
				//если список отсутствует
				if (list.length == 0) {
					//удаляем текст "Записи отстутствуют"
					$('h2').detach();
					//создаем список
					$('<ul id="sortable"></ul>').sortable().insertAfter($('#addNewNote'));
					//создаем форму "Сохранить сортировку"
					createChangeOrderForm().insertAfter($('ul#sortable'));
				}
				//вставляем новую запись в список
				createLi(response['id'], response['note']).prependTo('#sortable');
			}
		});
	} else {
		alert('Вы забыли ввести текст заметки');
	}
	return false;
});

В нём мы проверяем, заполнено ли поле с текстом записи, и отправляем AJAX запрос скрипту addnew.php.

После получения ответа сервера мы либо выводим сообщение об ошибке, либо добавляем новую запись. При добавлении записи проверяем, существует ли список вообще и, если нет, создаём его. Обратите внимание, что при создании списка мы делаем его сортируемым (метод sortable()).

Отдельно рассмотрим функцию создания элемента списка (тег li).

function createLi(id, note) {
	var note = $('<span class="note" id="n_' + id + '">' + note + '</span>').editable('update.php', editableOptions);
	var link = $('<a href="#"><img src="images/delete.png" alt="Удалить" /></a>').click(removeLi);
	return $('<li id="note_' + id + '" class="editable"></li>').append(note).append(link);
}

Здесь мы устанавливаем классы и id элементов и назначаем обработчики событий. Всего обработчиков два:

— обработчик двойного клика по тексту записи (он создаётся методом .editable, подробнее о нём чуть позже);

— обработчик клика по ссылке «Удалить» (позже мы его тоже подробно рассмотрим).

На этом я сегодня остановлюсь. Остальные функции мы рассмотрим в следующий раз.

Если есть вопросы или замечания, пишите, постараюсь ответить.

До встречи!

UPD. Дописал продолжение jQuery + плагины: сортировка и редактирование списка (часть вторая)

  • Serator

    Пара вопросов / рассуждений по статье (как всегда не вдаваясь в подробности 🙂 ). В json по мимо всего прочего шлется:"id":"note_77"но можно же как"id":77.
    Ну и порядок элементов как-то сложно реализован. Зачем создавать что-то свое, если есть готовое и работает вроде как везде, то бишь нативные средства работы с DOM'ом. Имеем элемент контейнер, а в нем n нодов, у каждого автоматически есть номер :).

    • Наверное, вы правы, лучше было бы убрать приставку (note_) средствами JS, а не на сервере.
      Но есть нюанс. Для редактирования записей используется плагин jeditable, а он сам отправляет id элемента и его текст. Поэтому код для удаления приставки на сервере все-равно нужно написать 🙂

      По поводу порядка элементов я непонял. ul — это контейнер, li — элементы, в каждом span — текст записи и a — ссылка «удалить». По-моему обычная структура. id элементов совпадают с id записей в базе, иначе не получится их обновить.

  • Serator

    Пара вопросов / рассуждений по статье (как всегда не вдаваясь в подробности 🙂 ). В json по мимо всего прочего шлется:"id":"note_77"но можно же как"id":77.
    Ну и порядок элементов как-то сложно реализован. Зачем создавать что-то свое, если есть готовое и работает вроде как везде, то бишь нативные средства работы с DOM'ом. Имеем элемент контейнер, а в нем n нодов, у каждого автоматически есть номер :).

    • Наверное, вы правы, лучше было бы убрать приставку (note_) средствами JS, а не на сервере.
      Но есть нюанс. Для редактирования записей используется плагин jeditable, а он сам отправляет id элемента и его текст. Поэтому код для удаления приставки на сервере все-равно нужно написать 🙂

      По поводу порядка элементов я непонял. ul — это контейнер, li — элементы, в каждом span — текст записи и a — ссылка «удалить». По-моему обычная структура. id элементов совпадают с id записей в базе, иначе не получится их обновить.

  • Пример отличный, скачал исходники! Интересно как можна было это реализовать на HTML5? Жду продолжения, очень интересно!

    • Владимир Соловых

      Есть такой http://www.yiiframework.com/extension/jeditable/
      Пока не использовал. Я вообще не совсем понимаю как его использовать 🙂

      • А что именно не понятно? Это расширение к фреймворку yii, которое подключает JS плагин jeditable.

  • Пример отличный, скачал исходники! Интересно как можна было это реализовать на HTML5? Жду продолжения, очень интересно!

  • wra

    Спасибо.
    Очень интересно! А как на счет реализации в виде виджета для Yii (было бы очень здорово)?

    • Спасибо за совет, но пока такой виджет делать не буду. Не совсем понятно как его использовать.

  • wra

    Спасибо.
    Очень интересно! А как на счет реализации в виде виджета для Yii (было бы очень здорово)?

    • Спасибо за совет, но пока такой виджет делать не буду. Не совсем понятно как его использовать.

  • Очень интересный примерчик! Игрался очень долго демкой! А без БД такое замутить никак нельзя? Как насчет второго варианта?

    • Serator

      http://dev.w3.org/html5/webstorage/ (кстати в ie8 вроде как работало 😉 )

      Ну и sqlite еще есть, но там меньше поддержки браузерами да и не нужна она для этих целей.

      • Да, но перетягивание элементов по-моему не работает.
        К тому же в html5 есть и другие возможности, я еще думаю как лучше сделать 🙂

    • Данные нужно где-то хранить, если хранить их только на клиенте, то при входе с другого компьютера изменения в списке посмотреть не удасться.

  • Очень интересный примерчик! Игрался очень долго демкой! А без БД такое замутить никак нельзя? Как насчет второго варианта?

    • Serator

      http://dev.w3.org/html5/webstorage/ (кстати в ie8 вроде как работало 😉 )

      Ну и sqlite еще есть, но там меньше поддержки браузерами да и не нужна она для этих целей.

      • Да, но перетягивание элементов по-моему не работает.
        К тому же в html5 есть и другие возможности, я еще думаю как лучше сделать 🙂

    • Данные нужно где-то хранить, если хранить их только на клиенте, то при входе с другого компьютера изменения в списке посмотреть не удасться.

  • За решение этой задачи на jQuery спасибо.
    *Ушел в глубокое познование

  • За решение этой задачи на jQuery спасибо.
    *Ушел в глубокое познование

  • Спасибо за урок, все работает, но возникла одна проблема, при добавлении новой заметки, в поле note в базе mysql отображаются кракозябры, а в браузере все нормально:
    кодировка базы mysql
    — character_set_client -utf8
    — character_set_connection -utf8
    — character_set_database -utf8
    — character_set_results -utf8
    подскажите пожалуйста как можно исправить ошибку?

  • Каким образом вы смотрите базу?
    У меня была подобная проблема с phpMyAdmin, но виноват оказался я, т.к. неправильно указал кодировку соединения.

    Проверьте, что выдают запросы:

    SHOW VARIABLES LIKE 'character%'
    SHOW VARIABLES LIKE 'collation%'

  • Omro

    Огромное спасибо, единственная понравившаяся реализация идеи. 🙂

  • lamex

    $query = $dbh->query('SELECT note_order FROM notes ORDER BY note_order DESC LIMIT 1');
    $result = $query->fetchAll();
    if (0 == count($result)) {
    $order = 1;
    } else {
    $order = $result[0]['note_order'] + 1;
    }

    Некрасиво.
    SELECT MAX(note_order) FROM notes

  • Спасибо! Учту 😉

  • sutinor

    suka

  • Jurnalist

    Хорошо добавить в
    ul li {

        cursor: move;
    }