От автора: в данном уроке мы рассмотрим создание динамического меню, с помощью технологии AJAX. Если Вы работали с CMS WordPress, то, наверное, знакомы с такой штукой, как виджеты WordPress. В данном случае виджеты — это блоки, которые можно перемещать в режиме Drag and Drop («тащи и бросай»). При этом, после перемещения блока, он сохраняет свою позицию, т.е., к примеру, в сайдбаре сайта у нас имеется блок поиска, под которым размещен блок с произвольным текстом… мы захотели поменять их позиции… нет ничего проще — перетащили блок с поиском под блок с текстом и… вуаля — на сайте эти изменения расположения блоков вступили в силу. Согласитесь, динамическое меню — это быстро, удобно и эффектно.
1. Постановка задач. Создание БД
Для начала сформулируем задачу. Вместо блоков у нас будет меню, пункты которого у нас и будут перетаскиваемыми блоками. Для чего вообще это нужно? Допустим, мы хотим изменить сортировку пунктов меню, т.е. сделать так, чтобы из БД пункты меню выводились не отсортированными по идентификатору пункта меню, а по, к примеру, полю position. Для этого, конечно же, можно написать несложный скрипт, который будет выводить текущую позицию каждого из пунктов меню в числовом виде (1, 2, 3 и т.д.). Далее мы сможем для конкретного пункта задать новую числовую позицию и сохранить изменения в БД. Но гораздо быстрее и красивее будет выглядеть реализация, когда мы просто возьмем пункт меню и перетащим его на новое место… все — он сохранит свою позицию.
Итак, мы создаем меню и через административную часть сайта сможем изменять позицию его пунктов. Давайте теперь определимся с тем, что нам понадобится. Прежде всего, нам понадобятся 2 библиотеки jQuery:

JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Узнать подробнеесобственно сама библиотека jQuery;
библиотека jQuery User Inteface.
Взять их можно на офф сайте jQuery. Также Вы найдете эти скрипты в исходниках к уроку. Также нам понадобится файл стилей для оформления пунктов меню, который Вы можете сгенерировать на сайте jQuery в разделе jQueryUI или найти в исходниках к уроку (или написать стили для пунктов меню самостоятельно в зависимости от дизайна Вашего сайта).
У нас будет 2 файла. Первый файл (index.php) — это страница сайта для общего доступа. Второй файл (admin.php) — это страница админки, на которой мы сможем изменять позицию пунктов меню. Конечно же, доступ к админке должен быть ограничен (как это сделать Вы можете узнать из предыдущих уроков). Основное отличие файла admin.php состоит в том, что мы подключили в него 2 указанных скрипта:
1 2 | <script src="scripts/jquery-1.6.2.min.js" type="text/javascript"></script> <script src="scripts/jquery-ui-1.8.14.custom.min.js" type="text/javascript"></script> |
В файл index.php эти библиотеки подключать не нужно.
Также в оба файла мы подключим файл стилей:
1 | <link type="text/css" href="css/jquery-ui-1.8.14.custom.css" rel="stylesheet" /> |
И напишем несколько правил в стилях на странице index.php:
1 2 3 4 5 | <style> #sortable { list-style-type: none; margin: 0; padding: 0; width: 200px; } #sortable li { margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; font-size: 1.4em; height: 15px; cursor: pointer;} #sortable li span { position: absolute; margin-left: -1.3em; } </style> |
И на странице admin.php:
1 2 3 4 5 6 | <style> #sortable { list-style-type: none; margin: 0; padding: 15px 40px 15px 0; width: 200px; } #sortable li { margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; font-size: 1.4em; height: 15px; cursor:move} #sortable li span { position: absolute; margin-left: -1.3em; } .block{/*border:1px solid #ccc;*/ width:200px;} </style> |
Следующее, что нам понадобится — это БД, в которой будут храниться названия каждого из пунктов меню и их текущая позиция на сайте. Чтобы не создавать БД вручную, Вы можете импортировать дамп БД из исходников (файл test.sql).
БД я назову test и создам в ней таблицу sortable, для которой понадобится всего 3 поля:
id, тип INT, первичный ключ, автоинкремент;
name, тип VARCHAR, длина 255 символов;
position, тип INT.
Теперь занесем в созданную таблицу 3 тестовых пункта меню:
Пункт 1 с позицией 1;
Пункт 2 с позицией 2;
Пункт 3 с позицией 3;
Отлично! БД готова.
2. Выводим меню из БД
БД создана и теперь можно вывести меню на сайт. Прежде, чем сделать это создадим файл с подключением к БД. Этот файл мы будем включать на индексную и админскую страницы сайта. Я назову файл db.php. Как подключаться к серверу БД и выбирать необходимую для работы БД мы уже знаем, поскольку неоднократно это делали:
1 2 3 4 5 6 | <?php mysql_connect("localhost", "root", "") or die("No connect to server"); mysql_select_db("test") or die("No select DB"); ?> |
Теперь подключим этот файл в самом верху индексной и админской страницы:
1 | <?php require_once 'db.php'; ?> |
Отлично! Теперь выведем пункты меню на сайт. Для этого создадим заявку (запрос), которой выберем из таблицы sortable все, что там есть, и отсортируем выбранное по полю position в порядке возрастания (таким образом, пункты меню будут выведены традиционно — 1, 2, 3). С запросами мы уже также работали, поэтому ничего нового здесь быть для Вас не должно.
Открываем конструкцию PHP в том месте, где должно быть выведено меню и пишем код:
1 2 3 4 5 6 7 8 9 10 | <?php $res = mysql_query("SELECT * FROM `sortable` ORDER BY `position`") or die(mysql_error()); echo "<ul id='sortable'>\r\n"; while($row = mysql_fetch_assoc($res)){ echo "<li id='{$row['id']}' class='ui-state-default'>{$row['name']}</li>\r\n"; } echo "</ul>"; ?> |
Здесь мы выводим пункты меню списком. При этом стоит обратить внимание на то, что каждый из пунктов имеет свой идентификатор, значением которого является идентификатор конкретного поля в БД. Этот идентификатор нам понадобится для того, чтобы уникализировать каждый из пунктов меню.
Отлично!

JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Узнать подробнееПункты меню выбраны из БД и появились на сайте.
3. Реализуем сортировку без сохранения
С файло index.php мы закончили и теперь можно перейти к админке. Для начала просто скопируем код вывода меню из индексного файла. Теперь, если посмотреть в браузере страницу admin.php, то увидим аналогичное меню за одним исключением — при наведении на пункты меню курсор приобретает крестовидную форму, что говорит о том, что данные пункты можно перетаскивать. Реализуется такая форма курсора правилом в стилях:
1 | cursor:move |
Это правило мы прописали ранее. Курсор изменяет свою форму, но пункты меню пока что невозможно перетаскивать. Для того, чтобы это было возможно сделать, пропишем простой скрипт в блоке head:
1 2 3 4 5 6 7 | <script type="text/javascript"> $(document).ready(function(){ $('#sortable').sortable({ }); }); </script> |
Все…
Теперь мы уже можем перетаскивать пункты меню. Это реализовано благодаря работе тех двух библиотек, которые мы подключили ранее. Здесь же мы просто взяли в набор jQuery элемент с идентификатором sortable и вызвали для него метод sortable. Если Вы не знакомы с основами jQuery, то рекомендую Вам обратиться к предыдущим урокам на сайте, где мы знакомились с основами работы jQuery. Сам метод sortable, к которому мы обратились, описан в библиотеке jQuery UI. Он имеет множество параметров, событий и методов, некоторыми из которых мы сейчас и воспользуемся. Прежде всего, давайте ограничим перемещение блоков только осью Y (только по вертикали). Для этого обратимся к опции axis:
1 2 3 4 5 6 7 | <script type="text/javascript"> $(document).ready(function(){ $('#sortable').sortable({ axis: 'y' }); }); </script> |
Теперь давайте уменьшим непрозрачность перетаскиваемого элемента до 50%:
1 2 3 4 5 6 7 8 | <script type="text/javascript"> $(document).ready(function(){ $('#sortable').sortable({ axis: 'y', opacity: 0.5 }); }); </script> |
Отлично!
Теперь надо сделать так, чтобы мы могли получить текущую позицию каждого из элементов после того, как мы завершим перемещение. Для этого обратимся к событию stop и методу (функции) toArray:
1 2 3 4 5 6 7 8 9 10 11 12 | <script type="text/javascript"> $(document).ready(function(){ $('#sortable').sortable({ axis: 'y', opacity: 0.5, stop: function(){ var arr = $('#sortable').sortable("toArray"); alert(arr); } }); }); </script> |
Итак, что мы здесь сделали? После наступления события stop (когда завершено перетаскивание элемента) мы обращаемся к элементу с id sortable (это наше меню) и вызываем функцию toArray. Данная функция получает id каждого из отсортированных элементов и помещает его в массив в виде строки, где значения разделены запятой. Результат помещаем в переменную arr. Таким образом, если мы изменим изначальную позицию пунктов 1 и 2, то в переменной arr будет содержаться вот такая строка — 2, 1, 3. Для наглядности можно вывести методом alert значение переменной arr после каждого из перетаскиваний.
4. Обращаемся к методу ajax
Мы выполнили часть задачи — пункты меню можно теперь перетаскивать. Но они не сохраняют своей позиции в БД и после обновления страницы, все встает на круги своя. Для того, чтобы обновления были внесены в БД, необходимо передать значение переменной arr в скрипт PHP, который бы занес обновленные данные в БД. Сделать это, обратившись к методу jQuery под названием ajax(). Итак, пропишем его со всеми необходимыми нам параметрами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <script type="text/javascript"> $(document).ready(function(){ $('#sortable').sortable({ axis: 'y', opacity: 0.5, stop: function(){ var arr = $('#sortable').sortable("toArray"); //alert(arr); $.ajax({ url: 'save.php', type: 'POST', data: {masiv:arr}, error: function(){ $('#res').text("Ошибка!"); }, success: function(){ $('#res').text("Сохранено!"); } }); } }); }); </script> |
Пройдемся по параметрам:
url: указываем имя файла, который примет данные из скрипта (аналог атрибута action в форме);
type: тип передаваемых данных (аналог атрибута method в форме);
data: передаваемые данные в формате {переменная:значение};
error и success: события ошибки и успеха запроса соответственно.
Таким образом, мы передаем значение переменной masiv методом POST в файл save.php. В случае успеха в блок с идентификатором res будет добавлен текст "Сохранено!", в случае ошибки — «Ошибка!».
Все просто…
5. Реализуем сохранение сортировки
Осталось написать скрипт для файла save.php, который бы принимал данные. Создадим файл save.php и запишем в него следующий код:
1 2 3 4 5 6 7 8 9 10 11 | <?php require_once 'db.php'; $pos_new = 1; foreach($_POST['masiv'] as $item){ $res = mysql_query("UPDATE `sortable` SET `position`='{$pos_new}' WHERE `id`='{$item}'"); $pos_new++; } ?> |
Теперь давайте попробуем разобраться в работе того, что мы написали только что.
6. Логика работы скрипта
Поскольку мы принимаем массив данных — используем цикл foreach для того, чтобы пройтись по каждому из элементов массива. До начала цикла создадим переменную $pos_new со значением 1, которая будет новым значением позиции для каждого из пунктов меню. В теле цикла мы будем с каждым шагом цикла увеличивать значение переменной на единицу. Таким образом, ее значение последовательно будет изменяться от 1 до 3. Также в теле цикла указываем запрос, которым устанавливаем текущее значение переменной $pos_new значением поля position. Но делаем мы это для того поля, в котором значение поля id будет равно текущему значению перебираемого элемента массива, который мы получили. Здесь важно понять саму логику работы запроса. Поскольку мы передаем в массиве идентификаторы каждого из пунктов меню, то логично, что они будут соответствовать идентификаторам пунктов меню в БД. Именно поэтому для полученного идентификатора мы установим текущее значение переменной $pos_new в БД.
Вам проще будет уяснить работу скрипта, если Вы при помощи метода alert будете видеть что отсылается и наблюдать изменения в БД.
7. Добавляем юзабельности
Осталось добавить некоторые улучшения, которые сделают более приятной работу со скриптом. Например, мы можем после каждого сохранения только на время показывать текст "Сохранено!", а затем сделать так, чтобы он исчезал. Также сделаем так, чтобы место блока, который мы захватили, подсвечивалось:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <script type="text/javascript"> $(document).ready(function(){ $('#sortable').sortable({ axis: 'y', opacity: 0.5, placeholder: 'ui-state-default', containment: '.block', stop: function(){ var arr = $('#sortable').sortable("toArray"); //alert(arr); $.ajax({ url: 'save.php', type: 'POST', data: {masiv:arr}, error: function(){ $('#res').text("Ошибка!"); }, success: function(){ $('#res').show().text("Сохранено!").fadeOut(1000); } }); } }); }); </script> |
Теперь гораздо лучше. Вот и все. Наша задача реализована.
Заключение урока по созданию динамического меню для сайта
Пункты динамического меню после перетаскивания сохраняют свою позицию в БД. Стоит только напомнить о безопасности. В принципе, об этом я уже неоднократно говорил в предыдущих уроках, но напомню основное правило безопасности: все данные, принимаемые от пользователя, должны соответствующим образом быть обработаны. Здесь, конечно, в этом особого смысла нет, поскольку работа с БД осуществляется на уровне админки, но если бы мы принимали данные из пользовательской части, то каждый из элементов массива необходимо было бы привести к явному целочисленному типу функцией (int) или intval(), чтобы в запрос гарантированно попадало число.
На этом наш урок по созданию динамического меню завершен. Ваши вопросы и отзывы всегда будем рады видеть в комментариях. Удачи в Ваших разработках и до новых встреч!
Автор: Кудлай Андрей. Команда webformyself.

JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Узнать подробнее
Разработка веб-приложения на PHP
Создайте веб-приложение на PHP на примере приема платежей на сайте
Смотреть
Спасибо, как всегда пошаговая и подробная инструкция. Урок пригодится.
Очень интересно!
Спасибо!!!
Теперь не успокоюсь, пока где-нибудь не опробую!
Спасибо за очень добротно сделанный и полезный урок.
Понравилось, как всегда всё понятно, но пока ещё придётся разбираться, как я поняла.
Андрей, подскажите, пожалуйста, как на Вордпресс сделать Популярные записи картинками, как у вас, а не просто заголовок статьи.
Если не ошибаюсь, то в этом Вам поможет плагин WordPress Related Posts. Также можете посмотреть плагин WordPress Popular Posts и другие похожие.
Да, как сказал Андрей, я использовал плагин WordPress Popular Posts, но не в читом виде, так как он картинки не выводит, а дописывал его немного, чтобы он выводил картинки. Картинки которые выводятся нужно делать миниатюрами – в эту сторону копать нужно, если хотите подобное сделать.
Супер урок!!! Побольше бы таких про ajax!!!
Спасибо за урок! Автору ОГРОМНЫЙ респект!!!
О да.. Давно хотел сделать, но с аяксом не особа знаком.. Сделал сортировку вотографий в фотоальбоме, потом ужаснулся… Так низя
Если в альбоме больше 100 фоток, представляете что будет? Сколько будет инсертов в БД внутри цикла?
Логично
Этот способ хорош для незначительного количества информации — блоки, пункты меню и т.п.
Скажем так, раз они перемещаются, сохраняя позицию и без записи, то впринципе сабмит можно повесить на кнопку. Хотя наверное логичнее менять местами 2 цифры каждый раз.. Вот это было бы идеально. Где бы ещё пример реализации найти для ознакомления..
Кстати, как бы проверить откуда пришёл запрос? Куда всавить переменну, или идентификатор, чтобы он и массиву не мешал и можно было проверить его наличие в скрипте сохраниения.
отлично, спасибо, все получилось. Интересует как реализовать то же, но с вложенными пунктами для построения древовидного меню. Пункты меню родитель и дочерний пункт связаны через parent_id. Буду благодарен если примерно опишите общий алгоритм.
Пожалуйста.
А алгоритм будет тем же. Только нужно передать еще ID родителя, чтобы в БД изменять порядок у соответствующих потомков. Тут все упирается только в вопрос построения древовидного меню. А в остальном — все также, сортировка то производится внутри родителя, т.е. внутри родителя меняется позиция дочерних пунктов и, соответственно, меняем значения позиции этих дочерних пунктов в БД.
Спасибо за быстрый ответ, а что на счет jquery части?
А что с частью jQuery? Не совсем понял этот вопрос… Подозреваю, что имелось в виду, как применить метод sortable именно к дочерним элементам текущего родителя… если так, то это не должно быть сложно. При построении меню каждый родитель будет иметь ID типа 1, 2 и т.д. Соответственно, остается получить эту цифру. Ну а метод sortable вызывается не для элемента с конкретным ID, а динамически. К примеру, таким образом — $(«#»+ID).sortable… ID в данном случае — это переменная, в которой получаем ID родителя…
Как-то так выглядит общая схема реализации… дальше осталось попробовать насколько она реализуема.
Спасибо, не понятно как реализовать перетаскивание из дочернего элемента в корневой и на оборот, так же как это работает в wordpress в произвольном меню.
На нашем форуме можете выложить свои наработки кода и будем решать конкретно те моменты, которые вызывают затруднения.
Здравствуйте! Подскажите пожалуйста как сделать чтобы на конкретной странице в сайдбаре выводились подпункты меню, к которым относится данная категория или страница. Смысл в том, чтобы в главном меню отображался только первый уровень меню. А в сайдбаре — второй и третий
Здравствуйте, Мария.форуме , как можно подробнее опишите задачу… если затрудняетесь с объяснением, то можно приложить рисунки или объяснить на конкретном примере.
Не совсем понял, что Вам нужно получить. Создайте тему на нашем
Лучше использовать не событие «stop», а событие «update» ибо смысл сохранять в БД информацию если изменений не произошло, лишняя нагрузка. А событие «update» сработает только если пользователь двигал двигал и всёж передвинул а не вернул на место
Согласен.
На момент написания урока (2 года назад) в документации был пример именно с использованием события stop(), поэтому stop() использовался в уроке.
аа, ну тогда понятно, на дату просто не посмотрел, а так за урок спасибо, помог очень даже
Пожалуйста
Урок действительно помог, сам код в своей работе уже реализовывал. Но сильно не разбираясь всё, а тут всё по полочкам.
Действительно на данный момент стоит задача реализовать создание динамического меню с большим количеством параметров, и главная идея в том, чтобы можно было перетаскивать пункты меню не только между собой, но и менять родителя простым перетаскиванием
У меня что-то не получается сделать сортировку для элементов таблицы
элемент 1
элемент 2
элемент 3
элемент 4
элемент5
элемент 6
элемент 7
элемент 8
$(«#sort-ell tr td»).sortable({
});
Сортировка происходит только когда обращаюсь к
таблицы, а мне нужно сортировать . что я не правильно делаю
Спасибо за урок! Прикрутил, получилось. Но вот… подвязать код к еще одному списку ну выходит (( файл save.php прописан только под конкретную таблицу. Не подскажете как можно сделать код универсальным, так чтобы не было привязки только к ‘sortable’? Заранее благодарен!
Создайте для каждой из таблиц свою функцию и просто вызывайте нужную функцию для той или иной таблицы.
Вы имеете ввиду, в файле который принимает данные из скрипта (save.php)? А как мне в этом файле «словить» идентификатор места в котором я хочу произвести сортировку? Пробовал принимать GET страницы в которой хочу сортировать.. вот так:
if ($_GET['module'] == ‘menu’){
mysql_query («SELECT * FROM menu…
}elseif($_GET['module'] == ‘calalog’)|{
mysql_query («SELECT * FROM catalog…
}
чтобы посылать разные запросы, под каждую таблицу, но ничего не выходит.
Функция не привязывается к конкретному идентификатору, функция — это в некоторой степени абстрактный код, который можно использовать многократно для различных элементов. Соответственно, можно описать функцию, которая будет работать с абстрактным элементом, а конкретный элемент уже подавать этой функции параметром при вызове.
Спасибо большое. очень полезная статья! надо опробовать)
А как сделать динамическое меню если у меня вместо базы данных MYSQL текстовый файл, в котором располагаются ссылки и названия пунктов меню. Не могли бы дать пояснение ?
В зависимости от структуры файла выбираете подходящуюфункцию для работы с файлами , например функцию file . После считывания файла работаете с массивом полученных данных.
Все делала по уроку, но на этой строке выбивает ошибку, что делать?
//строка 34
$res = mysql_query(«SELECT * FROM
sortable
ORDER BYposition
«) or die(mysql_error());//ошибка
Fatal error: Uncaught Error: Call to undefined function mysql_query() in /Applications/MAMP/htdocs/new_test/index.php:34 Stack trace: #0 {main} thrown in /Applications/MAMP/htdocs/new_test/index.php on line 34
Дело в том, что это уже достаточно старый урок — 2011 года. За это время произошли некоторые изменения. В частности, в новых версиях PHP расширения mysql_ уже нет (об этом и сообщается в ошибке), вместо него можно использовать расширениеmysqli_ .