Chcemy, aby z naszej aplikacji korzystało się jak najszybciej i jak najwygodniej. Dzięki temu poprawiamy produktywność i wpływamy na pozytywne doświadczenia użytkownika podczas korzystania z aplikacji (ang. User experience, UX).
Możemy do aplikacji dodać mechanizm przeciągnij i upuść (ang. drag and drop). Zastosujemy go w aplikacji AgendaEditor, aby można było łatwo zmieniać kolejność wystąpień. Oczywiście moglibyśmy dodać przyciski, po których kliknięciu, wystąpienie przesuwałoby się w górę lub w dół. Ale będzie to wyglądało lepiej, jeżeli będziemy mogli je po prostu przeciągnąć.
Biblioteki drag & drop
Możemy w JavaScript sami zaimplementować taką funkcjonalność. Ale po co wymyślać koło na nowo. Ktoś już to dawno zrobił, dopracował i udostępnił, aby inni mogli skorzystać. Jeżeli kogoś by to jednak zainteresowało, to może poczytać o tym np. tutaj.
Znalazłem fajną bibliotekę dla AngularJS: angular-drag-and-drop-lists (demo). Ma wiele możliwości, dobre przykłady użycia i jest łatwa w użyciu. Dokładnie to czego potrzebuję. Jednak ma jedną, dużą wadę. Domyślnie nie działa na urządzeniach mobilnych (testowałem na Androidzie i rzeczywiście nie działa):
Touch devices are not supported, because they do not implement the HTML5 drag & drop standard. However, you can use a shim to make it work on touch devices as well.
Oczywiście jest więcej bibliotek np.
- jQuery UI Sortable for AngularJS (demo). Również dosyć ciekawa.
- Drag and Drop for AngularJS (demo)
- Native AngularJs drag and drop directive (demo, opis użycia)
- ngRepeatReorder (demo, opis użycia)
- ngDraggable (demo)
- Adapt-Strap (musimy przewinąć do sekcji Drag and drop, demo)
A tutaj można znaleźć spis jeszcze innych bibliotek dla drag&drop: Best AngularJs Drag & Drop Directives Plugins.
Jest jeszcze jedna biblioteka: Dragula, którą wykorzystam. Jej zaletą jest to, że działa na urządzeniach mobilnych (testowałem na Androidzie). Jest dosyć nowa i świeża, ale działa prawidłowo.
Dziękuję mik-laj z GitHuba za podpowiedź odnośnie biblioteki Dragula, bo tej jeszcze nie znałem, a jest dosyć dobra (swoją drogą dostałem pierwszy pull request do projektu; zajmę się nim po zakończeniu konkursu).
Dragula
Dostępna jest w wersji JavaScript: https://github.com/bevacqua/dragula
Ale dostępny jest takze wrapper pod AngularJS (wersja przystosowana, żeby można było jej łatwiej użyć w Angularze): https://github.com/bevacqua/angular-dragula/
Demo można znaleźć tutaj: http://bevacqua.github.io/angular-dragula/
Dodanie Draguli do projektu
Aby dodać pliki biblioteki do naszej aplikacji, możemy je ściągnąć z GitHuba z katalogu dist. A jeśli chcemy dodać link do zewnętrznego serwera, możemy skorzystać z serwerów CDN. Musimy dodać plik angular-dragula.js
oraz dragula.css
. Do wyboru mamy wersję zwykłą i zminifikowaną (z końcówką min
).
W pliku index.html w sekcji <head> doklejamy linki do biblioteki:
1 2 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-dragula/1.2.7/dragula.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-dragula/1.2.7/angular-dragula.min.js"></script> |
Oczywiście musimy pamiętać o odpowiedniej kolejności dodawania plików. Najpierw dodajmy pliki CSS, następnie pliki JavaScript: jQuery, Bootstrapa, Angulara, tutaj bibliotekę do Angulara i na końcu pliki z naszymi kontrolerami.
Konfiguracja Draguli
W pliku konfiguracyjnym naszej aplikacji app.js mamy linijkę definiującą aplikację i moduły wstrzyknięte do niej:
1 |
var app = angular.module('agendaEditor', ['ngRoute']); |
Musimy dodać zależność: angularDragula(angular)
. Oczywiście poprzednie zależności (w nawiasach kwadratowych) jakie mamy zostawiamy, tylko po przecinku dodajemy kolejną. Będzie to wyglądać tak:
1 |
var app = angular.module('agendaEditor', ['ngRoute', angularDragula(angular)]); |
Teraz możemy już skorzystać z Draguli.
Modyfikacja szablonu HTML
Skorzystamy z udostępnionych dyrektyw, aby uruchomić Dragulę. Zadanie mamy ułatwione, gdyż dobrze zaprojektowaliśmy naszą aplikację. Dane wystąpień są dostępne na liście agenda.list
(pobierana z naszego serwisu). Na niej będzie operować Dragula.
Mieliśmy taką tabelę w pliku home.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<table class="table"> <tr> <td class="col-sm-5">Title</td> <td class="col-sm-2">Name</td> <td class="col-sm-2">Surname</td> <td class="col-sm-2">Time</td> <td class="col-sm-1"></td> </tr> <tr ng-repeat="item in agenda.list" ng-click="$last && addEmptyItem()"> <td><input class="form-control" type="text" ng-model="item.title"></td> <td><input class="form-control" type="text" ng-model="item.name"></td> <td><input class="form-control" type="text" ng-model="item.surname"></td> <td><input class="form-control" type="text" ng-model="item.time"></td> <td><button ng-if="!$first" type="button" class="btn btn-danger btn-xs" ng-click="agenda.list.splice($index, 1)">-</button></td> </tr> </table> |
Musimy dodać dyrektywy:
dragula='"bag-one"'
, mówi nam, który element będzie miejscem do przeciągania elementów (moze być ich kilka, my jednak potrzebujemy jednego),dragula-model="agenda.list"
, podajemy na jakiej liście ma operować (przenosić w niej elementy),dragula-scope='$parent'
, ta dyrektywa jest potrzebna, żeby Angular odnalazł właściwy$scope
(bez tego upuszczanie elementów nie działa poprawnie, nie lądują w odpowiednim pojemniku). Umieszczamy ją w elemencie, który ma być przenoszony (czyli tym samym, w którym jestng-repeat
).
Te dyrektywy musimy dodać w elemencie nadrzędnym, które chce chcemy przeciągać (chcemy przeciągać wiersze <tr>
). Umieścilibyśmy dyrektywy Draguli w elemencie <table>, ale nie chcemy włączać przeciągania dla nagłówków. Dlatego dodamy kolejny element <tbody>
grupujący zawartość tabeli bez nagłówków (dla nich jest odpowiedni element <thead>
, który przy okazji możemy dodać).
Nasz tabela powinna teraz wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<table class="table"> <thead> <tr> <td class="col-sm-5">Title</td> <td class="col-sm-2">Name</td> <td class="col-sm-2">Surname</td> <td class="col-sm-2">Time</td> <td class="col-sm-1"></td> </tr> </thead> <tbody dragula='"bag-one"' dragula-model="agenda.list" dragula-scope='$parent'> <tr ng-repeat="item in agenda.list" ng-click="$last && addEmptyItem()"> <td><input class="form-control" type="text" ng-model="item.title"></td> <td><input class="form-control" type="text" ng-model="item.name"></td> <td><input class="form-control" type="text" ng-model="item.surname"></td> <td><input class="form-control" type="text" ng-model="item.time"></td> <td><button ng-if="!$first" type="button" class="btn btn-danger btn-xs" ng-click="agenda.list.splice($index, 1)">-</button></td> </tr> </tbody> </table> |
Aplikacja z drag & drop
Po dodaniu biblioteki Dragula, skonfigurowaniu i modyfikacji tabeli (dodaniu dyrektyw), przeciąganie i upuszczanie dla wystąpień działa. Zauważmy, że zmiany są wykonywane na bieżąco, a wynikowy tekst agendy jest modyfikowany od razu po przeciągnięciu (również godziny są obliczane). Jak widzimy nie było to trudne, a daje dobry efekt.
Podgląd działającej aplikacji (lub otwórz w nowej karcie lub edytor Plunker):
Pamiętajmy, że możemy wkleić przykładowe dane w zakładce import, aby móc szybko przetestować działanie:
Programowanie Jan Kowalski 00:20 AngularJS Anna Nowak 00:25 JavaScript Janusz Programista 00:15
Trzeba tylko dopisać godzinę rozpoczęcia np. 15:00
i możemy przeciągać wystąpienia.
Kod aplikacji, jak zawsze, dostępny na Githubie: https://github.com/mkczyk/agenda-editor
Świetny blog! Kopalnia wiedzy. Gratuluję