Jednym z podstawowych zastosowań Angulara jest tworzenie rozbudowanych one page sites, czyli aplikacji internetowych opartych na jednej stronie, bez konieczności przeładowywania widoku, także w przypadku użycia nawigacji. Aby to zrealizować Angular posiada szereg funkcji umożliwiających manipulowanie wyświetlania pojedynczych elementów interfejsu użytkownika. I to właśnie tego typu dyrektywom chciałbym poświęcić szósty odcinek poradnika do Angulara JS.
Ewentualna zmiana w naszym widoku może być zainicjowana w dwojaki sposób. Pierwszym z nich jest czynność wykonana przez użytkownika, wykonanie określonej operacji za pomocą interfejsu użytkownika. Taka akcja powoduje, iż uruchamiana jest obsługa wykrytego zdarzenia, którą to bezpośrednio definiuje programista za pomocą odpowiedniej klasy bądź atrybutu:
- ng-blur – zdjęcie focusa z elementu
- ng-change – obsługa zmiany w elemencie
- ng-click – kliknięcie myszą na elemencie (następuje po mousedown, mouseup)
- ng-copy – wykonanie kopiowania
- ng-paste – obsługa wklejania
- ng-cut – wycinanie
- ng-dblclick – obsługa zdarzenia podwójnego kliku
- ng-focus – obsługa zdarzenia ustawienia focusu na danym elemencie
- ng-keydown – wciśnięcie klawisza
- ng-keypress – wciśnięcie klawisza
- ng-keyup – puszczenie klawisza
- ng-mousedown – wcisnięcie klawisza myszy
- ng-mouseup – puszczenie klawisza myszy
- ng-mouseenter – najechanie myszą na element
- ng-mouseleave- opuszczenie elementu
- ng-mousemove – wykonanie ruchu myszą
- ng-mouseover – najechanie myszą na element (następuje po mouseenter)
- ng-submit – zdarzenie przesłania formularza
Wszystkie powyższe zdarzenia służą do wywoływania wcześniej ustalonych funkcji. Innym sposobem definiowania wyglądu elementów są dyrektywy przedstawione poniżej:
- ng-if – dołącza element do drzewa DOM jedynie w momencie spełnienia określonego kryterium
- ng-class – przypisuje wybranemu elementowi drzewa odpowiednią klasę
- ng-class-even – przypisuje daną klasę, jednak jedynie parzystym elementom stworzonych za pomocą ng-repeat
- ng-class-odd – przypisuje daną klasę, jednak jedynie nieparzystym elementom stworzonych za pomocą ng-repeat
- ng-hide – ukrywa dany element, pod warunkiem spełnienia określonego kryterium
- ng-show – pokazujedany element, pod warunkiem spełnienia określonego kryterium
- ng-style – przypisuje konkretne style do opisywanego elementu HTML
Najlepsze wyniki daje połączenie obydwu powyższych metod. Spróbujmy więc pokazać, jak je używać, wykorzystując przy tym kod z poprzedniego wpisu. Startujemy z takiego miejsca:
Z tak zdefiniowanym widokiem:
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 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular-resource.js"></script> <script src="../js/postsController.js"></script> <script src="../js/posts.js"></script> <html ng-app="PostsApp"> <div ng-controller="PostsController as vm"> {{ vm.choosenPost.id}} {{vm.choosenPost.body }} <div style="width:50%;float:left"> <h1>Posts from API Service NG Resource</h1> <ul > <li ng-repeat="x in vm.postsfromapiservice.items"> <button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body }} </li> </ul> </div> <div style="width:50%;float:left"> <h1>Users from API Service</h1> <ul > <li ng-repeat="x in vm.usersfromapiservice.items"> {{ x.id}} | {{x.email }} </li> </ul> </div> </div> </html> |
Widzimy, iż akcja dotyczące wybrania przycisku opisana zostały za pomocą ngclick, która to uruchamia funkcję choose z parametrem równym identyfikatorowi danego rekordu. Spróbujmy nieco zmodyfikować nasz kod. Dodajmy dwa przyciski, które zależnie od wymagań użytkownika, będą wyświetlały listę postów lub użytkowników, prześledźmy zmodyfikowany kod widoku:
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 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular-resource.js"></script> <script src="../js/postsController.js"></script> <script src="../js/posts.js"></script> <html ng-app="PostsApp"> <div ng-controller="PostsController as vm"> <button ng-click="vm.toggleUsers()" >Toggle Users</button> <button ng-click="vm.togglePosts()" >Toggle Posts</button> {{ vm.choosenPost.id}} {{vm.choosenPost.body }} <div style="clear:both"></div> <div ng-show="vm.posts" style="float:left;width:40%; margin:10px;"> <h1>Posts</h1> <ul > <li ng-repeat="x in vm.postsfromapiservice.items" > <button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body }} </li> </ul> </div> <div ng-show="vm.users" style="float:left;width:40%; margin:10px;"> <h1>Users</h1> <ul > <li ng-repeat="x in vm.usersfromapiservice.items" > {{ x.id}} | {{x.email }} </li> </ul> </div> </div> </html> |
W linii 12 i 13 dodaliśmy dwa przyciski, których akcje zostały ustawione na:
- toggleUsers() – do wyświetlania i ukrywania listy z użytkownikami
- togglePosts() – do wyświetlania i ukrywania listy z postami
Dodatkowo nasze dwa panele z postami i użytkownikami zaktualizowaliśmy nowymi dyrektywami:
- ng-show=”vm.posts” – oznacza, iż cała warstwa będzie wyświetlona wtedy i tylko wtedy, gdy zmienna posts będzie zawierała wartość true
- ng-show=”vm.users” – oznacza, iż cała warstwa będzie wyświetlana wtedy i tylko wtedy, gdy zmienna users będzie zawierała wartość false
Jak się nietrudno domyślić, za wypełnienie zmiennych danymi będą stały odpowiadające funkcje zdefiniowane w linijkach 12 i 13, a opisane w kontrolerze w liniach 33-47:
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 |
var dependencies = [ 'posts' ]; angular.module('PostsApp', dependencies) .controller('PostsController', function($http,posts,users,$scope) { var vm = this; vm.users = false; vm.posts = false; vm.postsfromapiservice = {}; vm.usersfromapiservice = {}; vm.choosenPost = {}; posts.findall({}, function(data) { vm.postsfromapiservice.items = data; }); users.findall({}, function(data) { vm.usersfromapiservice.items = data; }); vm.choose = function(id) { posts.find({id: id}, function(data) { vm.choosenPost = data; }); }; vm.toggleUsers = function() { if(!vm.users) vm.users = true; else vm.users = false; }; vm.togglePosts = function() { if(!vm.posts) vm.posts = true; else vm.posts = false; }; }); |
Jako, że dane postów i użytkowników pobierane są bezpośrednio przy renderowaniu strony, wystarczy, iż w naszych funkcjach użyjemy zwykłych przełączników, pomiędzy prawdą fałszem.
W tej chwili nasz system wygląda następująco:
Nasza funkcjonalność wyboru pojedynczego postu (przycisk Choose) oczywiście zachowała swoje przeznaczenie i działa całkowicie poprawnie, aczkolwiek spróbujmy dodać następujące udoskonalenia:
- Wybór postu numer 5 wyświetli dodatkowy komunikat – ten post jest wyjątkowy! – użyjemy do tego dyrektywy ng-if
- Spróbujemy sprawić, by lista wyświetlała się w dwóch kolorach
- Opuszczenie myszki warstwy z postami będzie skutkowało wyczyszczeniem pola z wybranym postem.
Zacznijmy od naszego wyjątkowe postu. W tym celu stworzymy nowy element HTML w naszym widoku i za pomocą dyrektywy ng-if sprawdzimy, czy wybrany identyfikator postu ma wartość równą 5, poniżej, który:
- sprawdza czy którykolwiek z postów został wybrany, w tym celu użyta zostaje dyrektywa ng-show, od której zależy czy zostanie pokazany komunikat ‘Wybrany post: (id i tytul postu)’
- Druga warstwa używa już natomiast dyrektywy ng-if, która sprawdza czy wartość przypisanej zmiennej id równa jest 5, w przypadku prawdy, wyświetlany jest komunikat, iż ‘Ten post jest szczegolnie wazny’.
1 2 3 4 5 6 7 8 9 10 |
<div ng-show="vm.choosenPost.id"> Wybrany post: {{ vm.choosenPost.id}} | {{vm.choosenPost.body }} </div> <div ng-if="vm.choosenPost.id == 5"> Ten post jest szczegolnie wazny: {{ vm.choosenPost.id}} | {{vm.choosenPost.body }} </div> |
Szybkie sprawdzenie i faktycznie, nasz kod działa jak należy, wybranie z listy postu o ID = 5 skutkuje wyświetleniem stosownego komunikatu.
Ten etap mamy z głowy, przechodzimy do kolorowania. W tym celu definiujemy dwa style, jeden dla wiersza parzystego, drugi, a jakże, dla nieparzystego:
1 2 3 4 5 6 7 8 9 10 11 |
<style> .parzysty{ background-color:#CDCEED; } .nieparzysty{ background-color:#FBFBFF; } .wybrany{ background-color:#FD9F43; } </style> |
Nasze style musimy teraz dopisać do odpowiednich rekordów. W tym celu użyjemy wspomnianej już wyżej dyrektywy ng-class-odd i ng-class-even:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<div ng-show="vm.posts" style="float:left;width:40%; margin:10px;"> <h1>Posts</h1> <ul > <li ng-repeat="x in vm.postsfromapiservice.items" ng-class-odd="'nieparzysty'" ng-class-even="'parzysty'"> <button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body}} </li> </ul> </div> <div ng-show="vm.users" style="float:left;width:40%; margin:10px;"> <h1>Users</h1> <ul > <li ng-repeat="x in vm.usersfromapiservice.items" ng-class-odd="'nieparzysty'" ng-class-even="'parzysty'"> {{ x.id}} | {{x.email }} </li> </ul> </div> |
Szybkie sprawdzenie – i faktycznie, nasz kod zmienia cyklicznie kolor tła rekordów:
Ostatni etap prac to czyszczenie wybranego postu po wyjechaniu myszą poza obszar listy postów. To także nie przysporzy problemów, użyjemy dyrektywy ng-mouseleave na warstwie z listą postów. Musimy również zmodyfikować nasz kontroler i dodać akcję czyszczenia, nasz widok:
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 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular-resource.js"></script> <script src="../js/postsController.js"></script> <script src="../js/posts.js"></script> <style> .parzysty{ background-color:#CDCEED; } .nieparzysty{ background-color:#FBFBFF; } .wybrany{ background-color:#FD9F43; } </style> <html ng-app="PostsApp"> <div ng-controller="PostsController as vm"> <button ng-click="vm.toggleUsers()" >Toggle Users</button> <button ng-click="vm.togglePosts()" >Toggle Posts</button> <div ng-show="vm.choosenPost.id"> Wybrany post: {{ vm.choosenPost.id}} | {{vm.choosenPost.body }} </div> <div ng-if="vm.choosenPost.id == 5"> Ten post jest szczegolnie wazny: {{ vm.choosenPost.id}} | {{vm.choosenPost.body }} </div> <div style="clear:both"></div> <div <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular-resource.js"></script> <script src="../js/postsController.js"></script> <script src="../js/posts.js"></script> <style> .parzysty{ background-color:#CDCEED; } .nieparzysty{ background-color:#FBFBFF; } .wybrany{ background-color:#FD9F43; } </style> <html ng-app="PostsApp"> <div ng-controller="PostsController as vm"> <button ng-click="vm.toggleUsers()" >Toggle Users</button> <button ng-click="vm.togglePosts()" >Toggle Posts</button> <div ng-show="vm.choosenPost.id"> Wybrany post: {{ vm.choosenPost.id}} | {{vm.choosenPost.body }} </div> <div ng-if="vm.choosenPost.id == 5"> Ten post jest szczegolnie wazny: {{ vm.choosenPost.id}} | {{vm.choosenPost.body }} </div> <div style="clear:both"></div> <div ng-mouseleave="vm.disablePosts()" ng-show="vm.posts" style="float:left;width:40%; margin:10px;"> <h1>Posts</h1> <ul > <li ng-repeat="x in vm.postsfromapiservice.items" ng-class-odd="'nieparzysty'" ng-class-even="'parzysty'"> <button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body}} </li> </ul> </div> <div ng-mouseleave="vm.disableUsers()" ng-show="vm.users" style="float:left;width:40%; margin:10px;"> <h1>Users</h1> <ul > <li ng-repeat="x in vm.usersfromapiservice.items" ng-class-odd="'nieparzysty'" ng-class-even="'parzysty'"> {{ x.id}} | {{x.email }} </li> </ul> </div> </div> </html> ng-show="vm.posts" style="float:left;width:40%; margin:10px;"> <h1>Posts</h1> <ul > <li ng-repeat="x in vm.postsfromapiservice.items" ng-class-odd="'nieparzysty'" ng-class-even="'parzysty'"> <button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body}} </li> </ul> </div> <div ng-mouseleave="vm.disableUsers()" ng-show="vm.users" style="float:left;width:40%; margin:10px;"> <h1>Users</h1> <ul > <li ng-repeat="x in vm.usersfromapiservice.items" ng-class-odd="'nieparzysty'" ng-class-even="'parzysty'"> {{ x.id}} | {{x.email }} </li> </ul> </div> </div> </html> |
Widzimy, iż praktycznie jedyna zmiana to dodanie:
1 |
ng-mouseleave="vm.disableUsers()" |
i
1 |
ng-mouseleave="vm.disablePosts()" |
czyli uruchomienie funkcji disableUsers() i disablePosts(), w momencie gdy użytkownik usunie mysz z wybranego diva.
Kod kontrolera, w którym zawiera się funkcja usuwająca znajduje się w poniższym listingu:
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 |
var dependencies = [ 'posts' ]; angular.module('PostsApp', dependencies) .controller('PostsController', function($http,posts,users,$scope) { var vm = this; vm.users = false; vm.posts = false; vm.postsfromapiservice = {}; vm.usersfromapiservice = {}; vm.choosenPost = {}; posts.findall({}, function(data) { vm.postsfromapiservice.items = data; }); users.findall({}, function(data) { vm.usersfromapiservice.items = data; }); vm.disablePosts = function() { vm.choosenPost = false; }; vm.disableUsers = function() { vm.choosenPost = false; }; vm.choose = function(id) { posts.find({id: id}, function(data) { vm.choosenPost = data; }); }; vm.toggleUsers = function() { if(!vm.users) vm.users = true; else vm.users = false; }; vm.togglePosts = function() { if(!vm.posts) vm.posts = true; else vm.posts = false; }; }); |
Tym razem zmiana polegała na dodaniu:
1 2 3 4 5 6 7 8 |
vm.disablePosts = function() { vm.choosenPost = false; }; vm.disableUsers = function() { vm.choosenPost = false; }; |
Dzięki temu powinniśmy osiągnąć pożądany efekt.
Mimo, iż pokazane w powyższym wpisie programy są wybitnie trywialne, pozwoliły na pokazanie przykładowego użycia sposobów modyfikacji elementów HTML, czyli tak naprawdę modyfikacji elementów interfejsu użytkownika.