Только для читателей Lifeexample возможно открыть интернет-магазин на Moguta.CMS со скидкой в 15%
jQuery UI sortable – как узнать новую позицию элементов
Здравствуйте, уважаемые читатели блога LifeExample в этой статье будет показан процесс создания сортируемых элементов при помощи jQuery UI sortable, как сортировать элементы, отправлять обновленные данные на сервер через AJAX и обрабатывать их с помощью алгоритма определяющего удаленные элементы и место их удаления.
Если вы попали на эту страницу, значит вы скорее всего понимаете что-такое jQuery UI sortable, но на всякий случай поясню, jQuery UI sortable – это плагин позволяющий менять местами выводимые на HTML странице элементы, простым перетаскиванием их с помощью указателя мыши (обычно метод такого перемещения называют Drag and Drop – перетаскивай и отпускай ).
Использование jQuery UI sortable, в веб-проекте хорошо тем, что позволяет сэкономить большое количество времени, собственно как и множество других возможностей доступных в jQuery UI.
Написав всего одну строку кода, мы сделаем все пункты в маркированных списках перетаскиваемыми.
1 | $("ul").sortable(); |
Несмотря на удобство использования, jQuery UI sortable порождает следующую проблему, о которой программист начинает задумываться лишь при необходимости зафиксировать новые позиции элементов в базе данных. Как узнать новую позицию элементов?
Проблема заключается вот в чем: после перетаскивания элемента, последовательность поменяла свой порядок, но как узнать с какой позиции был удален элемент и на какую он переместился.
Приведу пример для наглядности
Например, если первоначальной последовательностью были цифры
1, 2, 3, 4, 5
и число "3" было перемещено между "1" и "2", получаем следующий результат:
1, 3, 2, 4, 5
В тоже время если "2" был перемещен между "3" и "4", получаем следующий массив
1, 3, 2, 4, 5
Хммм… один и тот же массив…
Как понять какие элементы поменялись местами
В скачанном архиве вы найдете классы Profile.php и SortableElement.php модуль инициализации index.php, который просто создает данные для демонстрации примера и занимается отбором. Файл sortExample.html является основной страницей и оформлен в style.css.
Файлы sortable.js и SortController.php являются важными. Они обеспечивают взаимодействие между браузером и сервером. В классе контроллера представлена логика для обработки получаемых данных от метода jQuery UI sortable при сортировке элементов.
Следующая функция JavaScript инициализирует контейнер (в данном случае div), который содержит сортируемые элементы и вызывается после построения страницы.
1 2 3 4 5 6 7 8 | function initSortableContainer(){ $(function(){ $("#SortableElements").sortable({ cursor: "move", stop: handleReorderElements }); }); } |
С помощью функции handleReorderElements() отправляется AJAX запрос на сервер. Функция initSortableContainer() задает событие для контейнера div, который содержит сортируемые элементы и указывает, что функция handleReorderElements() вызывается после завершения операций по сортировке.
Функцией jQuery sortable(«serialize») мы можем получить строку GET параметров из всех отсортированных id элементов в новом порядке
1 2 3 4 5 6 7 8 9 10 | function handleReorderElements(){ var url = 'controllers/SortController.php'; var fieldData = $( "#SortableElements" ).sortable( "serialize" ); fieldData += "&action=reorderElements"; var posting = $.post( url, fieldData); posting.done( function( data){ reorderElementsResponse( data ); }); } |
Используемая в приведенном выше коде функция reorderElementsResponse(data) является обработчиком обратного вызова AJAX метода, который был отправлен через jQuery UI sortable.
1 2 3 4 5 6 7 8 | function reorderElementsResponse( data ){ if (data.FAIL === undefined){ $("#Message").html( data.resultString + data.itemIndexString ); } else { alert( "Не удалось сохранить порядок!" ); } } |
Она выведет соответствующее сообщение об успешном или не успешном сохранении сортировки.
Для функционирования данного примера, id перемещаемого элемента должен содержать в себе нижнее подчеркивание и номер. Если вы обратите внимание на index.php, то увидите, что id перемещаемых элементов генерируются вот так
1 | id='SortableElement_".$element->getId() |
В результате id получаются такого рода: SortableElement_1, SortableElement_2, SortableElement_3 и т.д. Переменные, отправляемые на сервер, выглядят следующим образом: SortableElement[]=1&SortableElement[]=2&SortableElement[]=3, и т.д. и фактически рассматриваются в PHP как массив.
В SortController.php, мы можем получить этот массив с помощью
1 | $sortIdArray = $_POST["SortableElement"]; |
Достаточно легко!
Обработка данных на сервере
SortController.php обрабатывает отправленный AJAX запрос через jQuery UI sortable со страницы. Он получает значения и сохраняет их в переменной $sortIdArray. Затем он вытягивает первоначальный массив из сессии и сохраняет ключи, которые являются id-шниками элементов в отдельный массив:
1 2 | $sortList = $profile->getSortableElementList(); $originalIdList = array_keys( $sortList ); |
Следующая функция сравнивает массивы для определения того какой элемент был перемещен. Функция getArrayDiff($originalKeys, $newKeys) получает массив с исходной последовательностью Id элементов и массив с измененной последовательностью Id.
С помощью этой функции сравнивается каждый первоначальный и измененный порядковый номер элементов.
Сравнение происходит до момента, пока не найдутся не совпадающие друг с другом порядковые номера.
Один из id будет удален. Для того чтобы определить какой именно id будет удален эта функция удаляет первоначальный id из обоих массивов и повторно сравнивает два массива.
Структура таблицы в базе данных
А теперь пора немного вернуться назад и обсудить структуру таблицы, с помощью которой база данных сохраняет порядок измененных id.
Секрет состоит в типе данных DOUBLE для поля с порядком сортировки ItemIndex.
Вместо того чтобы перезаписывать все записи в таблице и обновлять их индексы для отображения нового порядка, мы просто можем изменить ItemIndex на дробную величину между предыдущим и следующим значением ItemIndex.
Например, если мы перемещаем ItemIndex 8 между ItemIndexes 3 и 4 мы просто обновляем индекс перемещенного объекта на 3.5.
Таким образом при сортировке по id , восьмой элемент будет отражаться ровно между третьим и четвертым. Это позволит сохранить порядок элементов всего одним запросом к базе.
При такой конфигурации таблицы всего лишь остается посчитать разницу между следующим и предыдущим ItemIndexes при обработке операций произведенной jQuery UI sortable с перемещением элементов в начало списка или в конец списка:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | if ($previousKey === 0) { $itemIndex = (double)((double)(reset( $sortList )->getItemIndex() / 2)); } else if ($nextKey === INF) { $itemIndex = (double)((double)(end( $sortList )->getItemIndex() + 10)); } else { $lowIndex = (double)$sortList[(int)$previousKey]->getItemIndex(); $highIndex = (double)$sortList[(int)$nextKey]->getItemIndex(); $itemIndex = (double)($lowIndex + ($highIndex - $lowIndex) / 2); } |
У данного метода есть один серьезный минус — мы можем потерять возможность сохранения индексов при многочисленном делении из-за нехватки точности вещественного числа.
На самом деле мы можем повторно разделить величину типа DOUBLE более тысячи раз, пока у не закончатся уточнения. На практике, возможно, этого не случится. Чтобы обезопасить себя от потери точности, необходимо время от времени обновлять все индексы в таблице, например с помощью cron задач или вручную
Данный метод был встречен мной на одном из буржуйских блогов. В статью я его включил для расширения кругозора, и потому что к нему имеются исходники. Лично мне больше по душе другой вариант сохранения сортировки для jQuery UI sortable, который я разработал самостоятельно и использую его в своем движке moguta.cms.
Второй пример сохранения jQuery UI sortable
Чтобы поменять порядок элементов, нам необходимо иметь в таблице поле sort с целочисленным типом INT, в отличии, от предыдущего примера где для аналогичного поля мы использовали тип DOUBLE. Как вы понимаете нам придется менять местами значения сортировки записей между друг другом.
Например, если в элементах 1,2,3,4,5 мы меняем местами два соседних элемента 3 и 2, это значит, что в одной строке таблицы произойдет замена индекса с 2 на 3, а в другой соответственно с 3 на 2. В этом случае в следующий раз при сортировке по полю sort нам будет выведена уже последовательность 1,3,2,4,5
Сложность заключается в вычислении всей цепочки обменов при перемещении, например, 4 элемента на место перед 2–ым. Вот так 1,4,2,3,5.
Для выявления цепочки перемещения мною был разработан следующий алгоритм. Используя штатные методы jQuery UI sortable такие как start() и update() мы можем обработать два события – начало сортировки и ее завершение, когда элемент отпущен и установлен на новую позицию.
Алгоритм сохранения сортировки jQuery UI sortable:
- На старте перед началом перемещения запоминаем последовательность id-шников элементов;
- Удаляем индекс перемещаемого элемента из последовательности;
- На место удаленного id записываем маркер ‘start’;
- После перемещения запоминаем новую последовательность id-шников;
- Удаляем индекс перемещаемого элемента из последовательности;
- На место удаленного id записываем маркер ‘end’;
- В итоге получаем два массива [«1», «2», «3», «start», «5»] и [«1», «end», «2», «3», «5»]
- Проходя в направлении от метки ‘start’ к метке ‘end’, отсекаем незатронутые элементы последовательностей.
- Получаем цепочку для последовательного обмена порядковых номеров [«3″,»2″,»4»]
- Передаем на сервер данный массив, где методом обработки производим последовательную перезапись порядковых номеров для строк в таблице базы данных, в соответствии с присланной AJAX запросом цепочкой перемещения элемента.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | /** * Разрешает менять местами строки таблицы, для сортировки элементов * tableSelector - селектор объекта , таблица в которой будет доступна сортировка строк * tablename - название таблицы в базе данных (обязательно должна иметь поля id и sort) * у строк таблицы обязательно должен быть атрибут data-id * @returns {undefined} */ sortable: function(tableSelector,tablename) { /** * создает массив позиций с заданной меткой на месте перемещаемого элемента * используется для сохранения стартовой последовательности и финальной */ function createArray(ui, marker) { var strItems = []; // пробегаем по таблице сохраняя последовательность id строк // если встречаем id = id перемещаемого элемента то на это место добавляем маркер $(tableSelector).children().each(function (i) { var tr = $(this); if(tr.data("id")==ui.item.data("id")){ strItems.push(marker); }else{ if(tr.data("id")!=undefined){ strItems.push(tr.data("id")); } } }); return strItems; } var listIdStart = []; var listIdEnd = []; if($(tableSelector).hasClass('ui-sortable')){ $(tableSelector).sortable('destroy'); $(tableSelector).unbind(); } $(tableSelector).sortable({ // на старте перед началом перемещения запоминаем последовательность id start: function (event, ui) { listIdStart = createArray(ui,'start'); }, // после перемещения запоминаемновую последовательность id update: function (event, ui) { listIdEnd = createArray(ui,'end'); var $thisId = ui.item.data("id"); //Вычисляет последовательность замены порядковых индексов var sequence = getSequenceSort(listIdStart,listIdEnd,$thisId); if(sequence.length>0){ sequence = sequence.join(); admin.ajaxRequest({ mguniqueurl:"action/changeSortRow", //метод обработки на сервере switchId: $thisId, sequence: sequence, tablename: tablename, }, function(response) { admin.indication(response.status, response.msg) } ); } } }); /** * Вычисляет последовательность замены порядковых индексов * Получает два массива * ["1", "start", "9", "2", "10"] * ["1", "9", "2", "end", "10"] * и ID перемещенной категории */ function getSequenceSort(arr1, arr2, id) { var startPos = ''; var endPos = ''; // вычисляем стартовую позицию элемента arr1.forEach(function(element, index, array) { if(element=="start"){ startPos=index; arr1[index]=id; return false; } }); // вычисляем конечную позицию элемента arr2.forEach(function(element, index, array) { if(element=="end"){ endPos=index; arr2[index]=id; return false; } }); // вычисляем индексы которым и надо поменяться местами var result = []; // направление переноса, сверху вниз if(endPos>startPos){ arr1.forEach(function(element, index, array) { if(index>startPos && index<=endPos){ result.push(element); } }); } // направление переноса, снизу вверх if(endPos<startPos){ arr2.forEach(function(element, index, array) { if(index>endPos && index<=startPos){ result.unshift(element); } }); } return result; }; }, |
Такой подход как я упоминал выше применен в движке для интернет-магазинов Moguta.CMS при сортировки товаров. Вот вырезка javascript функции реализующей данный алгоритм:
Плюсы очевидны база всегда в актуальном состоянии, а все расчеты происходят на компьютере клиента.
На первый взгляд увеличение числа запросов пропорционально количеству перестановок.
На практике если сравнивать данный способ с первым, обновление по крону будет производить постоянно количество запросов столько же, но каждый день.
Надеюсь, статья о jQuery UI sortable будет полезной вам, и вы сможете выбрать для себя подходящий способ сохранения порядка перемещаемых элементов и вопрос как узнать новую позицию элементов вас больше не потревожит.
Было бы интересно послушать ваши комментарии относительно одного и другого приведенных методов сохранения сортировки.
Читайте также похожие статьи:
Чтобы не пропустить публикацию следующей статьи подписывайтесь на рассылку по E-mail или RSS ленту блога.
Комментарии
Ваш пример (второй) не рабочий, ошибки,ошибки. Куда вставлять этот код? Что за объект admin здесь admin.ajaxRequest( ….
Что это вообще ajaxRequest ? за аякс…. Не знаю может я чего-то не понимаю.
Вместо скриншота можно было и живой пример поместить, а так, извините, лажа получается 🙂
Псевдокритикам поясню: данная статья рассчитана строго на тех, кто хоть как то разбирается в JavaScript языке и знаком с Sortable из фрэйма Jquery-UI. А так, могу лишь посоветовать поискать замечательные плагины, модули, дополнения для Ваших CMS
А нельзя сделать как-то так:
1. Из базы получаем порядковый номер каждого элемента (по нему же упорядочиваем);
2. Сохраняем номер в отдельном аттрибуте элемента;
3. При перемещении получаем id и порядковый номер элемента, который подняли и элемента на который «уронили» поднятый;
4. Отправляем запрос на сервер: заменить порядковый номер элемента с id №1 на переданный, заменить порядковый номер элемента с id №2 на переданный.