Obserwowalne, obserwowane? lepiej brzmi w oryginalne observable. Dziś skupię się na właściwościach observable, zapraszam 🙂
Poprzednio widzieliśmy zaledwie ułamek możliwości observable, wiemy, że ta właściwości pozwala automatycznie zaktualizować element HTML przy użyciu Knockout’a kiedy poszczególny element ulegnie zmianie. Knockout.js posiada jeszcze dwie inne możliwości zastosowania tych właściwości w ViewModel są to: computed observables i observable arrays.
Computed observables pozwala stworzyć dynamiczne generowaną właściwość. Oznacza to, że możemy połączyć kilka normalnych właściwości observables w jedną właściwość, i Knockout.js nadal będzie śledzić zmiany w widoku kiedy jakakolwiek wartość ulegnie zmianie.
Observable arrays połączenie potęgi observable Knockouta.js z czystą JavaScript’ową tablicą. Tak jak podstawowa tablica, tak observable arrays posiada listę elementów którymi można manipulować. Dzięki temu, że tablice są observable każde połączenie z elmentem HTML powoduję, że elementy tablicy są automatycznie aktualizowanie podczas dodania, albo usunięcia elementu z tablicy.
Możliwości łączenia właściwości observables z listą elementów, dostarcza w pełni kompletną strukturę którą przechowujemy w ViewModel.
Computed Observables
Na początku zacznę z prostym przykładem. Połączę właściwości firstName i lastName observables z klasy PersonViewModel, tworząc fullName computed observable
this.fullName = ko.computed(function(){ return this.firstName() + " " + this.lastName(); },this);
Definiuje anonimową funkcje która zwraca pełną nazwę użytkownika z PersonViewModel.fullName. Następuję w pełni dynamiczna generacja fullName z istniejących komponentów (firstName i lastName) takie rozwiązanie zapobiega gromadzeniu nadmiarowości danych, ale pomału jeszcze jest wiele do zrobienia. Funkcja ko.computed() mówi Knockout’owi, że musi zaktualizować jakikolwiek element HTML który przechowywany jest w fullName w naszym przypadku jest to firstName lub lastName.
Upewnijmy się, że nasz computed observable działa poprawnie, wynik jaki powinien się pojawić to: „Koszyk Przemek Nyweron”
<p>Koszyk</p><span data-bind='text: fullName'></span>
Teraz sprawdzimy czy Knockout.js poprawnie śledzi element HTML synchronicznie, kiedy zmienimy jedną z właściwości. Po zbindowaniu instancji PersonViewModel, spróbujemy zmienić firstName.
var vm = new PersonViewModel() ko.applyBindings(vm); vm.firstName("Tomek");
Zmiana powinna wyglądać następująco: „koszyk Tomek Nyweron”. Warto pamiętać, że aby odczytać czy ustawić wartość observable, należy użyć nawiasów, wyobrazić sobie, że jest to funkcja z jednym argumentem, nie stosować znaku przypisania (=).
Computed observables dostarcza wiele takich samych możliwości jak automatyczna synchronizacja widoku w Knockout.js. Zamiast śledzenia która właściwość powinna zostać zaktualizowana w części widoku ViewModel, computed observables pozwalają na tworzenie aplikacji wokół właściwości atomowych i delegowanie śledzenia zależności do Knockout’a.
Observable Arrays
Observable arrays pozwalają Knockout’owi na śledzenie elementów w liście. Postaram się to przedstawić tworząc koszyk który będzie wyświetlany na stronie użytkownika. W pierwszej kolejności tworze obiekt który będzie reprezentować produkty. Na samej górze naszego skryptu przed definicją PersonViewModel, dodam poniższy kod:
function Product(name, price){ this.name = ko.observable(name); this.price = ko.observable(price); }
Jest to zwykły obiekt który przechowuje kilka właściwości. Z ważny rzeczy które warto wiedzieć to, można nadać wielu obiektom właściwość observable. Mówiąc inaczej można utworzyć relacje między wieloma modułami ViewModels w pojedynczej aplikacji.
Następnie stworzymy kilka instancji dla naszej nowej klasy Product które będą odzwierciedlać wirtualny koszyk. Poniższy kod należy umieścić w PersonViewModel
this.basket = ko.observableArray([ new Product("element 1", 10), new Product("element 2", 20), new Product("element 3", 30) ]);
Powyższy kod to nic innego jak zwykła tablica JavaScript przechowująca trzy elementy, dzięki ko.observableArray Knockout.js może śledzić zmiany jakie zachodzą po przez dodanie bądź usunięcie elementu. Teraz przyszła pora na aktualizację widoku.
<table> <thead> <tr> <th>Produkt</th> <th>Cena</th> </tr> </thead> <tbody data-bind='foreach: basket'> <tr> <td data-bind='text: name'></th> <td data-bind='text: price'></th> </tr> </tbody> </table>
Dodaliśmy tabele z HTML5 przechowująca kolumny o nazwach produkt i cena. Ten przykład wprowadza nowe bindowanie takie jak foreach. Kiedy Knockout.js napotyka foreach: basket powoduje to uruchomienie pętli dla każdego elementu w właściwości ViewModel. basket. text: name można odczytać jako basket[i].name. Poniżej wynik naszej pracy
Hello World! z Knockout.js
Koszyk Tomek Nyweron Produkt Cena element 1 10 element 2 20 element 3 30
Dodanie produktu
Cała zabawa z observable arrays rozpoczyna się gdy pozwolimy synchronizować widok Knockout’owi kiedy dodamy albo usuniemy element. Na przykład, dodamy metodę do ViewModel która będzie dodawać element do koszyka.
this.addProduct = function(){ this.basket.push(new Product("Nowy element", 11)); }
Teraz możemy dodać przycisk który uruchomi powyższą metodę. Podmienimy troszkę nasz przycisk checkout()
<button data-bind='click: addProduct'>Dodaj element</button>
Teraz klikając w nasz przycisk, metoda addProduct() z ViewModel będzie dodawać nowy element do koszyka.
Warto wspomnieć, że głównym założeniem Knockout’a jest tworzenie jak najmniejszej ilości zmian które są niezbędne do synchronizacji widoku. Zamiast za każdym razem na nowo generować listę Knockout.js śledzi, które elementy DOM są do zmodyfikowania, jeżeli Knockout.js napotka taki element natychmiast go aktualizuje.
Usunięcie produktu
Podobnie Knockout.js radzi sobie z usuwaniem elementów z observable array przy użyciu metody remove(). Do naszej klasy PersonViewModel dodajmy nową metodę.
this.removeProduct = function(product){ this.basket.remove(produkt); }
Teraz dodajmy przycisk usuń do każdego elementu generowanego przez pętlę w znaczniku tbody
<tr> <td data-bind='text: name'></td> <td data-bind='text: price'></td> <td> <button data-bind='click: $root.removeProduct'>Usuń</button> </td> </tr>
Tutaj zaszła mała zmiana, ponieważ jesteśmy w pętli foreach musimy dodać $root referencja do elementu z naszego ViewModel wewnątrz pętli. Jeżeli spróbujemy dodać removeProduct() bez referencji, Knockout.js będzie chciał wykonać metodę z klasy Product, która nie istnieje.
Dobrze, dodaliśmy do pętli foreach referencje removeProdukt(). W foreach troszkę nam popsuło aplikację, gdy uruchomimy kod i klikniemy w Usuń wystąpi error. Użyjemy małego JavaScript’owego triku który rozwiąże ten problem. W klasie PersonViewModel przypiszmy do zmiennej self wyrażenie this
function PersonViewModel() { var self = this; ...
Teraz, użyjmy self wewnątrz metody
this.removeProduct = function(product){ self.basket.remove(product); };
Po wykonaniu powyższych czynności bez problemu manipulacja dodawania i usuwania nowego elementu powinna działać bez zarzutu. Knockout.js automatycznie dodaje element w pętli jako pierwszy parametr do removeProdukt().
Zniszczenie produktu
Metoda remove() jest przydatna kiedy manipulujemy listą w czasie rzeczywistym, ale to może spowodować kłopoty kiedy spróbujemy wysłać dane z ViewModel do serwera.
Na przykład, rozważmy akacje zapisywania koszyka do bazy danych za każdym razem kiedy dodajemy albo usuwamy element z tablicy. Z użyciem metody removeProdukt() usunięcie elementu zachodzi natychmiast, więc wszystko co musimy zrobić to wysłać do serwera całą nową listę, jest możliwe sprawdzenie który element został dodany czy usunięty. Można zarówno zapisać całą listę, albo manualnie porównać zawartość nowej listy z tą już istniejącą w bazie co wiąże się z wykonaniem kolejnego żądania AJAX.
Żadna z powyższych opcji nie jest wydajna, szczególnie, że Knockout.js wie który dokładnie element został usunięty. Aby zaradzić tej sytuacji, observable array posiada metodę destroy(). Spróbujmy zmienić PersonViewModel.removeProduct() na poniższy kod
this.removeProduct = function(product){ self.basket.destroy(product); alert(self.basket().length); }
Teraz gdy klikniemy w
alert(product._destroy);
Właściwość _destroy powoduje, że lista observable jest posortowana i zdejmuję tylko ten element który właśnie został usunięty. Dzięki temu, możemy wysłać tylko te elementy do serwera które zostały usunięte. Takie rozwiązanie jest bardziej wydajne i efektywne przy zarządzaniu listą kiedy pracujemy z żądaniami AJAX. Zauważ, że dla każdego przejścia, pętla jest „świadoma” takiego zdarzenia i usuwa znaczniki tr z widoku, nawet jeśli element pozostaje w tablicy.
Inne metody tablic
Observable arrays są właściwie takie same jak właściwości observable, z wyjątkiem tego, że pod sobą mają czystą tablicę JavaScript’ową zamiast łańcucha znaków, liczb, albo obiektów. Knockout.js posiada własne wersje metod do pracy z observable arrays np: push(), pop(), shift(), sort(), remove(), destroy() itp…
Podsumowanie
W tym wpisie widzimy jak wiele razy computed observable może być użyty gdy połączymy go z zwykłą właściwością observables która jest śledzona przez Knockout’a. Również zobaczyliśmy jak działają observable arrays, które są dobrym elementem w Knockout.js pozwalające synchronizować listę z danymi w naszym ViewModel z komponentami HTML’a.
Computed observables i obserable arrays powodują, że Knockout.js jest świetną opcją dla szybkich zmian. Pozwalają umieścić nam całą skomplikowaną funkcjonalność w jednym kawałku, gdzie resztą zajmuję się już Knockout.js. Na przykład stworzenie prostego computed observable który zliczy całą sumę w koszyku i wyświetli wynik na stronie. Raz stworzona funkcjonalność zliczania sumy może być użyta gdziekolwiek chcemy na przykład dla żądania AJAX które potrzebowałoby dostępu do właściwości ViewModel’u.
Pozdro 🙂