środa, 31 października 2012

[HTML|JS|CSS] HTML5: Microdata

Ideą, która przyświecała twórcom mikrodanych jest zaszycie w znacznikach html informacji o pewnych encjach danych. Funkcjonalność taka, jest możliwa dzięki temu, że HTML5 ignoruje atrybuty których nie zna, co nie powoduje utrudnień w działaniu strony.  Dane  z jednej strony są przedstawiane użytkownikom odwiedzającym strony, z drugiej dostępne są do pobrania w formie klucz - wartość.  W związku z tym wymagane jest podanie adresu URL do opisu poszczególnych properties danego typu. Same atrybuty mikrodanych opisano poniżej:
  • itemscope - definicja kontenera na mikrodane
  • itemtype - adres URL z opisem poszczególnych pól danego typu, typy można zagnieżdżać
  • itemprop - klucz atrybut (wartość przeważnie pomiędzy tagiem otwierającym a zamykającym)

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Microdata</title>
</head>
<body>
  <div itemscope itemtype="http://www.data-vocabulary.org/Person/">
 <div>
   <span>Hello, my name is <strong itemprop="name">John Doe</strong></span>
 </div>
 <div>
   <span>I work as a <strong itemprop="role">Software Developer></strong> and I study at  <strong itemprop="affiliation"> AGH UST</strong>
 </div>
  </div>
</body>
</html>

[HTML|JS|CSS] HTML5: Web Workers

Web Workers to funkcjonalność, która umożliwia wykonywanie długotrwałych operacji w tle. Główną korzyścią jest więc responsywny interfejs użytkownika podczas wykonywania tych operacji. Kilka wad, jakie pojawiają się przy korzystaniu z WW, to przede wszystkim ograniczenia, a pośród nich brak dostępu do:
  • DOM
  • Window
  • Host page
Kolejnym problemem może być fakt, że kod wykonywany przez workera powinien być pisany tylko w JavaScripcie. W związku z tym nie możemy korzystać z popularnych bibliotek jak na przykład jQuery.  Pomimo tych problemów, wciąż mamy dostęp do takich mechanizmów, jak:
  • navigator
  • timeout
  • XHR
Aby skorzystać z Web Workera musimy użyć funkcji konstruktora Worker, komunikacja odbywa się poprzez funkcję postMessage, gdzie dane przekazuje się jako argumenty. Aby przerwać działanie wystarczy wywołać funkcję terminate().

Poniżej przykład obliczania w tle silni dla zadanej ilości liczb.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Web workers</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
 <h3>Factorial calculator</h3>
 <div>
  <span>
   <input type="number" id="count" />
   <button id="start">Start</button>
   <button id="terminate">Terminate</button>
  </span>
 </div>
 <div>
  <ol id="list">
  </ol>
 </div>
</body>
</html>

Plik ze skryptem:

var worker;

$(function(){
 $('#start').click(function(){
  $('#list').html("");
  var count = $('#count').val();
  var settings = {};
  settings.count = parseInt(count);
  worker = new Worker('factorial.js');
  worker.onmessage = function(event){
   var list = event.data;
   $.each(list, function(){
    printNumber(this);
   })
  };
  worker.postMessage(settings);
 });

 $('#terminate').click(function(){
  if(worker){
   worker.terminate();
  }
 });
});

function printNumber(number){
 $('#list').append('<li>'+number+'</li>');
}

Plik factorial.js

var results = [];

function messageHandler(e){
 if(e.data.count > 0){
  calculatefactorial(e.data.count);
 }
}

function factorial(n){
 if(n < 2){
  return 1;
 }
 else
  return n*factorial(n-1);
}

function calculatefactorial(length){
 for(var i = 1; i <= length; i++){
   results.push(factorial(i));
  }; 
 setTimeout(function(){
  postMessage(results);
 }, i*100);
 
}

addEventListener("message", messageHandler, true);

[HTML|JS|CSS] HTML5: Geolocation

Geolokacja, a więc zdolność do określania położenia geograficznego  jest kolejną funkcjonalnością dostępną w HTML5. Długość i szerokość geograficzną urządzenia klienta uzyskuje się na różne sposoby zależnie od tego, co umożliwia urządzenie, z którego wysyłamy żądanie podania położenia:
  • Adres IP
  • Sieć WiFi
  • Wbudowane urządzenie GPS
  • Bluetooth MAC Address
  • GSM/CDMA telefonu
Sposób określania położenia przekłada się na dokładność, z jaką podane nam zostaną nasze współrzędne geograficzne.

Poniżej przykład, jak w prosty sposób pobrać współrzędne:

$(function(){

 $('#getBtn').click(function(){
  navigator.geolocation.getCurrentPosition(callback,err);
 });
});

function callback(position){
 var coords = position.coords;
 $('#lat').val(coords.latitude);
 $('#lon').val(coords.longitude);
}
function err(){
 alert('An error occured');
}

Przed pierwszym pobraniem przeglądarka zapyta, czy zezwolić na pobieranie danych na temat współrzędnych.


API udostępnia kilka opcji, które możemy podać jako ostatnim argument:
  • High Accuracy (bool, default:false) - może poprawić dokładność kosztem dłuższego oczekiwania
  • Timeout (int [ms], default no value) - czas po którym strona ma zaprzestać obliczeń.
  • Maximum Age (int[ms], default: 0) - w trybie ciągłego śledzenia położenia jest to czas, po którym ma się ponownie pobrać informacja o współrzędnych.

sobota, 27 października 2012

[HTML|JS|CSS] HTML5: Offline Web Applications

Najprościej mówiąc aplikacje offline, to takie, które będą działały po utracie połączenia internetowego. Aby było to możliwe, cały dokument html wraz ze wszystkimi zależnościami musi być przechowywany na komputerze użytkownika i dodatkowo potrzebujemy mechanizmów powiadających o utracie (lub odzyskaniu) połączenia. Oczywiście nie jesteśmy w stanie przesłać wszystkiego, ale pewna ilość plików wystarczy, aby zachować funkcjonalność w trybie offline.

Do tej pory przeglądarki udostępniały tzw. Browser Cache. Jego zasada działania jest następująca : przy odwiedzeniu pewnej strony po raz pierwszy, tworzony jest dla niej folder z cache,  z pobranymi plikami (html, js, css, img). Przy kolejnych requestach o ten plik pobierany jest on z cache (jeżeli w międzyczasie nie został zmieniony), przez co czas od wysłania żądania do wyświetlenia całej zawartości strony jest znacznie krótszy. Gdy utracone zostaje połączenie, z cache pobrany zostanie tylko dokument html, bez plików js, css i plików graficznych.

Nowa funkcjonalność to Application Cache, który również jest miejscem na komputerze użytkownika, z którego strony mogą być dostarczane. Różnica polega na tym, że pliki nie są pobierane pojedynczo, a "tranzakcyjnie". Oznacza to mniej więcej tyle, że nie będziemy mogli otworzyć strony, jeżeli którykolwiek z plików nie został pobrany. Z drugiej strony, jeżeli transakcja zostanie zakończona pomyślnie, to mamy pewność, że cała zawartość strony znajduje się na naszym dysku.  

Skąd przeglądarka ma wiedzieć o tym, które pliki tworzą razem aplikację ? Mówi o tym plik manifestu, o którego strukturze nieco więcej zostanie napisane poniżej. Warto pamiętać, że zmiana dowolnego pliku aplikacji na serwerze nie spowoduje jego aktualizacji po stronie klienta. Dzieje się tak dlatego, że pliki aktualizowane są dopiero przy zmianie manifestu.

Aby móc skorzystać z funkcjonalności aplikacji offline, nie wolno korzystać z browser cache, a także należy pliki manifestu przesyłać ze specjalnym Content-Type w nagłówku. Przykładowa konfiguracja serwera w Node.js poniżej.

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

app.use(express.static(__dirname));

app.get("/cache.manifest", function(req, res){
  res.header("Content-Type", "text/cache-manifest");
  res.end("CACHE MANIFEST");
});

app.listen('1027');

Manifest

Dołącza się go w znaczniku html, może być plikiem o dowolnym rozszerzeniu, np:
<html manifest="cache.manifest">
Przykładowy plik manifestu:

CACHE MANIFEST
# version 1.2

CACHE:
script.js
HTML5_Logo.png
http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js


NETWORK:
jquery.jpg

Mamy dostępne kilka sekcji. Wersja może być przydatna, ponieważ zmiana wersji spowoduje pobranie na nowo plików przez przeglądarkę.  
CACHE - lista plików do pobrania
NETWORK - pliki, które z jakiegoś powodu muszą być pobierane zawsze
FALLBACK - zamienniki dla plików, których nie da się pobrać gdy aplikacja przejdzie w tryb offline.

W javascripcie poprzez obiekt applicationCache, możemy obsłużyć zdarzenia, jakie występują w przypadku update'u manifestu oraz pobierania plików z Application Cache.

Pełny kod z przykładem obsługi wszystkich dostępnych zdarzeń poniżej:


<!doctype html>
<html manifest="cache.manifest">
<head>
<meta charset="utf-8">
<title>Offline Web Applications</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript">
 $(function(){
 window.applicationCache.onupdateready = function(e){
  console.log('onupdateready');
  applicationCache.swapCache();
 }
});
</script>
</head>
<body>
 <h1>hello world</h1>
</body>
</html>



$(function(){
 if(window.applicationCache){
  console.log('supporting app cache');

  window.applicationCache.onchecking = function(e){
   console.log('onchecking');
  }

  window.applicationCache.oncached = function(e){
   console.log('oncached');
  }

  window.applicationCache.onupdate = function(e){
   console.log('onupdate');
  }

  window.applicationCache.onobsolete = function(e){
   console.log('onobsolete');
  }

  window.applicationCache.ondownloading = function(e){
   console.log('ondownloading');
  }
 }
});

piątek, 26 października 2012

[HTML|JS|CSS] HTML5: Web storage

Do tej pory popularną metodą przechowywania danych po stronie klienckiej były tzw, cookies. Serwer wysyłał pewne informacje, zapisywane na komputerze klienta i odczytywał je przy ponownym uruchomieniu strony. Mechanizm ten jest nadal szeroko stosowany przy projektowaniu "spersonalizowanych" stron www. Wraz z nadejściem standardu HTML5 dostajemy alternatywę w postaci Web Storage. Funkcjonalność ta umożliwia składowanie danych po stronie klienckiej bez ingerencji serwera. Dodatkowo otrzymujemy znacznie więcej dostępnej pamięci. Wprowadzono dwa rodzaje Web storage, które udostępniają identyczne API dla programistów JS:
  • Local storage
  • Session storage
Oba obiekty to properties obiektu window, więc mamy do nich łatwy dostęp po stronie skryptowej. Różnica polega na tym, że o ile pamięć local storage jest trwała (tzn. musimy ją opróżniać konkretnym poleceniem), o tyle dane zapisane w session storage zostają utracone wraz z utratą sesji (15 minut bezczynności użytkownika, zamknięcie przeglądarki, zamknięcie karty itd).
Aby uruchomić funkcjonalność Web storage należy stronę wystawiać na serwerze (nie wystarczy otworzyć w przeglądarce dokumentu).

Poniżej prosty przykład wykorzystujący API Web storage. Zapis i odczyt można wykonywać na trzy sposoby:
  • localStorage[key] = value
  • localStorage.setItem(key,value)
  • localStorage.key = value


<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Web storage</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
 <div>
  <h1>Local storage</h1>
  <span>Key: 
   <input type="text" id="lskey" />
  </span>
  <span>Value: 
   <input type="text" id="lsvalue" />
  </span>
  <span>
   <button id="lswrite">Write</button>
   <button id="lsread">Read</button>
   <button id="lsclear">Clear</button>
  </span>
 </div>
 <div>
  <h1>Session storage</h1>
  <span>Key: 
   <input type="text" id="sskey" />
  </span>
  <span>Value: 
   <input type="text" id="ssvalue" />
  </span>
  <span>
   <button id="sswrite">Write</button>
   <button id="ssread">Read</button>
   <button id="ssclear">Clear</button>
  </span>
 </div>
</body>
</html>

Obsługa API Web Storage:

$(function(){

 //local storage
 $('#lswrite').click(function(){
  var key = $('#lskey').val();
  var val = $('#lsvalue').val();
  localStorage.setItem(key,val);
 });

 $('#lsread').click(function(){
  var key = $('#lskey').val();
  $('#lsvalue').val(localStorage.getItem(key));
 });

 $('#lsclear').click(function(){
  var key = $('#lskey').val();
  var val = $('#lsvalue').val();
  if(!localStorage[key]){
   localStorage.clear();
  }
  else{
   localStorage[key] = "";
  }
 });

 //session storage
 $('#sswrite').click(function(){
  var key = $('#sskey').val();
  var val = $('#ssvalue').val();
  sessionStorage.setItem(key,val);
 });

 $('#ssread').click(function(){
  var key = $('#sskey').val();
  $('#ssvalue').val(sessionStorage.getItem(key));
 });

 $('#ssclear').click(function(){
  var key = $('#sskey').val();
  var val = $('#ssvalue').val();
  if(!sessionStorage[key]){
   sessionStorage.clear();
  }
  else{
   sessionStorage[key] = "";
  }
 });
});

wtorek, 23 października 2012

[HTML|JS|CSS] HTML5: Audio and video

Standard HTML5 wprowadza dwa nowe tagi: <audio> i <video>. Umożliwiają one osadzanie plików multimedialnych w stronach www, zapewniając jednocześnie podstawową funkcjonalność do ich odtwarzania. Jedną z głównych zalet w porównaniu z dotychczasowym sposobem odtwarzania multimediów jest brak zapotrzebowania na technologię Flash, z którą spore problemy występują np. na iPadach.

1. Tag <video>

Aby odtwarzać filmy na naszej stronie, wystarczy dostarczyć plik oraz osadzić poniższy fragment kodu na stronie. 

<video width="320" height="240" controls="controls">
 <source src="Two Steps from Hell - Heart of Courage.mp4" type="video/mp4">   
  Your browser does not support video tag
</video>

Tekst o nie wspieraniu tagu zostanie przykryty w momencie gdy przeglądarka rozpozna tag source. Dodatkowo mamy do dyspozycji kilka użytecznych atrybutów.
  • autoplay - true lub false w zależności od tego, czy film ma startować od razu przy przeładowaniu strony.
  • controls - w zależności od tego, czy występuje pojawia się pasek z kontrolkami obsługującymi film
  • loop  gdy ustawimy na true, film będzie się zaczynał od nowa po zakończeniu odtwarzania
  • poster - obrazek, który będzie wyświetlany, gdy nie zostanie odnaleziony film
  • onerror - zdarzenie błędu, które może być obsłużone w JavaScripcie.

2. Tag <audio>

W bardzo podobny sposób działa tag do odtwarzania plików dźwiękowych. W tym przypadku warto pamiętać o tym, która przeglądarka wspiera które formaty (choć oczywiście sytuacja jest dynamiczna i już wkrótce ten problem może się nie pojawiać). Spis kompatybilności przeglądarek z formatami audio tutaj. Jeżeli chcemy wspierać wiele przeglądarek, można zawsze umieścić źródła do plików w wielu formatach. Przeglądarka wybierze pierwszy wspierany.

<audio controls="controls">
 <source src="Wilderness.ogg" type="audio/ogg">
 <source src="Wilderness.mp3" type="audio/mp3">
 Your browser does not support audio tag
</audio>

Z atrybutów opisanych dla video mamy dostępne wszystkie poza poster.

poniedziałek, 22 października 2012

[HTML|JS|CSS] HTML5: Drag and drop

Jedną z nowości, jakie wprowadza standard HTML5 jest API do drag and drop. Dzięki niemu użytkownik może w obrębie okna przeglądarki przesuwać elementy. Programista dostaje z kolei kilka atrybutów, które może wykorzystać jako callbacki oprogramowane w JavaScript. Wspomniane atrybuty to:
  • draggable
  • ondragenter
  • ondragover
  • ondrop
  • ondragstart
  • ondragend
Pierwszy z nich ustawia się na true na pewnym elemencie DOM, jeśli chcemy zezwolić użytkownikowi na przesuwanie tego elementu. Pozostałe służą do powiązania z nimi odpowiednich funkcji JS. Atrybuty ondragenter oraz ondragover oraz ondrop służą do przechwytywania zdarzeń elementu, nad którym ma mieć miejsce przeciąganie, dwa ostatnie dotyczą samego elementu przeciąganego.

Ostatnim ważnym obiektem jest dataTransfer. Dostęp do niego mamy poprzez zdarzenia związane z drag and drop. Możemy w nim przechowywać dane za pomocą funkcji setData, getData, clearData. W property o nazwie effectAllowed programista może ustawić na jaki efekt zezwala użytkownikowi:
  • move - przeniesienie elementu
  • copy - skopiowanie elementu w miejscu, w którym nastąpi drop
  • link - podlinkowanie elementu poprzez data transfer 
Poniżej obszerny przykład, jak napisać prosty mechanizm drag and drop w HTML5.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Drag and drop</title>
<script type="text/javascript" src="script.js"></script>
<style>
 #target1, #target2, #target3{
  float: left; 
  width: 250px; 
  height: 250px;
  padding: 10px;
  margin: 10px;
  background-color: cyan;
 }
 #draggable1, #draggable2, #draggable3{
  width: 75px;
  height: 70px;
  padding: 5px;
  margin: 5px;
  background-color: orange;  
 } 
</style>
</head>
<body>
 <h1>Drag and drop example</h1>
 <div id="target1"
  ondragenter="return enter(event)"
  ondragover="return over(event)"
  ondrop="return drop(event)">
  <div id="draggable1" draggable="true"
   ondragstart="return start(event)"
   ondragstop="return stop">1</div>
  <div id="draggable1" draggable="true"
   ondragstart="return start(event)"
   ondragstop="return stop">2</div>
  <div id="draggable1" draggable="true"
   ondragstart="return start(event)"
   ondragstop="return stop">3</div> 
 </div>
 <div id="target2"
  ondragenter="return enter(event)"
  ondragover="return over(event)"
  ondrop="return drop(event)">
 </div>
 <div id="target3"
  ondragenter="return enter(event)"
  ondragover="return over(event)"
  ondrop="return drop(event)">
 </div>
</body>
</html>


Część skryptowa:
1. start przy rozpoczęciu przemieszczania elementu, określany jest dozwolony rodzaj ruchu oraz przechowywane jest jego id.

function start(e){
 e.dataTransfer.effectAllowed = 'move';
 e.dataTransfer.setData("Data", e.target.getAttribute('id'));
 return true;
}

2. enter- pozwala draggowalnym obiektom przemieszczać się na targetami

function enter(e){
 return true;
}

3. over - funkcja ta decyduje o tym, czy dany element może zostać opuszczony nad konkretnym kontenerem

function over(e){
 var dragid = e.dataTransfer.getData("Data");
 var id = e.target.getAttribute('id');
 if(id == "target3" && dragid == "draggable3"){
  return true;
 }
 if(id == "target2" && dragid == "draggable2"){
  return true;
 }
 return false;
}

4. drop - co ma się stać z elementem po opuszczeniu go przez użytkownika

function drop(e){
 var id = e.dataTransfer.getData("Data");
 e.target.appendChild(document.getElementById(id));
 e.stopPropagation();
 return false;
}

5. end - zakończenie drag and drop, czyszczenie dataTransfer

function end(e){
 e.dataTransfer.clearData("Data");
 return false;
}

sobota, 20 października 2012

[HTML|JS|CSS] JavaScript Patterns: Wzorce projektowe

Wzorce projektowe znane z języków obiektowych nie zawsze są łatwe do zaimplementowania w języku JavaScript. Aby uzyskać podobny efekt, często należy znać pewne specyficzne konstrukcje JS. Poniżej przedstawiono trzy wzorce przez tzw Gang of Four
. Ich implementacja w JS jest nieco inna od tej, którą wszyscy znają z języków obiektowych.

  • Singleton

Jeżeli chcemy zapewnić, aby pewien obiekt był zinstancjonowany tylko raz, można skorzystać z faktu, że funkcja jest obiektem, który może posiadać własne properties.

function MyObject(){

 if(typeof MyObject.instance !== "undefined"){
  return MyObject.instance;
 }

 this.name = "MyObject";
 this.counter = 0;

 MyObject.instance = this;
}

var o1 = new MyObject();
o1.counter++;
var o2 = new MyObject();
console.log(o2.counter);

Program wypisze 1. Problemem jest, że statyczna instancja jest publiczna, aby tego uniknąć można skorzystać z konstrukcji closure.

  • Factory

Wzorzec przydatny przy tworzeniu całych rodzin obiektów. Ma on na celu umożliwienie podjęcia decyzji o tym, który dokładnie obiekt ma zostać stworzony, podczas pracy programu. JavaScript ułatwia implementację wzorca, ponieważ warto pamiętać, że obiekty są parami typu klucz - wartość.

function VehicleMaker(){}

VehicleMaker.prototype.printType = function(){
 console.log(this.type);
};

VehicleMaker.factory = function(type){
 var constr = type,
  newvehicle;

 if(typeof VehicleMaker[constr] !== "function"){
  throw {
   name: "Error",
   message: constr + " not found"
  };
 }

 if(typeof VehicleMaker[constr].prototype.printType !== 
  "function"){
  VehicleMaker[constr].prototype = new VehicleMaker();  
 } 
 newvehicle = new VehicleMaker[constr]();
 return newvehicle;
};

VehicleMaker.Car = function(){
 this.type = 'car';
};

VehicleMaker.Bike = function(){
 this.type = 'bike';
};

VehicleMaker.Bus = function(){
 this.type = 'bus';
};

var bus = VehicleMaker.factory('Bus');
var car = VehicleMaker.factory('Car');
var bike = VehicleMaker.factory('Bike');

bus.printType();
car.printType();
bike.printType();

try{
 var whatever = VehicleMaker.factory('whatever');
}
catch(err){
 console.log(err);
}

  • Decorator

Wzorzec pozwala rozszerzać w pewien sposób jakiś obiekt w czasie pracy programu, bez z góry narzuconych reguł. Każdy obiekt po dekoracji zachowuje swoje właściwości plus dodatkowo dostaje coś ekstra. W przypadku języka JS do implementacji można wykorzystać listę dekoratorów i odpowiednią metodę, która wykona ich zadania gdy przyjdzie na to czas.

function Degree(name){
 this.name = name;
 this.decorators_list = [];
}

Degree.decorators = {};

Degree.decorators.Engineer = {
 getDegree: function(name){
  return "inz. " + name;
 }
};

Degree.decorators.Doctor = {
 getDegree: function(name){
  return "dr " + name;
 }
};

Degree.decorators.Professor = {
 getDegree: function(name){
  return "Prof. " + name;
 }
};

Degree.decorators.Print = {
 getDegree: function(name){
  console.log(name);
 }
};

Degree.prototype.decorate = function(decorator){
 this.decorators_list.push(decorator);
};

Degree.prototype.getDegree = function(){
 var fname = this.name,
  i,
  max = this.decorators_list.length,
  name;
 for(i = 0; i < max; i+=1){
  name = this.decorators_list[i];
  fname = Degree.decorators[name].getDegree(fname); 
 }
 return fname;
};

var prof = new Degree("Jan Kowalski");
prof.decorate('Engineer');
prof.decorate('Doctor');
prof.decorate('Professor');
console.log(prof.getDegree());

[HTML|JS|CSS] JavaScript Patterns: Code Reuse

JavaScript jest językiem, w którym nie występują klasy, a zatem klasyczne dziedziczenie znane chociażby z C++ musimy zastępować pewnymi wzorcami. Dzięki ich zastosowaniu będziemy mogli wiele razy używać niektórych fragmentów kodu.

  • Classical pattern

Wzorzec ten ma imitować dziedziczenie klas z języków obiektowych, stąd nazwa. Istnieje kilka sposobów jego implementacji. Poniżej przykład, w którym wykorzystuje się obiekt prototype. Zasada działania jest prosta. Jeżeli przy wywołaniu funkcji nie ma jej w konstruktorze danego obiektu, jej sygnatura poszukiwana jest w referencji __proto__ wskazującej na obiekt prototype. Podobnie rzecz ma się z properties.


function Parent1(n){
 this.name = n || 'Parent2';
}

Parent1.prototype.say = function(){
 console.log('My name is: ' + this.name);
};

function Child1(n){
 Parent1.apply(this,arguments);
}
Child1.prototype = new Parent1();

var c1 = new Child1('Child1');
console.log(c1.name);
c1.say();
delete c1.name;
c1.say();

Konsola:

Child1
My name is: Child1
My name is: Parent1

  • Klass

Ideą tego wzorca jest emulacja klas z języków obiektowych, a więc doprowadzenie do takiego API jak w poniższym przykładzie.


var car1 = new car('simple car');
car1.printName();
var car2 = new bmw('bmw car');
car2.printName();

Definicję funkcji konstruktorów wprowadza się za pomocą specjalnej funkcji klass. Przyjmuje ona funkcję konstruktora dla parenta oraz listę properties (jako obiekt), charakterystycznych dla nowego obiektu.


var car = klass(null, {
	__construct : function(name){
		console.log('constructing car...');
		this.name = name;
	},
	printName : function(){
		console.log(this.name);
	}
});

var bmw = klass(car, {
	__construct : function(){
		console.log('constructing bmw');
	},
	printName : function(){
		console.log('bmw printer');
		bmw.uber.printName.call(this);
	}
});

Cała "magia" zawarta jest w funkcji klass:

var klass = function(Parent, props){
	var Child, F, i;
	Child = function(){
		if(Child.uber && Child.uber.hasOwnProperty("__construct")){
			Child.uber.__construct.apply(this, arguments);
		}
		if(Child.prototype.hasOwnProperty("__construct")){
			Child.prototype.__construct.apply(this, arguments);
		}
	};
	Parent = Parent || Object;
	F = function(){};
	F.prototype = Parent.prototype;
	Child.prototype = new F();
	Child.uber = Parent.prototype;
	Child.prototype.constructor = Child;

	for(i in props){
		if (props.hasOwnProperty(i)){
			Child.prototype[i] = props[i];
		}
	}
	return Child;
};

Przy użyciu pomocniczego obiektu funkcji F(), obiekt Child przejmuje prototype swojego parenta podanego jako argument funkcji. Obiekty uber i prototype służą do wywoływania konstruktorów będących wyżej w hierarchii dziedziczenia w momencie konstrukcji obiektu. Ostatecznie kopiowane są również properties obiektu parent.

  • Prototypical inheritance

Jest to prostszy od powyższych i często wystarczający wzorzec, w którym mechanizm dziedziczenia polega na przekazaniu prototypu rodzica do prototypu dziecka. Warto zwrócić uwagę na to jak w obiekcie dziedziczącym zachowuje się funkcja definiowana w prototype, a jak ta z obiektu parenta.

function object(parent){
	var F = function(){};
	F.prototype = parent;
	return new F();
}

function Parent3(){
	this.name = 'Parent3';
	var that = this;
	this.print = function(){
		console.log(that.name);
	};
}

Parent3.prototype.printName = function(){
	console.log(this.name);
};

var newparent = {
	name: "ParentName"
};

var child = object(newparent);
console.log(child.name);

//errors
//child.printName();
//child.print();

var parent3 = new Parent3('Parent3');
var child3 = object(parent3);

child3.name = "Child3";
child3.printName();
child3.print();

Konsola wypisze dwa różne wyniki, ponieważ jedynie funkcja z prototypu będzie wywołana z property z obiektu dziedziczącego. Ponadto obiekt z którego chcemy dziedziczyć musi być tworzony przez operator new.

ParentName
Child3
Parent3

  • Mixin


Jest to specjalny wzorzec umożliwiający dziedziczenie z kilku prostych obiektów w jeden. Tworzy się specjalną funkcję mix, do której przekazywane są jako argumenty obiekty, które chcemy złączyć w całość.


function mix(){
	var arg, prop, child = {};
	for (arg = 0; arg < arguments.length; arg++) {
		for(prop in arguments[arg]){
			if(arguments[arg].hasOwnProperty(prop)){
				child[prop] = arguments[arg][prop];
			}
		}
	}
	return child;
}

var obj = mix({i : 1, one : "one"},
			{j : 1, two : "two"},
			{j : 2, three : "three"});

console.dir(obj);

W przypadku dwóch identycznie nazywających się properties w obiektach, wybrany zostanie ostatni.

{ i: 1, one: 'one', j: 1, two: 'two', three: 'three' }

czwartek, 18 października 2012

[HTML|JS|CSS] JavaScript Patterns: Tworzenie obiektów

Tym razem kilka wzorców projektowych charakterystycznych dla języka JavaScript, które przydają się przy tworzeniu obiektów. Z jakimi podstawowymi problemami spotyka się programista w tym języku ? Przede wszystkim jest to brak klas i przestrzeni nazw. Poniżej użyteczne wzorce.

  • Przestrzeń nazw

Spore zapotrzebowanie na zmienne globalne powoduje zaśmiecanie obiektu okna. Aby temu zapobiec tworzy się jeden globalny obiekt będący odpowiednikiem modułu reprezentującego pewne właściwości.

var mymodule = mymodule || {};
mymodule.length = 3;
mymodule.maxusers = 15;
mymodule.config = {};
mymodule.config.initialtext = "hello world";

  • Prywatne składowe

Aby uzyskać efekt prywatnych składowych obiektu można skorzystać z konstrukcji closure.

function Person(name){
 var myname = name;
 this.getName = function(){
  return myname;
 };
 this.setName = function(str){
  myname = str;
 };
}

var sb = new Person("John");
console.log(sb.myname);
console.log(sb.getName());
sb.setName("Joe");
console.log(sb.getName());

  • Wzorzec modułu

Połączenie kilku mniejszych wzorców, mające na celu utworzenie obiektu będącego modułem z prywatnymi zmiennymi, metodami itd.

var mymodule = {};
mymodule.array = {};
mymodule.array.utils = function(){
 //private members
 var logged = 0;
 var logger = function(i,e){
  logged++;
  console.log("Index:" + i + ", element: " + e);
 };
 //public members
 var totalLogged = function(){
  return logged;
 };
 var printArray = function(array){
  for (var i = array.length - 1; i >= 0; i--) {
   logger(i,array[i]);
  }
 };
 //public API
 return{
  print : printArray,
  total : totalLogged
 };
}();

var myarray = ['one','two','three'];

mymodule.array.utils.print(myarray);
console.log(mymodule.array.utils.total());

  • Sandbox

Ulepszona wersja wzorca przestrzeni nazw. Ponieważ każda funkcja jest obiektem, możemy w niej enkapsulować serwisy i przez samą funkcję się do nich odwoływać oraz zwracać je za pomocą callbacku.

function Sandbox(){
 var args = Array.prototype.slice.call(arguments),
 callback = args.pop(),
 modules = (args[0] && typeof args[0] === "string") ? args : args[0],
 i;

 if(! (this instanceof Sandbox)){
  return new Sandbox(modules, callback);
 }

 for(i = 0; i < modules.length; i += 1){
  Sandbox.modules[modules[i]](this);
 }

 return callback(this);
}

Sandbox.modules = {};
Sandbox.modules.vector = function(box) {
 box.sum = function(v){
  var sum = 0;
  for (var i = v.length - 1; i >= 0; i--) {
   sum += v[i];
  }
  return sum;
 };
 box.normalize = function(v,sumfun){
  var sum = sumfun(v);
  var arr = [];
  for (var i = v.length - 1; i >= 0; i--) {
   arr.push(v[i]/sum);
  }
  console.log(arr);
 };
};

Sandbox('vector', function(box){
 var a = [1,2,3,4];
 console.dir(box.sum(a));
 box.normalize(a,box.sum);
});

  • Statyczne pola

Aby uzyskać konstrukcję składniową statycznego pola, znaną z języków obiektowych typu C++, należy pamiętać o tym, że każda funkcja jest obiektem, do którego można dynamicznie przypisywać właściwości.

function Computer(){
 Computer.instances++;
 return {
  ram : 4096,
  hdd : 1024
 };
}

Computer.instances = 0;

var computers = [new Computer(), new Computer()];
console.log(Computer.instances);

  • Chaining pattern


Jest to wzorzec umożliwiający wywoływanie wielu funkcji w łańcuchu, co poprawia czytelność kodu i zmniejsza jego ilość, kosztem utrudnień w testowaniu.
var number = {
 value : 0,
 add : function(x){
  this.value += x;
  return this;
 },
 multiply : function(x){
  this.value *= x;
  return this;
 },
 print : function(){
  return this.value;
 }
};

console.log(number.add(10).multiply(12).print());

poniedziałek, 15 października 2012

[HTML|JS|CSS] JavaScript Patterns: Funkcje

Funkcje są powszechnie stosowane w języku JavaScript. W porównaniu z językami obiektowymi mamy dwie bardzo ważne cechy, o których nie należy zapominać: funkcje są obiektami i zapewniają osobny scope. Można je tworzyć dynamicznie, przypisywać do zmiennych, przekazywać jako argumenty funkcji i jako wartość zwracaną przez funkcję. Każda funkcja ma właściwość name, którą można wykorzystywać np. podczas debugowania.

var f1 = function(){};
function f2(){}
var f3 = function f3(){};

console.log(f1.name);
console.log(f2.name);
console.log(f3.name);

Konsola:

undefined
f2
f3

Poniżej kilka wzorców projektowych przydatnych przy pracy z funkcjami.

  • Callback

Przekazanie funkcji jako argumentu by wykonać pewne specyficzne operacje np. zapewniając dodatkowy scope.

var mycallback = function(msg){
 if(msg % 2 == 0){
  mycallback.counter++;
 }
};
mycallback.counter = 0;

var fun = function(fun){
 var j = 2;
 for (var i = 1; i < 100000; i++) {
  j += i;
  fun(j);
 }
};

fun(mycallback);
console.log('Operations finished');
console.log('Even numbers: ' + mycallback.counter);

  • Funkcja zwracająca funkcję

Funkcja może być zwracana przez inną funkcję. Zewnętrzna funkcja może się zająć pewną inicjalizacją, natomiast funkcja zwracana dokonywać modyfikacji danych.

function myfun(){
 var number = 0;
 return function(){
  number++;
  console.log(number);
 };
}

var myfunvar = myfun();
myfunvar();
myfunvar();
myfun()();

Konsola

1
2
1

  • Samo definiująca się funkcja

Bardzo użyteczny wzorzec dla opóźnionych inicjalizacji.

var sdfunc = function(){
 var name = "Mike";
 console.log('Initializing');
 sdfunc = function(){
  console.log('My name is ' + name);
 };
};

console.log('Before');
sdfunc();
sdfunc();

Konsola:

Before
Initializing
My name is Mike

  • Immediate functions

Gdy chcemy wykonać pewne operacje tylko raz i nie chcemy zaśmiecać globalnego scope nowymi zmiennymi.


(function(){
 var day = (new Date()).getDay();
 var days = ['pn','wt','sr','cz','pt','sb','nd'];
 console.log('Dzisiaj jest: ' + days[day - 1]);
})();

  • Memoization

Wzorzec używany do cache'owania wyników długich operacji, tak aby przeprowadzane były tylko raz, a przy kolejnych wywołaniach można było korzystać z uzyskanych już wyników.

var cfun = function(param){
 if(!cfun.cache[param]){
  var result = 2;
  console.log('Computing...');
  for (var i = 1; i < param; i++) {
   result += i;
  }
  cfun.cache[param] = result;
 }
 return cfun.cache[param];
};

cfun.cache = {};

console.log(cfun(55));
console.log(cfun(33));
console.log(cfun(55));

Konsola:


Computing...
1487
Computing...
530
1487

  • Currying

Nazwa wzorca pochodzi od nazwiska znanego matematyka Haskella Curry. Jego istotą jest transformacja funkcji, która przyjmuje wiele parametrów. Po transformacji będzie możliwe wywołanie funkcji jako łańcucha wywołań z pojedynczym argumentem.Szczegółowy opis tutaj.

var multiply = function(x,y){
 var oldy = y, oldx = x;
 if(typeof oldy === "undefined"){
  return function(newy){
   return oldx*newy;
  };
 }
 return x*y;
};

console.log(multiply(2,4));
var multiply5 = multiply(5);
console.log(multiply5(2));
console.log(multiply(3)(4));

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.