Как стать автором
Обновить

Sortable v1.0: Новые возможности

Время на прочтение 6 мин
Количество просмотров 28K
Привет хабр! В преддверии нового года хочу поделится своей радостью — выходом Sortable v1.0. Ровно год назад я представил на ваш суд мой маленький инструмент для сортировки списка при помощи drag’n’drop. Всё это время я скрупулезно собирал обратную связь, добавлял новые возможности и правил мелкие баги. Под катом я расскажу о новых возможностях, интеграции с AngularJS, Meteor и других нюансах.


Введение


Sortable — это минималистичный инструмент, для организации сортировки внутри списка и между списками. Библиотека не зависит от jQuery или других решений, использует Native Drag’n’Drop API, работает как на desktop, так и touch устройствах. Имеет простое API, легко интегрируется в проект и является отличной заменой jQueryUI / Sortable ;]

И так, что же нового принес этот релиз:

  • Продвинутые группы (гибкая настройка перемещений и не только)
  • Анимация при перемещении
  • Умная прокрутка окна браузера и списка
  • Отключение сортировки (позволяет эмитировать draggable и droppable)
  • Методы для получения и изменения сортировки
  • Возможность фильтрации
  • Поддержка AngularJS и Meteor


Продвинутые группы


С самого начала библиотека имела возможность перемещения между группами, достаточно было задать им одинаковое имя и готово:
// foo и bar — ссылки на HTMLElement
Sortable.create(foo, { group: 'shared', });
Sortable.create(bar, { group: 'shared' });
http://jsbin.com/yexine/1/edit
Пример работы (gif)
image

Со временем, стало очевидно, что этого недостаточно. Например, нельзя было сделать так, чтобы один список только отдавал, а другой только принимал элементы. Помимо этого, ключевым недостатком было отсутствие возможности как-то организовать взаимодействие нескольких групп. Нужно было выработать решение, которое бы позволило реализовать возникшие задачи и оставить возможность расширения на будущее, при этом не потерять текущий интерфейс.

Теперь можно задать опцию `group` как объект со следующими свойствами:
  • name — название группы;
  • pull — возможность «вытаскивать» элементы при перемещении между списками, так же свойство может принимать значение `clone`;
  • put — возможность принять элемент из другой группы, либо массив разрешенных групп.

Как это работает проще объяснить этом на примере:
  • У вас есть три списка «A», «B» и «C»;
  • Перетаскивать нужно из «A» и «B» в «C», между «A» и «B» перенос невозможен;
  • При перетаскивании из «A» на его месте должен появляться «клон».

Чтобы показать сразу все возможности, я решу эту задачу двумя способами.
Общая группа Несколько групп
http://jsbin.com/yexine/2/edit http://jsbin.com/yexine/8/edit
Sortable.create(listA, {
  group: {
    name: 'shared',
    pull: 'clone',
    put: false
  }
});

Sortable.create(listB, {
  group: {
    name: 'shared',
    put: false
  }
});

Sortable.create(listC, {
  group: 'shared'
});
Sortable.create(listA, {
  group: {
    name: 'A',
    pull: 'clone'
  }
});

Sortable.create(listB, {
  group: 'B'
});

Sortable.create(listC, {
  group: {
    put: ['A', 'B']
  }
});

Анимация


Тут особо расписывать нечего, анимация сделана очень просто, при помощи CSS3 transition, включить её можно задав опцию `animation` в `ms`. Увы, у неё есть недостатки, которые пока не решены, но надеюсь в будущем найдется способ, как «дешево» их исправить.
http://jsbin.com/yexine/4/edit
Пример работы (gif)


Умная прокрутка окна и списка


Совсем недавно возникла задача: сделать прокрутку окна при достижении одного из краев. По идее, это должно было работать по умолчанию, т.к. используется Native Drag’n’Drop API, но на деле браузер крайне не охотно прокручивал окно. Так же оставалась проблема, если список находится в overflow. Поэтому подумав, получилось сделать умную прокрутку, которая сначала прокручивала список, если он в overflow и/или окно если мы достигли края браузера. Для более тонкой настройки введены три дополнительные опции:
  • scroll — включить авто-прокрутку;
  • scrollSensitivity — на сколько нужно приблизится к краю для активации прокрутки;
  • scrollSpeed — скорость прокрутки в `px`;

Примеры:

Отключение сортировки


Да, именно так, это может показаться странным, но при помощи этого параметра можно отключить то, ради чего и создан данный инструмент ;] Например это можно использовать для имитации draggable и droppable: http://jsbin.com/xizeh/3/edit?html,js,output


Методы для получения и изменения сортировки


Так как эта библиотека появилась в результате исследования возможностей Drag’n’Drop API, то банальные методы получить порядок или изменить его просто не закладывались. Заглянув в API jQueryUI, обнаружил, что у них можно только получить порядок элементов, но изменить его нельзя, это не порядок ;] Чтобы решить все эти проблемы, было добавлено свойство `store`, которое принимает объект из двух параметров `get` и `set` для получения и сохранения сортировки, а так же два метода `toArray` и `sort`.

Например, сохранение порядка через `localStorage` будет выглядеть следующим образом:
Sortable.create(users, {
  store: {
    // Получение сортировки (вызывается при инициализации)
    get: function (sortable) {
      var order = localStorage.getItem(sortable.options.group);
      return order ? order.split('|') : [];
    },

    // Сохранение сортировки (вызывается каждый раз при её изменении)
    set: function (sortable) {
      var order = sortable.toArray();
      localStorage.setItem(sortable.options.group, order.join('|'));
    }
  }
});

http://jsbin.com/yexine/7/edit (измените порядок и обновите страницу)


Возможность фильтрации


Допустим вам нужно сделать сортируемый список с возможностью редактирования и удаления элемента. Раньше вам бы понадобилась самостоятельно навесить нужные обработчики. Теперь, решить подобную задачу можно средствами самой библиотеки, без дополнительных инструментов: http://jsbin.com/yexine/6/edit?html,js,output


Поддержка AngularJS


Angular всё больше завоевывает рынок и чтобы облегчить людям использование Sortable, было решено сделать директиву, для быстрой интеграции в проект. Посмотрев на аналоги, я увидел странность, все как один делают так:
<ul ui-sortable="sortableOptions" ng-model="items">
    <li ng-repeat="item in items">{{ item }}</li>
</ul>

Зачем? Ведь это попросту copy-paste, да и если быть совсем честным, костыль. На мой взгляд, логичной и правильной будет следующая запись, без `ng-model`:
<ul ng-sortable="sortableOptions">
    <li ng-repeat="item in items">{{ item }}</li>
</ul>

Интересующие нас данные уже содержит `ng-repeat`, чтобы их получить нам понадобиться функция `$parse` и небольшая хитрость. Хитрость заключается в том, что данные из `ng-repeat` можно получить только найдя спец. комментарий оставленный самим ангуляром:
<ul ng-sortable="{ animation: 150 }">
       <!-- ngRepeat: item in items -->
       <!-- end ngRepeat: item in items -->
</ul>

Теперь мы можем создать метод для работы с данными связанными с `ng-repeat`:
/**
 * Получить объект для работы с данными в ng-repeat
 * @param {HTMLElement}  el
 * @returns {object}
 */
function getNgRepeat(el) {
  // Получаем текущий `scope` связанный в элементом
  var scope = angular.element(el).scope();
  
  // Находим нужный нам комментарий
  var ngRepeat = [].filter.call(el.childNodes, function (node) {
     return (
           (node.nodeType === 8) &&
           (node.nodeValue.indexOf('ngRepeat:') !== -1)
        );
  })[0];

  // Прасим название переменных элемента и массива
  ngRepeat = ngRepeat.nodeValue.match(/ngRepeat:\s*([^\s]+)\s+in\s+([^\s|]+)/);

  // Конвертируем названия переменных в `expression` для получения их значений из `scope`
  var itemExpr = $parse(ngRepeat[1]);
  var itemsExpr = $parse(ngRepeat[2]);

  return {
     // Получить модель элемента списка
     item: function (el) {
        return itemExpr(angular.element(el).scope());
     },
     // Получить массив связанный с `ng-repeat`
     items: function () {
        return itemsExpr(scope);
     }
  };
}

Пример работы: http://jsbin.com/fumote/1/edit
Полный код директивы: https://github.com/RubaXa/Sortable/blob/master/ng-sortable.js


Интеграция с Meteor


Это совсем новая возможность, которая появилось благодаря Dan Dascalescu, так что если вы используете meteor, то библиотека добавлена в атмосферу, а Dan добавил подробный мануал по использованию и пример. Если что, ставьте задачу с меткой meteor на него, он будет рад помочь ;]


В завершении хочу сказать спасибо всем тем, кто принимал участие в тестирование и развитии библиотеки, хоть она немного и «потолстела», но это все тот же легкий и гибкий интрумент. Спасибо за внимание.

Планы на будущее


  • Покрытие тестами (пока не до конца понятно как покрыть Drag'n'Drop, но идеи есть)
  • Улучшенная анимация
  • Система расширений (например вложенные списки или объединение двух элементов в один)
  • Ограничение по осям (увы, возможно придется отказаться от Drag’n’Drop API)
  • Ваш вариант ;]


               Примеры   &nbsp |   &nbspКод и документация   &nbsp |   &nbspМой github   &nbsp |   &nbsp@ibnRubaXa


Теги:
Хабы:
+28
Комментарии 24
Комментарии Комментарии 24

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн