KnockoutJS jest JavaScriptowym frameworkiem MVC/MVVM, który wykorzystujemy w projekcie, w którym obecnie biorę udział. Co prawda nie opisywałem go w moim wpisie dotyczącym tego rodzaju frameworków, ale jest on pełnoprawnym przedstawicielem swojego gatunku, a od wersji 3.2.0, która to pojawiła się w połowie zeszłego (2014) roku wprowadzono kilka “ficzerów”, które spowodowały, że teraz patrzę na to narzędzie zupełnie inaczej… Komponenty KnockoutJS są właśnie taką super nowością, z którą na pewno warto się zapoznać. I to komponenty właśnie są tematem mojego dzisiejszego wpisu.

Komponenty KnockoutJS - cóż to takiego?

Ogólnie rzecz ujmując komponenty KnockoutJS są to kawałki modelu (view-modelu) powiązane z odpowiadającymi im szablonami HTML. Pozwalają nam one zorganizować aplikację w niezależne, “re-używalne” moduły. Moim zdaniem najlepiej odpowiadają one dyrektywom znanym Wam zapewne z AngularJS. Efektem utworzenia komponentu jest nowy tag HTML, który możemy użyć w kodzie widoku - na przykład w ten sposób:

<navbar params="home, about, contact"></navbar>
<grid params="data: items"></grid>
...

W efekcie umieszczenia takiego komponentu w kodzie widoku, zamiast niego wyrenderowany zostanie kod HTML komponentu i automatycznie zostanie on “zbindowany” z danymi view-modelu.

Przykład komponentu

Myślę, że dobrym sposobem na zaprezentowanie działania komponentów KnockoutJS będzie pokazanie Wam (i dokładne omówienie) przykładu z oficjalnej dokumentacji “nokauta” w poniższym codepenie:

See the Pen WbXWgz by burczu (@burczu) on CodePen.

Objaśnienie przykładu

Najpierw przeanalizujmy kod JavaScript (zakładka JS). Widzimy, że aby utworzyć komponent wystarczy wywołać funkcję ko.components.register, przekazując jej jako pierwszy parametr nazwę komponentu - będzie to jednocześnie nazwa tagu HTML, który umieścimy w widoku. Drugi parametr jest obiektem zawierającym dwie właściwości: viewModel oraz template. Na pewno domyślacie się do czego one służą ;)

Otóż właściwość viewModel jest to model danych naszego komponentu, który powiązany jest z szablonem - ten z kolei przekazujemy jako wartość właściwości template. Jeśli dokładniej się im przyjrzeć to widać, że model danych zawiera dwie funkcje: jedna odpowiedzialna za ustawienie zmiennej chosenValue wartości like; druga natomiast ustawia jej wartość dislike. Zwróćcie uwagę, że ustawianie odbywa się tak jakby była to zmienna obserwowalna - to dlatego, że taki typ wartości przekazywany jest poprzez parametr funkcji - zobaczyć to można później w kodzie HTML przykładu. Szablon natomiast zawiera kod wyświetlający, w zależności od zmiennej chosenValue, albo guziki głosowania, albo jego rezultat.

Myślę, że dla osób, które znają KnockoutJS, opisywane zagadnienia są w miarę jasne - dla tych którzy nie znają, zachęcam do zapoznania się z podstawami KnockoutJS (niestety to zbyt obszerny temat na wpis) - “nokaut” idzie moim zdaniem w dobrym kierunku… ;)

Bardziej optymalna rejestracja komponentu

Oczywiście taki sposób rejestrowania komponentu nie jest optymalny. Na pewno dobrą praktyką jest umieszczenie kodu view-modelu i szablonu w osobnych plikach. Wówczas kod rejestracji wyglądać mógłby jakoś tak (uwaga! wymagane zależności - szczegóły pod przykładem):

ko.components.register('like-widget', {
  viewModel: { require: 'models/like-widget' },
  template: { require: 'text!templates/like-widget.html' }
});

Prawda, że lepiej? Oczywiście wprawne oko zauważy pewien szczegół… Chodzi oczywiście o wyrażenie require - tak, tak… aby to zrobić w powyższy sposób należy zastosować narzędzie RequireJS, czyli “ładowarkę modułów AMD”. Oczywiście nic nie stoi na przeszkodzie aby użyć innego komponentu tego typu.

Ok, ale wracajmy do naszego przykładu… Poniżej rejestracji komponentu mamy po prostu “logikę biznesową” pod postacią utworzenia obiektu Product. Później natomiast tworzony jest ogólny viewModel dla całej aplikacji (MyViewModel), w którym tworzymy listę dostępnych produktów. Na koniec wszystko to jest “bindowane” do widoku w sposób typowy dla KnockoutJS.

Pora teraz przejść do kodu HTML (odpowiednia zakładka w codepenie). Nie ma już tutaj specjalnej filozofii: dla każdego ze zdefiniowanych w ogólnym view-modelu produktów wyświetlana jest jego nazwa, a pod nią utworzony przez nas wcześniej komponent (wiemy już, że wyświetla on guziki głosowania). Zwróćcie uwagę co jest przekazywane jako parametr komponentu - zmienna userRating obiektu Product, która jest obserwowalna. To dlatego później w view-modelu używaliśmy jej w taki, a nie inny sposób. Efekty działania komponentu możecie podziwiać w zakładce Result codepena.

Podsumowanie

Myślę, że pokazane powyżej komponenty KnockoutJS to nie jest coś szczególnie odkrywczego, szczególnie jeśli ktoś zna inne JavaScriptowe frameworki MVC/MVVM. Jednak w KnockoutJS to jednak nowość, której bardzo brakowało. A w powiązaniu z jedną z bibliotek pomagających w realizacji routingu po stronie klienta (na przykład CrossroadsJS), “nokaut” wchodzi na zupełnie inny poziom niż dotychczas!