Artykuł

angularjs.org angularjs.org
sie 07 2013
0

AngularJS - godny uwagi frameworek MVC

Blisko 10 lat temu, JavaScript znajdował się na technologicznym zakręcie i po jego początkowej ekspansji nie było widać ani śladu. Od tego czasu sieć przeszła sporą przemianę, na której w głównej mierze zyskał właśnie JavaScript. Dzięki silnej ekspansji Ajaxa, JS odżył i obecnie trudno sobie wyobrazić jakąkolwiek witrynę w sieci, która choćby w minimalnym stopniu nie wykorzystywała tej technologii.

JavaScript wciąż posiada kiepskie wsparcie w IE (co prawda problem dotyczy głównie starszych wersji, ale mimo wszystko...) oraz kilka swoich językowych dziwności, jednak nie przeszkadza to w żaden sposób dynamicznemu rozwojowi tej technologii. Dziś JS pojawia się nawet poza siecią, np. w Windows 8, jako element budulcowy dla aplikacji Modern UI. Nikogo również nie dziwią wszelkiej maści biblioteki i frameworki zbudowane na bazie JavaScriptu. Dlatego też nie powinno być żadną niespodzianką, że po ten niepozorny język skryptowy sięgają najwięksi.

Tak też uczynił Google, tworząc bardzo udany framework MVC AngularJS, który to też mam przyjemność Wam dziś przedstawić.

Wprowadzenie - co i jak

Na temat wzorca projektowego MVC pisałem na łamach tego blogu już wielokrotnie, ale dla przypomnienia warto wspomnieć, że jest to podejście w którym całość programu/strony, rozbijamy na trzy zasadnicze elementy:

  • Kontroler - odpowiada za obsługę nadchodzących do aplikacji żądań. W przypadku AngularJS jest to specjalny fragment kodu JS
  • Model - jest odpowiedzialny za obsługę logiki biznesowej. W przypadku AngularJS jest to najczęściej fragment kodu JS, który odpowiada za przetwarzanie, łączenie się z serwerem itp.
  • Widok - odpowiada za warstwę prezentacji. W tym przypadku widok zapisujemy w pliku HTML

Aby móc korzystać z tego frameworka, musimy pobrać oczywiście jego bibliotekę JavaScript i podpiąć ją do projektu. Możemy oczywiście również skorzystać z adresu CDN (link do najnowszej wersji w dniu pisania wpisu):

https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js

Podział na dalsze elementy składowe aplikacji jest w praktyce dowolny. Możemy rozdzielić kod pomiędzy pliki HTML, JS oraz CSS wspomagające warstwę widoku, bądź też umieścić wszystko w jednym dużym HTMLu (oczywiście wariant skrajnie niepolecany w większości przypadków).

Kod HTML widoku, który będziemy pisać korzystając z frameworka, jest później kompilowany w locie przez przeglądarkę. Dzięki temu stosując specjalne konstrukcje oraz atrybuty, jesteśmy w stanie uzyskać użyteczne rozwiązania (pętle, zmienne itp.), których nie posiada klasyczny HTML i jednocześnie mamy gwarancję, że zadziałają one w każdej nowoczesnej przeglądarce. Można więc powiedzieć, że mamy tu coś na kształt popularnego swego czasu systemu szablonów Smarty, z tym że nie mamy skompilowanych plików wynikowych, tylko pliki szablonów, które są kompilowane w locie przez przeglądarkę przy każdym wywołaniu i w ten sposób serwowane użytkownikom..

Warto również wspomnieć o jednym z ważniejszych elementów AngularJS czyli o bindowaniu*. W przypadku tego frameworka działa ono naprawdę świetnie i mocno rozszerza standardowe możliwości HTMLa i JSa. Skuteczność tego mechanizmu zaprezentuję w pierwszym z przykładów.

Bindowanie* - mechanizm polegający na skojarzeniu dwóch elementów pochodzących z określonych miejsc, do współdzielenia pewnej określonej wartości. Bindowanie może być jednostronne tzn. zmiana u źródła generuje automatyczną zmianę u odbiorcy z wyłączeniem tożsamej relacji w drugim kierunku, bądź też obustronna - zmiana w dowolnym miejscu, wymusza zmianę w drugim miejscu.

Przywitajmy się

Na początek krótki przykład, który można znaleźć również na oficjalnej stronie projektu. W tym przypadku użytkownik może wpisać imię, a dzięki wspomnianemu wcześniej bindowaniu, każda wpisana fraza zostanie automatycznie wyświetlona w innym - wskazanym miejscu strony. Spójrzmy na kod:

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  </head>
  <body>
    <div>
      <label>Imię:</label>
      <input type="text" ng-model="name" placeholder="Podaj swoje imię">
      <hr>
      <h1>Witaj {{name}}!</h1>
    </div>
  </body>
</html>

Zróbmy krótką analizę. Pojawiło się tutaj kilka nowych elementów.

Przede wszystkim w linii 2 mamy atrybut ng-app. Generalnie określa on obszar, na którym działa AngularJS. Najczęściej spotyka się go właśnie przy znaczniku HTML dzięki czemu framework swym zasięgiem obejmuje całą stronę. W tym przypadku atrybut nie posiada żadnej wartości. Jeśli jednak takowa istnieje, to w w takiej sytuacji mówimy Angularowi by korzystał w tym obszarze z określonego modułu zdefiniowanego w kodzie.

Wróćmy jednak do meritum, czyli w tym przypadku do dalszej analizy kodu. Kolejnym ważnym elementem jest znacznik script (4), w którym wczytujemy ze zdalnego adresu kod samego frameworka. Oczywiście możemy ten plik trzymać lokalnie na serwerze - wszystko zależy od Waszych preferencji oraz możliwości.

W dalszej części tworzymy tekstowy input (9), który zyskuje dodatkowy atrybut ng-model. Jego wartością jest nazwa bindowanego pola w tym przypadku name.

Ostatni element na który powinniśmy zwrócić uwagę, to nagłówek (11) w którym wyświetlamy wpisane przez nas wcześniej imię w polu input, za pomocą konstrukcji zawierającej podwójne klamrowe nawiasy oraz nazwę bindowanego pola. Jak pokazuje przykład, bindowanie działa naprawdę dobrze i nie wymaga ono od nas ani jednej linijki dodatkowego kodu JavaScript.

Lista zadań

Drugi przykład również zaczerpnąłem z oficjalnej strony projektu i zaadaptowałem go na swoje potrzeby. W tym przypadku tworzymy listę zadań, której wyjściowe dane znajdują się w tablicy obiektów JS. Przykład nie posiada żadnego cache'a czy też zapisu do bazy danych, tak więc po zamknięciu przeglądarki wszystkie wprowadzone modyfikacje zostaną utracone. W tym przypadku najbardziej istotnym elementem będzie wprowadzony w kodzie JS kontroler:

function TodoCtrl($scope) {
  $scope.todos = [
    {text:'Naucz się czegoś nowego', done:true},
    {text:'Stwórz coś kreatywnego', done:false}];
 
  $scope.addTodo = function() {
    $scope.todos.push({text:$scope.todoText, done:false});
    $scope.todoText = '';
  };
 
  $scope.remaining = function() {
    var count = 0;
    angular.forEach($scope.todos, function(todo) {
      count += todo.done ? 0 : 1;
    });
    return count;
  };
 
  $scope.archive = function() {
    var oldTodos = $scope.todos;
    $scope.todos = [];
    angular.forEach(oldTodos, function(todo) {
      if (!todo.done) $scope.todos.push(todo);
    });
  };
}

Jak widać, cały kod JS znajduje się właśnie w przestrzeni kontrolera. Nazwa w tym przypadku nie ma większego znaczenia, a zastosowany skrót Ctrl bardziej pomaga określić z jakim elementem mamy do czynienia. W ramach kontrolera, kluczowy będzie dla nas argument scope, który tak naprawdę przechowuje wszystkie dane modelu i jest swego rodzaju łącznikiem kontrolera z widokiem. Wewnątrz kontrolera przypisujemy do niego wszelakie funkcje i obiekty, które później będzie można wywołać z poziomu widoku. Tak samo wszystkie zmienne zastosowane w polach w pliku widoku, widoczne będą tutaj jako elementy podrzędne tego obiektu.

Na wstępie definiujemy tablicę obiektów przechowujących informacje o zadaniach (2-4). Dla każdego z nich określamy treść (właściwość text) oraz flagę określającą (done) czy zadanie zostało zrealizowane, czy też nie.

W kolejnym kroku definiujemy 3 kluczowe funkcje. Kolejno są to:

  • addTodo (6-9)
  • remaining (11-17)
  • archive (19-25)

Pierwsza z nich dodaje do tablicy nowe zadanie, które domyślnie jest nie zrealizowane (flaga done z wartością false), natomiast tekst pobierany jest z modelu danych z pola todoText, które na formularzu znajduje się w polu input. W tym przypadku zachodzi bindowanie obustronne. Przy dodaniu zadania dodajemy tekst z tego pola do tablicy, natomiast po operacji czyścimy zmienną i tym samym wyczyszczone zostanie również pole. Warto zwrócić uwagę na obiekt scope pochodzący z parametru, który w tym przypadku jest swego rodzaju przekaźnikiem - globalną przestrzenią w naszej aplikacji, za pomocą której odbywa się rzeczywista komunikacja pomiędzy kontrolerem a widokiem.

Funkcja remaining zwraca liczbę zadań, które nie zostały jeszcze zrealizowane, a które obecnie znajdują się na liście. Wykorzystujemy tutaj funkcję frameworka forEach, która działa praktycznie w identyczny sposób jak analogiczna funkcja each z popularnego jQuery.

W ostatniej funkcji, korzystamy z poznanego przed chwilą forEach i generalnie czyścimy listę zadań z tych, które zostały już zakończone (flaga done = true).

Przejdźmy teraz do kodu widoku. W tym przypadku również pojawia się kilka nowości.

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script src="todo.js"></script>
    <link rel="stylesheet" href="todo.css">
  </head>
  <body>
    <h2>Lista zadań</h2>
    <div ng-controller="TodoCtrl">
      <span>Pozostało {{remaining()}} z {{todos.length}}</span>
      [ <a href="" ng-click="archive()">usuń zakończone</a> ]
      <ul>
        <li ng-repeat="todo in todos">
          <input type="checkbox" ng-model="todo.done">
          <span class="done-{{todo.done}}">{{todo.text}}</span>
        </li>
      </ul>
      <form ng-submit="addTodo()">
        <input type="text" ng-model="todoText" size="30"
               placeholder="Dodaj nowe zadanie">
        <input class="btn-primary" type="submit" value="Dodaj">
      </form>
    </div>
  </body>
</html>

Pierwszą nową rzeczą jest podpięcie kontrolera (10) do atrybutu ng-controller, który ma być aktywny dla całego diva użytego w naszej aplikacji. W linii 11 wyświetlamy listę pozostałych do realizacji zadań. W tym celu wykorzystujemy wcześniej napisaną funkcję remaining do określenia liczby niezrealizowanych zadań spośród liczby wszystkich na liście (długość tablicy todos).

W linii 12 poznajemy mechanizm ng-click, który pozwala na przypisanie funkcji obsługującej kliknięcie na wskazanym elemencie. W tym przypadku podpinamy funkcję archive, która usuwa z listy zrealizowane zadania.

W liniach 14-17, zapisujemy instrukcję, która działa jak klasyczny foreach, z tym że jest ona bindowana i automatycznie się odświeża. Jednym zdaniem - wyświetlamy kolejne obiekty todo z wcześniej zadeklarowanej tablicy todos. Dla każdego z tych elementów mamy checkbox zbindowany za pomocą atrybutu ng-model oraz span z treścią zadania. Warto zwrócić również uwagę na klasę elementu span, dzięki której możemy nadać odpowiedni styl zadaniom wykonanym (done-true) / przeznaczonym do realizacji (done-false).

Zwieńczeniem całości jest formularz (19-23), który również nie ustrzegł się nowości. Nie ma w tym przypadku klasycznych atrybutów action oraz method, jest za to nowa konstrukcja ng-submit (19), która wskazuje na funkcję addTodo zdefiniowaną w kontrolerze.

Kluczowym elementem jest input, w którym wprowadzamy treść zadania. Korzysta on już z wcześniej poznanego atrybutu ng-model, który w tym przypadku odnosi się do wartości todoText, która zapewne jak pamiętacie była użyta właśnie w funkcji addTodo.

Zwieńczeniem całości jest kod CSS, którego najważniejszym elementem będzie wspomniana wcześniej klasa done-true:

ul{
	padding-left: 0;
}

li{
	list-style: none;
}

.done-true {
	text-decoration: line-through;
	color: grey;
}

Tutaj możecie zobaczyć jak to wszystko działa w praktyce.

Zdaję sobie sprawę, że z tymi przykładami odrobinę poszedłem na łatwiznę, ale głównym moim zamiarem było w tym przypadku zachęcić Was, byście zapoznali się bliżej z tym frameworkiem;-) Dużo więcej na ten temat przeczytacie na oficjalnej stronie projektu.

Data ostatniej modyfikacji: 07.08.2013, 20:12.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus