czwartek, 11 października 2012

[HTML|JS|CSS] Node.js: RESTful API

Aby serwer mógł komunikować się ze stroną, możemy w prosty sposób wystawić RESTowe API dostosowane do naszych potrzeb. Z użyciem Node.js oraz expressa staje się to bardzo proste. Mamy do dyspozycji cztery metody:
  • GET
  • POST
  • PUT
  • DELETE
Poniżej przykład, jak zaprojektować takie API po stronie serwerowej. Użyto w nim dwóch obiektów middleware. Obiekt express.bodyParser, jak nazwa wskazuje parsuje body requestów typu post na obiekt javascript, dzięki czemu można odwoływać się do poszczególnych propercji takiego obiektu. Użycie express.static konfiguruje ścieżkę do szablonu html względem pliku serwera.

var express = require('express');
var app = express();

app.use(express.bodyParser());
app.use(express.static(__dirname + '/www'));

Samo API można zaprojektować w taki sposób jak poniżej.

app.get('/store/:key', function(req, resp) {
 var val = store[req.params.key];
 if(val){
  resp.send(val);
 }
 else{
  resp.send('undefined',400);
 }
});

app.post('/store/', function(req, resp) {
 store[req.body.key] = req.body.value;
    resp.send('OK');
});

app.put('/store/', function(req, resp){
 store[req.body.key] = req.body.value;
    resp.send('OK');
});

app.del('/store/:key', function(req, resp) {
 var val = store[req.params.key];
 console.log(req.params.key);
 if(val){
  store[req.params.key] = undefined;
  resp.send("OK");
 }
 else{
  resp.send('undefined',400);
 }
});

środa, 10 października 2012

[HTML|JS|CSS] Node.js: Operacje na plikach

Jeżeli chcemy korzystając z Node.js przeprowadzać operacje na plikach, mamy do dyspozycji moduł fs. Obszerne API powinno wystarczyć na potrzeby większości serwerów. Do odczytu i zapisu służą odpowiednio funkcje fs.readFile oraz fs.writeFile. Przy odczycie poza nazwą pliku podaje się kodowanie oraz funkcję jako callback, w którym dostępne są odczytane dane. Przy zapisie wystarczy podać nazwę oraz dane do zapisu. Przykład użycia poniżej.

var http = require("http");
var fs = require("fs");

http.createServer(function (request, response) {
 request.on("end", function () {
  if (request.url == '/') {
   fs.readFile("samplefiles/lyrics.txt",
    'utf-8', function (error, data) {
    response.writeHead(200, {
     'Content-Type': 'text/plain'
    });
    response.end(data);
    fs.readFile("samplefiles/stats.txt", 'utf-8', 
     function(error, data){
      var content = data + '\r\n';
      content += (new Date()).toString();
      fs.writeFile("samplefiles/stats.txt",
       content);
    })
   });
  }
  else
  {
   console.log('favicon request');
  }
 });
}).listen(8080);

[HTML|JS|CSS] Node.js: Wprowadzenie

Node.js jest bardzo modnym ostatnio środowiskiem pozwalającym na tworzenie aplikacji internetowych, przeważnie web serwerów.  Aplikacje te pisane są w JavaScripcie, z całym zestawem jego wad i zalet, na pewno jednak wyróżnia je duża skalowalność, o czym można przeczytać wszędzie gdzie pisze się coś na temat Node'a. Przy projektowaniu środowiska zastosowano event - driven programming - podejście w którym wykonywanie programu determinowane jest przez zdarzenia i odpowiadające im callbacki. Zatem każda funkcja w Node.js jest asynchroniczna.

Aby uprościć architekturę wprowadza się pojęcie modułów. Są to wydzielone, logiczne fragmenty kodu, które możemy importować do Node'a za pomocą instrukcji require(nazwa modułu). Domyślnie Node.js szuka modułu w folderze o nazwie node_modules. Możemy także podać ścieżkę do naszego modułu, rozpoczynającą się od ./, np. require('./mymodules/module').

Aby zainstalować środowisko należy pobrać z tej strony instalator dla naszego systemu operacyjnego. Wraz z serwerem zainstalowany zostanie npm, manager pakietów podobny np. do ruby gems. Poleceniem

npm install <nazwa_pakietu>

możemy pobrać dowolny pakiet.

Do uruchamiania aplikacji napisanych w node warto pobrać pakiet supervisor, który należy zainstalować globalnie.

npm install supervisor -g

Po napisaniu naszej aplikacji, np w pliku mojaaplikacja.js możemy uruchamiać ją za pomocą supervisora poleceniem

supervisor mojaaplikacja.js

Dzięki temu każda zmiana w pliku mojaaplikacja.js spowoduje restart serwera.

Kolejnym pakietem, który warto zainstalować jest express, który zainstalować należy w folderze z naszym projektem.

npm install express

Jest to pakiet, pozwalający w łatwy sposób tworzyć aplikacje internetowe.

Poniżej przykład użycia własnego modułu wraz z pierwszą aplikacją z użyciem expressa.
Plik mymodule.js

var name = "Michael";

var obj = {
 speak : function(){
  console.log("My name is " + name);
 }
}

console.log('Processing module during require directive');
module.exports = exports = obj;

Plik serwera

var express = require('express');
var mymodule = require('./mymodules/mymodule.js')
var app = express();

app.get('/', function(req, res){
 mymodule.speak();
   res.send('Hello World');
});

app.listen(8080);

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');
 })
}