AgendaEditor: lista i formularz w AngularJS

W tym wpisie pokażę jak zrobić prosty formularz w AngularJS, który będzie podpięty do listy z danymi. Wartości z pól tekstowych będą przepisywane automatycznie w miejsce wynikowe poprzez listę.

Weźmy szablon aplikacji z wpisu, w którym był omówiony kontroler. Będzie to nasz punkt wyjściowy do rozbudowy aplikacji.

index.html

controller.js

Szablon formularza w tabeli w HTML

Chcemy mieć gdzie wpisywać dane (tytuł, imię, nazwisko itd.). Najlepiej jak będą to pola tekstowe <input>, tak jak w poprzednim przykładzie. Tych pól będzie kilka dla jednego rekordu (jednego wystąpienia), a wierszy będzie kilka. Są przynajmniej dwa sposoby jak moglibyśmy zorganizować te pola:

  • lista nieuporządkowana <ul> z elementami <li>,
  • tabela <table> z wierszami <tr> i komórkami <td>.

Na pierwszy rzut oka wydaje się, że powinniśmy użyć listy (biorąc pod uwagę dobrych kilka lat walki z wypieraniem tabelek ze stron). Jednak mamy tu dane tabelaryczne i dlatego spróbujemy użyć tu tabeli (dzięki temu będziemy mieli też ładne nagłówki <th> w pierwszym wierszu <tr>).

W pierwszym pliku dodajmy tabelę:

Jak widzicie, dla pól <input> została zastosowana dyrektywa, która binduje model (tzn. odpowiednie „zmienne”). To co zostanie wstawione do modelu, będzie wyświetlone w polach tekstowych. I to co zostanie wpisane w pola tekstowe, automatycznie będzie dostępne w modelu.

Pojawiła się tu także nowa dyrektywa ng-repeat. Najczęściej używa się jej w ten sposób: ng-repeat="element in listaElementow". Działa ona w ten sposób, że powtarza element HTML, w którym jest umieszczona, tyle razy, ile jest elementów w liście (tak jakby pętla for each znana z innych języków – przechodzi po każdym elemencie). Każdy wybrany element jest dostępny w nazwanej przez nas zmiennej item. Przyjąłem, że lista zawierała obiekty, więc możemy odwołać się do pól obiektu. I tak, możemy z modelu wybrać np. tytuł poprzez item.title oraz zbindować go do pola tekstowego.

Dzięki temu zostanie wyświetlonych tyle linii w tabeli (z potrzebnymi polami), ile jest elementów na liście. Ale potrzebujemy właśnie tą listę i to dostępną w $scope.

Lista w kontrolerze w JavaScript

W kontrolerze deklarujemy listę (a właściwie tablicę):

Jeśli teraz odwołamy się do jakiegoś elementu lub pola, będzie miało wartość undefined. Aby pola tekstowe domyślnie zawierały puste wartości, a nie brzydką wartość nieokreśloną, musimy dodać obiekty i zainicjalizować pola. Nowy obiekt tworzymy za pomocą nawiasów klamrowych {} a w środku wpisujemy po przecinku nazwę pola, przecinek i wartość.  Wygląda to tak: {title: "", name: "", surname: "", time: ""}.

My chcemy od razu dodać kilka obiektów na naszą listę, aby domyślnie wyświetliło kilka wierszy do wpisywania. Użyjemy do tego metody push na liście. Czyli dodanie obiektu do listy w $scope powinno wyglądać tak: $scope.list.push(obiekt).

Możemy od razu dodać kilka obiektów w pętli:

Teraz powinny wyświetlić się nam trzy wiersze z polami.

Funkcja tworząca wynikowe dane

Oczywiście chcemy jeszcze, aby wpisywane dane do pól tekstowych były przepisywane zgodnie z naszymi wymaganiami. W tym celu  zmodyfikujemy funkcję myResult() z początkowego kodu.

Chcemy otrzymać listę wyników. Dlatego zadeklarujmy tablicę var result = []. Następnie w pętli, przechodźmy po wszystkich elementach naszej listy $scope.list. Łączmy dane z pól i wstawiajmy jako nowy element na listę wyników result.push(wynikWiersza). Na koniec zwróćmy listę wyników (bo przecież to jest funkcja). Kod wygląda tak:

W zmiennej concat po prostu znalazły się połączone (skonkatenowane) dane z pól (na razie godziny i czas wystąpienia zostały celowo pominięte – ich obsługa będzie w kolejnych wpisach).

Wyświetlenie listy wyników

Dodajmy po prostu element <div>, w którym przejdziemy po wszystkich elementach z listy, którą zwraca funkcja myResult(). Element <div> zostanie powtórzony tyle razy ile jest elementów na liście. Wyświetlmy w nim połączone dane i na koniec przejście do nowej linii.

Dlaczego nie robić nowych linii w funkcji?

Dlaczego nie mogłem połączyć wszystkich danych w funkcji, dodać znaki nowej linii i zapisać do jednej zmiennej całego tekstu? A potem tylko go wyświetlić? (Uniknęlibyśmy pracy z dodawaniem każdego wynikowego wiersza do listy, a potem wyświetlania ich w pętli)

Tzn. kod funkcji wyglądałby tak:

A w HTML wyświetlilibyśmy jedynie wynik funkcji:

Byłaby dużo prościej, prawa?

Ale nie działa. Znaczniki <br /> nie są zamieniane na nowe linie, tylko pokazują się jako zwykłe znaki. Jest to zabezpieczenie AngularJS przed umieszczaniem kodu HTML na stronie przez użytkownika.

Jeśli naprawdę chcemy, jak można to wyłączyć? We wcześniejszych wersjach Angulara istniała dyrektywa ng-bind-html-unsafe, która pozwalała zbindować zmienna, w której jest HTML i go wyświetlać. Wyglaładałoby to tak:

Obecnie ta dyrektywa została usunięta. Jak sobie z tym poradzić? Trzeba zrobić dwie rzeczy:

  • Zastosować do bindowania dyrektywę ng-bind-html, co wygląda tak:
  • Wstrzyknąć moduł ngSanitize. Aby to zrobić należy:
    • W inicjalizacji aplikacji dodać moduł:
    • Dodać plik angular-sanitize.min.js:

Teraz działa. Jednak nie jest to bezpieczne rozwiązanie i lepiej zastosować to wyżej (z listą i dodawaniem znaku <br /> już w samym kodzie HTML, a nie poprzez funkcję).

Problem: duplikaty

To by było na tyle tego wpisu. Ale można zauważyć błąd, który pokazuje się jeśli wpiszemy takie same dane w dwa wiersze: Error: [ngRepeat:dupes]. Zostajemy odesłani do dokumentacji: Error: ngRepeat:dupes. Duplicate Key in Repeater, w której możemy znaleźć rozwiązanie problemu. W dyrektywie ng-repeat musimy dodać  track by $index. Co daje efekt:

O co chodzi? Po prostu nie są dozwolone duplikaty w liście po której chcemy przechodzić (jest to związane z odświeżaniem drzewa DOM, więcej tutaj). Aby umożliwić wstawianie duplikatów, trzeba dodać wyrażenie śledzące poprzez track by wyrazenieSledzace. Wyrażeniem śledzącym musi być jakaś unikalna wartość np. ID. Możemy skorzystać również z $index, czyli kolejno generowane numery podczas pobierania elementów z listy (będziemy z tego korzystać jeszcze nieraz).

Kosmetyczne ukrycie

Na koniec dodajmy małą rzecz, która dużo nie robi, ale dzięki niej przyjemniej będzie się korzystało z aplikacji. Zauważyliśmy pewnie, że wszystkie wiersze są przepisywane (również te puste). Dlatego w funkcji myResult() w kontrolerze dodajmy warunek, który będzie dodawał do listy wyników jedynie, gdy tytuł będzie uzupełniony:

Wynikowy kod

index.html

controller.js

Wynik działania

 

2 responses on “AgendaEditor: lista i formularz w AngularJS

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *