Pokazywanie postów oznaczonych etykietą angularjs. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą angularjs. Pokaż wszystkie posty

niedziela, 7 października 2012

[HTML|JS|CSS] AngularJS: Services

Dzięki zastosowaniu serwisów AngularJS wspiera wzorzec projektowy Dependency Injection. Wzorzec ten znany z wielu obiektowych języków programowania ułatwia testowanie komponentów oraz podejmowanie decyzji o zależnościach w czasie pracy aplikacji. W przypadku Angulara podczas uruchomienia strony tworzony jest obiekt zwany injectorem, zarządzający wszystkimi serwisami. W momencie, gdy chcemy wykorzystać któryś z serwisów, injector zwraca jego instancję lub inicjuje go, jeśli nie nastąpiło to wcześniej. Ma tu więc miejsce połączenie wzorców singletonu oraz lazy-load. Samo wstrzykiwanie zależności można uzyskać na dwa sposoby: niejawnie podając nazwę serwisu jako argument funkcji (np. kontrolera), lub mechanizmem wstrzykiwania. Sposób pierwszy, choć czytelniejszy, napotyka jedną poważną przeszkodę. Podczas minifikacji i obfuskacji pliku ze skryptem zostają zmieniane nazwy zmiennych, również argumentów funkcji, przez co będziemy mieli do czynienia z zupełnie inną logiką. Drugi zapis, nieco dłuższy naprawia ten problem, kosztem czytelności kodu.
//DEPENDENCY INJECTION

function firstController($scope, $window){
 $scope.text = "firstController";
 $scope.introduce = function(e){
  $window.alert($scope.text);
 }
}

function secondController(s, w){
 s.text = "secondController";
 s.introduce = function(e){
  w.alert(s.text);
 }
}
secondController.$inject = ['$scope','$window'];

Mamy także możliwość tworzenia własnych serwisów dla naszych modułów. Dostarczając definicję, dostajemy w prezencie opisaną powyżej funkcjonalność, łącznie z możliwością wstrzykiwania naszego serwisu do kontrolera. Przykład poniżej.
angular.module('services', [], function($provide) {
    $provide.factory('mylogger', function() {
     var counter = 0;
     return function(text){
      counter += 1;
      console.log("Logging text number " + counter);
      console.log(text);
     }
    });
});

Przykład użycia w kontrolerze:
function firstController($scope, $window){
 $scope.text = "firstController";
 $scope.introduce = function(e){
  $window.alert($scope.text);
 }
}

Przez konwencję zaleca się, by nie nadawać własnym serwisom nazw rozpoczynających się od $, jako znaku zarezerwowanego dla serwisów Angulara.

[HTML|JS|CSS] AngularJS: Event Handlers

Zdarzenia możemy obsługiwać zarówno czystym JavaScriptem, jak i przyjazną biblioteką jQuery, Dlaczego więc korzystać z Angulara ? Ponieważ kilka rzeczy otrzymujemy za darmo. Po pierwsze w sposób deklaratywny w widoku możemy wpisać wyrażenia, które będą ewaluowane, gdy dane zdarzenie będzie miało miejsce. Można też oczywiście przekazać callback, który będzie odpowiednią funkcją z kontrolera. Przykład poniżej.

<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
 <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
 <script src="scripts/myscript.js"></script>
</head>
<body ng-controller="eventsController"> 
 <div ng-click="counter = counter + 1">click test</div>
 <div ng-click="clicks()">display</div>
 <div ng-mousemove="mouse = mouse + 1"
  ng-mouseout ="display()">here move mouse</div>
</body>
</html>

Cały kod po stronie kontrolera to inicjalizacja zmiennych i deklaracja funkcji.

function eventsController($scope){
 $scope.counter = 0;
 $scope.mouse = 0;
 $scope.clicks = function(e){
  console.log('clicks  ' + $scope.counter);
 }
 $scope.display = function(e){
  console.log('mouse moves ' + $scope.mouse);
  $scope.mouse = 0;
 }
}

Zdarzenia AngularJS wywoływane są w kontekście danego scope, dlatego jeśli w danym zdarzeniu chcemy modyfikować model DOM, to zostanie on automatycznie odświeżony. Korzystając z innych bibliotek w połączeniu z Angularem nie mamy tego konfortu i należy wykorzystywać instrukcję $scope.$apply do automatycznego odświeżenia UI. Poniżej porównanie równoważnych funkcjonalnie handlerów w jQuery i AngularJS.

<body ng-controller="eventsController"> 
 <div ng-click="add()">add element </div>
 <div id="add">add element (bind) </div>
 <ul>
  <li ng-repeat="n in numbers">{{n}}</li>
 </ul>
</body>

function eventsController($scope){
 $scope.numbers = [1,2];
 $scope.add = function(e){
  $scope.numbers.push($scope.counter);
 }

 $('#add').click(function(e){
  $scope.$apply($scope.numbers.push($scope.counter));
 })
}

Ostatnią przydatną funkcjonalnością dotyczącą zdarzeń jest możliwość ich propagacji w górę lub w dół drzewa DOM. Służą do tego odpowiednio funkcje $emit oraz $broadcast. 

<div ng-click="$broadcast('myevent')">
 Click to broadcast down, emittedEvents: {{outerEvents}}
 <div ng-controller="innerController">
  <div ng-click="$emit('myevent2')">
   Emit up, broadcastedEvents: {{innerEvents}}
  </div>
 </div>
</div>

Obsługa w kontrolerze wykonywana jest dzięki instrukcji $on.

function eventsController($scope){
 $scope.outerEvents = 0;
 $scope.$on('myevent2', function() {
  $scope.outerEvents++;
 });
}

function innerController($scope){
  $scope.innerEvents = 0;
  $scope.$on('myevent', function() {
  $scope.innerEvents++;
 });
}

[HTML|JS|CSS] AngularJS: Filters

Filtry są bardzo użytecznym narzędziem pozwalającym na wyświetlanie danych w sposób sformatowany do naszych potrzeb. Angular JS udostępnia kilka przydatnych filtrów, z których możemy w każdej chwili skorzystać podając ich nazwę w expression, w konwencji  {{text | filtr}}. Poniżej przedstawiono najważniejsze filtry. Dla każdego property z poniższego kontrolera można zastosować filtry z widoku.

function filtersController($scope){
 $scope.text = "hello world";
 $scope.cost = 1000000000;
 $scope.population = 38276149;
 $scope.time = new Date();
 $scope.entity = {
  name : "Johnny",
  surname : "Depp",
  country : "USA"
 }
 $scope.numbers = [1,2,3,4,5,6,7,8,9];
 $scope.leet = "HeLLo WoRLd"; 
}


<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
 <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
 <script src="scripts/myscript.js"></script>
</head>
<body ng-controller="filtersController"> 
 <div>{{text | uppercase}}</div>
 <div> {{cost | currency:"PLN" }} </div>
 <div> {{population | number}} </div>
 <div> {{time | date:'yyyy-MM-dd HH:mm:ss'}}</div>
 <div> {{entity | json}} </div>
 <div> {{numbers | limitTo:4}} </div>
 <div> {{numbers | limitTo:-4}} </div>
 <div> {{leet | lowercase}} </div>
</body>
</html>

Po uruchomieniu strony otrzymano poniższy widok:


Jeżeli dostarczone filtry nam nie wystarczają, możemy stworzyć własny filtr. Deklarujemy go w podobny sposób jak dyrektywę:

angular.module('filters', []).
 filter('oddUpper',function(){
 return function(text){
  var text = text.toUpperCase();
  var text2 = text.toLowerCase();
  var oddUpper = "";
  for (var i = 0, length = text.length; i < length; i+=1) {
   oddUpper += i % 2 == 0 ? text.charAt(i) : text2.charAt(i);
  };
  return oddUpper;
 }
});

Widok korzysta z filtrów użytkownika w identyczny sposób jak z normalnych filtrów.

<p>My custom filter</p>
<div>{{title | oddUpper}}</div>

sobota, 6 października 2012

[HTML|JS|CSS] AngularJS: Directives

Dyrektywy w Angularze są mechanizmem pozwalającym w prosty sposób tworzyć potężne mechanizmy, służące zarówno do definiowania zachowań, jak i manipulacji  modelem DOM. Podczas procesu kompilacji dyrektywy są wyszukiwane w kodzie HTML i przetwarzane zgodnie ze zdefiniowaną w nich logiką. Angular JS umożliwia wprowadzenie czterech rodzajów własnych dyrektyw. Mogą one być umieszczane w:
  • nazwach elementów
  • klasach
  • atrybutach
  • komentarzach
Istnieje pewna konwencja, której należy przestrzegać: w kodzie html dyrektywy piszemy jako tzw. snake - names (to-jest-dyrektywa), natomiast w code behind w tzw. camel - case (toJestDyrektywa). Deklaracja dyrektyw w widoku może przebiegać w następujący sposób.

<!doctype html>
<html ng-app="directivesSample">
<head>
<meta charset="utf-8">
 <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
 <script src="scripts/myscript.js"></script>
</head>
<body> 
 <div class="my-first-directive"> </div>
 <my-second-directive></my-second-directive>
 <div my-third-directive></div>
 <!-- directive: my-fourth-directive exp -->
</body>
</html>

Aby AngularJS mógł odpowiednio zinterpretować dyrektywy zawarte w widoku, należy skonfigurować moduł aplikacji w code behind.

angular.module('directivesSample', []).
 directive('myFirstDirective', function(){
   return {
    restrict : 'C',
    template : "<div><button>First directive</button>"+
    "<label>{{myAttribute}} </label>" + 
    "<label>{{literal}} </label>" + 
    "<label>{{text}}</label>" + 
    "</div>",
    scope: { 
     myAttribute:'@myAttribute',
     literal : '=literal' 
    },
    controller : function($scope){
     console.log('controller first... ');
     $scope.text = "someText";
     $scope.literal += ' modified';
     console.log("=literal " + $scope.literal);
    }
  }
 })

W obiekcie konfigurującym mamy do dyspozycji szereg właściwości i funkcji, które możemy modyfikować. Poniżej omówione zostały niektóre z nich:
  • restrict
Informacja na temat tego, gdzie została umieszczona dyrektywa (C - w klasie, M - w komentarzu, A - w atrybucie, E - jako osobny element).
  • template
Zawiera kod HTML, który zostaje doklejony na końcu ciała elementu z dyrektywą. Można użyć property templateUrl z podaną ścieżką do osobnego pliku html z szablonem.
  • replace
Po ustawieniu na true dyrektywa zostanie zastąpiona szablonem (wymagane dla dyrektyw w komentarzach). Domyślnie wartość false, przez co szablon jest wstawiany do wnętrza dyrektywy
  • scope
true - tworzy nowy scope w obrębie dyrektywy, dziedziczy po parent scope
{} nowy izolowany scope (brak bezpośredniego dostępu do properties z parent scope). Podczas tworzenia scope można pobierać atrybuty z elementu DOM na którym zastosowana jest dyrektywa (np. '@myAttribute', oraz dworzyć dwukierunkowy binding do właściwości z parent scope (np. '=literal'). Poniżej przykład:

<body ng-controller="myController"> 
 <div>{{literal}}</div>
 <div class="my-first-directive" literal="literal" my-attribute="myAttr"></div>
</body>


function myController($scope){
 console.log('creating outer Scope...');
 $scope.literal = 'outerScope';
}

angular.module('directivesSample', []).
 directive('myFirstDirective', function(){
   return {
    restrict : 'C',
    template : "<div><button>First directive</button>"+
    "<label>{{myAttribute}} </label>" + 
    "<label>{{literal}} </label>" + 
    "<label>{{text}}</label>" + 
    "</div>",
    scope: { 
     myAttribute:'@myAttribute',
     literal : '=literal' 
    },
    controller : function($scope){
     console.log('controller first... ');
     $scope.text = "someText";
     $scope.literal += ' modified';
     console.log("=literal " + $scope.literal);
    }
  }
 })

Wyliczany napis w pierwszym divie zostanie przez data binding zaktualizowany na "outerScope modified".
  • compile: function compile(tElement, tAttrs, scope)
Funkcja wywoływana podczas procesu kompilacji, na tym etapie transformuje się drzewo DOM, np. klonuje elementy za pomocą ng-repeat. Rzadko stosowana.
  • link: function(scope, element, attrs)
W tym miejscu następuje rejestracja event handlerów dla elementów modelu DOM.
link: function(scope, element, attrs) {
 $(element).bind('click',function(){
  alert('click');
 })
}

niedziela, 30 września 2012

[HTML|JS|CSS] AngularJS: Routing

Podczas tworzenia zaawansowanych stron internetowych zachodzi potrzeba zarządzania wieloma widokami tak, aby użytkownik mógł sprawnie nawigować w obrębie strony. AngularJS wspiera mechanizm routingów umożliwiając łatwe zarządzanie szablonami i ich kontrolerami na podstawie podanego adresu url rozpoczynającego się od znaku #. Aby skorzystać z gotowego mechanizmu należy skonfigurować moduł $routeProvider. Zatem pierwszym krokiem, jaki należy wykonać jest podanie, jakie szablony html mają być wyświetlane przy odpowiednich adresach url i jaki zastosować do nich kontroler. Przykład poniżej:

angular.module('routingsample', []).
config(['$routeProvider', function($routeProvider) {
 $routeProvider.
 when('/first/:myparam', {templateUrl: 'pages/first.html', controller: templateController}).
 when('/second/:myparam', {templateUrl: 'pages/second.html', controller: templateController}).
 when('/third/:myparam', {templateUrl: 'pages/third.html', controller: templateController}).
 otherwise({redirectTo: '/first/:myparam'});
}]);

Nazwę modułu (routingsample) podaje się w widoku html w instrukcji ng-app. Konfiguracja polega na podaniu odpowiednich szablonów dla każdego możliwego przypadku. Instrukcja otherwise przekieruje stronę do domyślnego szablonu. Po dwukropku podać można opcjonalny parametr routingu dostępny w kontrolerze. Przykładowy url strony to:

http://localhost:8800/#/first/22

Tworząc kontroler należy za pomocą mechanizmu Dependency Injection zdeklarować chęć użycia parametru routingu, dostępnego w providerze $routeParams.

Przykładowy kontroler może ustawiać parametr routingu w obiekcie $scope do którego bindowany jest widok.

function templateController($scope, $routeParams)
{
 console.log($routeParams.myparam);
 $scope.param = $routeParams.myparam;
}

Szablony html podawane w konfiguracji wstawiane są w widoku w miejsca oznaczone atrybutem ng-view. Tak więc osobny szablon nie musi być całą stroną, a jedynie jej fragmentem. Na przykład:

<!doctype html>
<html lang="en" ng-app="routingsample">
<head>
<meta charset="utf-8">
 <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
 <script src="scripts/routingexample.js"></script>
</head>
<body ng-controller="RoutingExampleController"> 
<ul>
 <li>
  <a href="#/first/">First</a>
 </li>
 <li>
  <a href="#/second/">Second</a>
 </li>
 <li>
  <a href="#/third/">Third</a>
 </li>
</ul>
<div ng-view></div>
</body>
</html>

W takim przypadku pliki z szablonami mogą zawierać kod html bez znaczników head i body z instrukcjami angulara, np. {{param}}, gdzie do bindingu wykorzystany zostanie kontroler, który deklarujemy przy konfiguracji routingu.

wtorek, 25 września 2012

[HTML|JS|CSS] AngularJS: Templates

Poniewż aplikacje tworzone w AngularJS powinny wykorzystywać wzorzec MVC, należy wydzielić komponenty widoku, kontrolera oraz modelu danych. Za widok odpowiada szablon html, który zostaje przekompilowany do ostatecznej postaci za pomocą dyrektyw biblioteki. Kontrolerem będzie funkcja w pliku .js, natomiast model danych zostanie zaaplikowany do obiektu $scope.

Przykładowy widok poniżej prezentuje działanie dyrektyw ng-repeat, ng-controller oraz ng-click.

<body ng-controller="CountriesController"> 
<ul>
   <li ng-repeat="country in countries">
      <div>
 <span>
 Name : <strong>{{country.name}}</strong>
 </span>
 <span>
 Capital City : <strong>{{country.capital}}</strong>
 </span>
      </div>
   </li>
</ul>
<p> Total countries in Benelux : {{countries.length}} </p>
<button ng-click="removeLast()">Remove last item</button> 
</body>

Aby wykonać data-binding, należy podać nazwę kontrolera, za pomocą atrybutu ng-controller umieszczanego wewnątrz tagu body. AngularJS po napotkaniu takiej dyrektywy zacznie poszukiwanie funkcji o identycznej nazwie w plikach js. Kolejna instrukcja to ng-repeat, czyli odpowiednik pętli foreach z języka C#, Umożliwia ona binding do kolekcji obiektów i stworzenie szablonu dla pojedynczego obiektu. Za pomocą ng-click, możemy podać jaka akcja ma się wykonać po kliknięciu w element. W powyższym przykładzie będzie to funkcja removeLast zdefiniowana w kontrolerze.

//funkcja konstruktora dla country
var Country = function(name,capital)
{
 this.name = name;
 this.capital = capital;
}

function CountriesController($scope) {
 $scope.countries = [
  new Country("Netherlands","The Hague"),
  new Country("Belgium","Brussels"),
  new Country("Luxembourg","Luxembourg")
  ];
 $scope.removeLast = function(){
  var index = $scope.countries.length - 1;
  $scope.countries.splice(index, 1);
 }
} 

Funkcja kontrolera wywoływana z parametrem $scope, definiuje model danych oraz zachowania poprzez odpowiednie funkcje. Zmiany przeprowadzane na kolekcji (np. usunięcie elementu), spowodują natychmiastowe odświeżenie widoku.

Kolejną prostą do dodania funkcjonalnością jest przeszukiwanie pełno-tekstowe (Full Text Search), które nie korzysta w żaden sposób z kontrolera (!).
Aby móc przeszukiwać kolekcję, do której wpięliśmy data-binding, należy zmodyfikować ng-repeat o dyrektywę filtru.

<li ng-repeat="country in countries | filter:query">

Dodatkowo wystarczy dodać element input, do którego użytkownik będzie mógł wpisać swoje zapytanie.

<input type="text" ng-model="query">

W przypadku kolekcji obiektów przeszukane zostaną wszystkie properties i element zostanie zwrócony, jeżeli jakakolwiek zawiera podaną przez użytkownika frazę.

Aby zastosować binding w dwie strony, wystarczy dodać property query do elementu $scope w kontrolerze. Dzięki temu możemy na przykład wypisywać i resetować zapytanie.
<label>{{query}}</label>
<!-- ... -->
<button ng-click="resetQuery()">Reset query</button> 

function CountriesController($scope) {
 $scope.query = '';
 $scope.resetQuery = function(){
  $scope.query = '';
 }
} 

Binding automatycznie odświeży zarówno UI, jak i filtr wyszukiwania.

poniedziałek, 24 września 2012

[HTML|JS|CSS] AngularJS: Wprowadzenie

AngularJS to kolejny znakomity framework do tworzenia aplikacji w oparciu o wzorzec MVC. Swe działanie opiera on na możliwości rozszerzania kodu HTML o instrukcje interpretowane przez sam framework.Korzystanie z Angulara nie koliduje w żaden sposób z możliwością użycia innych frameworków. Podobnie jak np. KnockoutJS, Angular umożliwia proste deklarowanie data-bindingu. Controllery odpowiedzialne są z kolei za zachowanie strony, definiowane głównie poprzez callbacki. Za pomocą dyrektyw programista może tworzyć w prosty sposób reużywalne komponenty w postaci HTML + JS.
To tylko niektóre zalety tego frameworka, pozostałe, takie jak na przykład Routing Provider czy Dependency Injection omówione zostaną w kolejnych postach.

<!doctype html>
<html ng-app>
<head>
<meta charset="utf-8">
   <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
</head>
<body>
 
<p>Hello world ! <strong>{{4-3 + 'step'}}</strong> comes here.</p>
 
</body>
</html>

W pierwszym przykładzie kluczowe są dwa fragmenty: ng-app, który decyduje o tym, że kod html ma zostać skompilowany przez angulara. Drugi fragment to wyrażenie w podwójnych nawiasach. Służa one z jednej strony do bindingu, ale także można w nie wstawić wyrażenie javascript, które zostanie wyliczone w momencie kompilacji.