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

wtorek, 3 listopada 2015

[HTML|JS|CSS] ES6: Classes

Nowa specyfikacja EcmaScript wproawdza słowo kluczowe class, by znacznie uprościć budowanie obiektowych konstrukcji w JS. Do tej pory obiekty tworzyliśmy między innymi za pomocą konstruktorów (funkcja wołana ze słowem kluczowym new), natomiast za dziedziczenie odpowiadało property prototype. ES6 wprowdza klasy, aby uporządkować te mechanizmy. Jedynym celem, dla którego powinniśmy używać klas jest tworzenie obiektów.

Klasy możemy definiować na dwa sposoby:

// class declaration
class Car
{
  constructor(make)
  {
    this.make = make;
  }
}

var ford = new Car("Ford");
console.log(ford.make);

console.log(typeof Car); //function

// class expression syntax
var Plane = class {
  constructor(make)
  {
    this.make = make;
  }
}

var plane = new Plane("Airbus");
console.log(plane.make);

Poza konstruktorami, klasy mogą zawierać regularne funkcja (które zostaną dodane do prototypu obiektu), oraz settery i gettery pozwalające na zaimplementowanie reguł enkapsulacji (jak na przykład w C#). Specjalnym rodzajem metod są metody statyczne - nie zostaną one dodane do prototype, tylko do samej klasy.

class Tank
{
  constructor()
  {
    this._name = "";
  }

  get name(){
  	console.log("getting name");
  	return this._name;
  }

  set name(value){
  	console.log("setting name");
  	this._name = value;
  }

  fire(){
  	console.log("fire !");
  }

  static report(){
  	console.log("reporting !");
  }
}


var abrams = new Tank();
abrams.name = "Abrams";
console.log(abrams.name);
abrams.fire();
Tank.report();

console.log(typeof abrams.__proto__.fire); //function
console.log(typeof abrams.__proto__.report); //undefined

Klasy niosą za sobą także możliwość dziedziczenia, reprezentowaną przez słowo kluczowe extends. Dziedziczyć można po klasie lub bezpośrednio po konstruktorze. Drugie, nowe słowo kluczowe to super. Służy ono do wywołania konstruktora bazowego oraz do odwołania się do metod z bazowego konstruktora.

function A(a)
{
  this.a = a;
}

A.prototype.printA = function(){
  console.log(this.a);
}

class B extends A
{
  constructor(a, b)
  {
    super(a);
    this.b = b;
  }

  printB()
  {
    console.log(this.b);
  }

  static sayHello()
  {
    console.log("Hello");
  }
}

class C extends B
{
  constructor(a, b, c)
  {
    super(a, b);
    this.c = c;
  }

  printC()
  {
    console.log(this.c);
  }

  printAll()
  {
    this.printC();
    super.printB();
    super.printA();
  }
}

var obj = new C(1, 2, 3);
obj.printAll();

C.sayHello();
asdasd

niedziela, 1 listopada 2015

[HTML|JS|CSS] ES6: Promise

Pisanie asynchronicznego kodu nigdy nie było zbyt przyjemne w JS. Między innymi dlatego zdecydowano się na zaimplementowanie dobrze znanego wzorca Promise, jako natywnego mechanizmu JS. Od wersji ES6, obiekty Promise reprezentujące asynchroniczną operację, tworzone są konstruktorem. Funkcja, którą przyjmuje konstruktur powinna reprezentować asynchroniczną operację (np. AJAX-ową), zakonczoną wywołaniem callbacka resolve lub reject.

var timeoutPromise = new Promise(function(resolve, reject){

  setTimeout(function(){
   var d = new Date();
 var milis = d.getMilliseconds();
 if(milis % 10 === 0){
  reject("an error occured");
 }
 else{
  resolve(d);
 }
  }, 1000);

});

timeoutPromise.then(function(data){
 //onFulfilled
 console.log(data);
}, function(err){
 //onError
 console.log(err);
})


timeoutPromise.then(function(data){
 console.log(data);
 return "processed"; //returns next promise
}, function(err){
 console.log(err);
}).then(function(data){
 console.log(data);
});

Promise'y można tworzyć z synchronicznych obiektów metodami Promise.resolve, np. Promise.resolve(13). Dodatkowo przydatne są metody all i race, które pozwalają obsługiwać wiele asynchronicznych operacji. Pierwsza z nich zwróci callback, kiedy wszystkie promise'y zostaną wykonane, druga w przypadku, gdy wykona się którykolwiek z nich.

var p1 = new Promise(function(resolve, reject){
  setTimeout(function(){
    resolve();
    console.log("p1");
  }, new Date().getMilliseconds());
});

var p2 = new Promise(function(resolve, reject){
  setTimeout(function(){
    resolve();
    console.log("p2");
  }, new Date().getMilliseconds());
});


Promise.all([p1, p2]).then(function(){
  console.log("All done");
});

Promise.race([p1, p2]).then(function(){
  console.log("First done");
});

sobota, 31 października 2015

[HTML|JS|CSS] ES6: Strings, arrays, collections

Strings

Poza kilkoma dodatkowymi metodami na obiekcie stringa, takimi jak includes, startsWith, endsWith, znanymi z innych języków programowania, najważniejsza zmiana to tzw. template strings - wyrażania opakowane w backticki, służące do wygodnego budowania stringów.

let a = 20;
let b = 6;
let c = "JavaScript";
let str = `My age is ${a+b} and I love ${c}`;

console.log(str);

Arrays 

Metoda from przypomina select z C# z możliwością wstawienia opcji jako trzeci parametr. Dodatkowo wprowadzono metodę fill do wstawiania danej wartości w istniejącej już tablicy (z możliwością zdefiniowania granic wstawiania). Kolejne ważne metody to find oraz findIndex przyjmujące predykat.

Collections

W ES5 mieliśmy do dyspozycji tylko tablice. Teraz dochodzą nowe kolekcje, takie jak:
  • Array buffer - wydajna kolekcja przechowująca liczby, nie może rosnąć dynamicznie
  • Typed array - wrapper na array buffer pozwalający w prosty sposób czytać z niej i zapisywać obiekty
  • Set - kolekcja unikalnych wartości dowolnego typu, powtarzające się wartości są wyrzucane
  • Weak Set - w odróżnieniu od set przechowuje jedynie referencje do obiektów
  • Map - kolekcja par klucz  wartość
  • Weak Map - w odróżnieniu od map kluczami mogą być jedynie referencje obiektów

let set = new Set("Hello!!!");
set.add(12);
console.log(set.has("!")); //check if value exists
console.log(set.size);
set.delete(12);
console.log(...set); //H e l o !
set.clear();

let map = new Map();
let o = {n: 1};
map.set(o, "A"); //add
map.set("2", 9);
console.log(map.has("2"));
console.log(map.get(o));
console.log(...map);
map.delete("2"); //remove key with value
map.clear();

[HTML|JS|CSS] ES6: Syntax

Nowa specyfikacja ECMAScript wnosi sporo nowości do JavaScriptu. Nie możemy ich jeszcze w pełni wykorzystywać bez potrzeby transpilacji, ale kwestią czasu jest, kiedy przeglądarki zaimplementują wszystkie nowości w swoich engine'ach. W tym momencie do transpilacji można używać np. babeljs, będącego pakietem node.js. Instalujemy go poprzez wywołanie

npm install -g babel-cli
npm install --save-dev babel-preset-es2015
babel --presets es2015 script_es6.js --out-file script_es5.js

Ostatnie polecenie reprezentuje transpilację z jednego pliku do drugiego. Od kilku dni należy do tego celu wykorzystywać preset es2015, wcześniej transpilacja była domyślną operacją.

Najważniejsze zmiany składniowe w ES2015 (ES6):

let keyword

Deklaruje zmienną typu block-scoped, co jest naturalne w większości języków programowania, w JS nie było, kiedy mieliśmy do wyboru tylko var (function-scoped variables).

function myFunction()
{
  console.log(a);

  let b = 13; //accessible throughout function
  console.log(b);

  if(true)
  {
    let c = 14; //accessible throughout function
    let b = 15;
    console.log(b);
  }

  //console.log(c); //c is not defined
}

Zmienne możemy nadpisywać poprzez let, ale tylko jeżeli są w innym block-scope. Zaleca się używanie let zamiast var, ze względu na zmniejszenie podatności na bugi takiego kodu.

const keyword

Zmienne typu read-only, ostatnia linijka zwróci SyntaxError podczas transpilacji, a docelowo także błąd w run-time. Zmienne typu const są również block-scoped.

const pi = 3.141;
var r = 2;

console.log(pi * r * r); //Output "12.564"

pi = 12; //throws read-only exception

 W przypadku obiektów deklarowanych jako const będziemy mogli modyfikować ich propercje, a błąd dostaniemy tylko przy próbie nadpisania całego obiektu.

default parameter values

W ES5 nie musieliśmy przekazywać wszystkich argumentów wywołując funkcję. Nieprzekazane zmienne przyjmowały wartość undefined. Teraz możemy od razu zadeklarować dla każdego parametru domyślne wartości.

function myFunction(x = 1, y = 2, z = 3){
 console.log(x, y, z); //Output "6 7 3"
}

myFunction(6, 7);

spread operator

Operator ten reprentują ... , a jego zadaniem jest rozdzielenie obiektu typu iterable na pojedyncze elementy. Po transpilacji otrzymamy metodę apply (przyjmuje listę parametrów jako tablicę) wołaną na funkcji, ale docelowo wykorzystane będą znacznie szybsze mechanizmy w runtime.

function myFunction3(a, b)
{
 return a + b;
}

let data = [1, 4];
let result = myFunction3(...data);
console.log(result); //Output "5"

Można go wykorzystać także przy budowaniu tablicy z innej tablicy.

rest parameter

Trzech kropek można także użyć do obsługi funkcji ze zmienną liczbą argumentów. Rest parameter zmapuje nadmiarowe zmienne jako tablicę.

function myFunction(a, b, ...args)
{
  console.log(args); //Output "3, 4, 5"
}

myFunction(1, 2, 3, 4, 5);

array destructuring assignment

Pozwala na przypisanie wartości do wielu zmiennych za pomocą jednego wyrażenia. Prawą stroną musi być obiekt typu iterable. Array destructuring pozwala na ignorowanie niektórych elementów oraz użycie spread operator-a.

let myArray2 = [1, 2, 3];
let [g, h, i] = myArray2;
let [j,  , k] = myArray2;
let [l, ...m] = myArray2;

object destructuring assignment

W analogiczny sposób możemy wykonywać destructuring na obiektach.

let object = {"name" : "John", "age" : 23};
let {name, age} = object; //object destructuring assignment syntax

arrow function

Przypomina dobrze znane np. z C# lambda expression. Więcej niż jedną linię należy opakowywać w nawiasy klamrowe.

let circleArea = (pi, r) => pi * r * r;
let result = circleArea(3.14, 3);

let circleArea2 = (pi, r) => {
 let area = pi * r * r;
 return area;
};
let result = circleArea2(3.14, 3);


enhanced object literals

Wprowadzono kilka udogodnien składniowych przy budowie obiektów. Mamy do dyspozycji skróconą składnię przy definiowaniu propercji (również tych z wyliczanymi nazwami) oraz metod, 

let x = 1, y = 2;
let object = { x, y };

console.log(object.x); //output "1"

let object = {
  ["first" + "Name"]: "Eden",
};

//extract
console.log(object["first" + "Name"]); //Output "Eden"

let object = {
  myFunction(){
    console.log("Hello World!!!"); //Output "Hello World!!!"
  }
}

sobota, 16 listopada 2013

[HTML|JS|CSS] jQuery: Pluginy i rozszerzanie

Na githubie aż roi się od przeróżnych pluginów do jQuery pisanych przez mniej lub bardziej sprawnych programistów. Jak napisać własny plugin ? Przykład poniżej.

(function($){

 $.fn.print = function(props) {
  var functions = $.fn.print.functions;
  if ($.isArray(props)) {
   functions = $.merge(functions, props);
  }
  return this.each(function() {
   var element = $(this);
   functions.forEach(function(fn){
    console.log(fn, ': ', $(element)[fn]());
   });
  });
 };

 $.fn.print.functions= ['height', 'width'];

})(jQuery);

Trick z immediate function to zabezpieczenie na wypadek, gdyby aplikacja używała $ (który jest aliasem dla obiektu jQuery) do innych celów. Pisząc plugin rozszerza się obiekt fn, który jest referencją na prototype obiektu jQuery. Korzystając z faktu, że każda funkcja jest obiektem, możemy załączyć do niej obiekt, który może zostać wykorzystany, jako domyślne ustawienia pluginu.

Plugin wywołujemy na dowolnym selektorze, na przykład:

$(function(){
 $('div').print();
 $('a').print(['size']);
 $('button').print(['html']);
});

Drugi sposób na uzyskanie podobnego efektu, to funkcja extend z jQuery, którą wykorzysujemy do łączenia dwóch obiektów JavaScript w jeden. Wywołanie tej funkcji na $.fn spowoduje rozszerzenie jQuery o naszą funkcję.

(function($){

 $.fn.extend({
  count: function(){
   console.log(this.length);
  }
 });

})(jQuery);

$(function(){
 $('div').count();
});

sobota, 9 listopada 2013

[HTML|JS|CSS] jQuery: Lightbox

Lightbox to popularna na stronach internetowych technika wyświetlania obrazów z wyszarzonym tłem, znana na przykład z allegro. W tym poście przedstawione zostanie, jak zaimplementować taką funkcjonalność z użyciem jQuery.


Odnośniki do obrazków będą zwykłymi tagami typu <a>. Po kliknięciu w odnośnik zostaną dodane dwie warstwy: zewnętrzna (overlay) i ta, na której wyświetlany jest obrazek (lightbox).

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8" />
 <title>Lightbox</title> 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
    <script src="scripts/myscript.js"></script>
</head>
<body>
 <h2>Nice cars list</h2>
 <ul>
  <li>
   <a href='/images/bmw_seria6.jpg' class='lightbox'>BMW Seria 6</a>
   <a href='/images/hyundai_genesis.jpg' class='lightbox'>Hyundai Genesis</a>
   <a href='/images/lexus_ct200h.jpg' class='lightbox'>Lexus Ct200h</a>
   <a href='/images/mercedes_slk.jpg' class='lightbox'>Mercedes SLK</a>
   <a href='/images/nissan_350z.jpg' class='lightbox'>Nissan 350z</a>
  </li>
 </ul>
</body>
 <style>
  #lightbox_overlay {
   position:absolute;
   top:0;
   left:0;
   height:100%;
   width:100%;
   background:black url(spinner.gif) no-repeat scroll center center;
  }
  #lightbox {
   position:absolute;
  }
 </style>
</html>

Overlay pozycjonowany jest absolutnie z wymiarami na 100%. Pozycję lightboxa wylicza się tak, aby trafił na środek ekranu. Animacja opacity daje efekt półprzezroczystej warstwy.

$(document).ready(function(){
 $('a.lightbox').click(function(e) {
  $('body').css('overflow-y', 'hidden');
  $('<div id="lightbox_overlay"></div>').css('top', $(document).scrollTop())
  .css('opacity', '0')
  .animate({'opacity': '0.5'}, 'slow')
  .appendTo('body');
 $('<div id="lightbox"></div>')
  .hide()
  .appendTo('body');
 $('<img />')
  .attr('src', $(this).attr('href'))
  .load(function() {
   positionLightboxImage();
  })
  .click(function() {
   removeLightbox();
  })
  .appendTo('#lightbox');
  return false;
 });
 });

function positionLightboxImage() {
 var top = ($(window).height() - $('#lightbox').height()) / 2;
 var left = ($(window).width() - $('#lightbox').width()) / 2;
 $('#lightbox')
  .css({
   'top': top + $(document).scrollTop(),
   'left': left
  })
 .fadeIn();
}

function removeLightbox() {
 console.log('asd');
 $('#lightbox_overlay, #lightbox')
  .fadeOut('slow', function() {
   $(this).remove();
   $('body').css('overflow-y', 'auto');
  });
}

sobota, 17 sierpnia 2013

[HTML|JS|CSS] Wprowadzenie do TypeScript

TypeScript to język stosunkowo nowy (wprowadzony w drugiej połowie 2012 roku), opracowany przez Microsoft i pana Hejlsberga, mocno kojarzonego z językiem C#. TS ma być rozszerzeniem JavaScriptu wprowadzającym pewne znane z języków obiektowych konstrukcje. Pliki o rozszerzeniu .ts kompilowane są przez kompilator TypeScript do czystego JavaScriptu. Produktem, który idzie w parze z nowym językiem, jest Visual Studio 2012, ale sam kompilator jest darmowy i można go pobrać na przykład menadżerem pakietów Node'a, wpisując polecenie:

npm install typescript

Pliki TypeScript kompilujemy z linii poleceń komendą tsc, na przykład polecenie:

tsc sample.ts

utworzy plik o nazwie sample.js lub (oraz) wypisze błędy składniowe na konsoli.
A oto, co więcej poza czystym JavaScriptem daje programistom TypeScript:

  • Klasy i typowalność
Klasy wprowadzają pewną strukturyzację kodu, co jest nieco utrudnione w JS, gdzie obiektami są funkcje. Typowalność z jednej strony zapewnia walidację, z drugiej podpowiadanie składni w VS 2012. Przykład:


//1. classes

class Customer {
    customerId: number;
    companyName: string;
    contactName: string;
    country: string;

    addCustomer(): number {
        return 0;
    }
}

var cust = new Customer();
cust.companyName = "Google";

i wynik kompilacji:

//1. classes
var Customer = (function () {
    function Customer() {
    }
    Customer.prototype.addCustomer = function () {
        //some code here
        return 0;
    };
    return Customer;
})();

var cust = new Customer();
cust.companyName = "Google";

  • Modyfikatory dostępu
Properties w TS mogą być prywatne lub publiczne (domyślnie). Podobnie jak np, w C# do prywatnych nie mamy dostępu spoza klasy. Próba przypisania wartości spowoduje błąd kompilacji. Modyfikatory dostępu wpływają też na to, które properties w VS podpowiada nam IntelliSense.

//2. Access modifiers

class Employee {
    private customerId: number;
    public companyName: string;
    public contactName: string;
    private country: string;

    getEmployeeId(): number{
     return this.customerId;
    }
}

var emp = new Employee();
emp.companyName = "Google";
//error: 'Employee.country is inaccessible'
//emp.country = "USA";


  • Konstruktory, statyczne properties
Wszystko działa dokładnie tak, jak w językach statycznie typowanych.


//3. Constructors, static members

class Person {
    public personId: number;
    public firstName: string;
    public lastName: string;
    static totalPeople: number;

    constructor(){
     Person.totalPeople = Person.totalPeople + 1;
     this.personId = Person.totalPeople;
    }
}

Person.totalPeople = 0;
var p1 = new Person();
var p2 = new Person();
console.log(Person.totalPeople); //2


  • Dziedziczenie
Dziedziczenie, czyli większa reużywalność kodu,


//4. Inheritance

class Driver extends Person{
 public carMake: string;

 constructor(make: string){
  this.carMake = make;
  super();
 }

 getId(): number{
  return this.personId;
 }
}

var dr1 = new Driver("Ford Fiesta");
console.log(dr1.getId());

Ponadto mamy możliwość definiowania interfejsów (słowa kluczowe inteface i implements), odpowiadających interfejsom z C# oraz modułów (słowo kluczowe module) odpowiadających przestrzeniom nazw w C#.

czwartek, 28 marca 2013

[HTML|JS|CSS] HTML5: WebSQL

WebSQL jest jedną z tych funkcjonalności, które pomimo iż kojarzone są ze standardem HTML5, nie znajdą się w tym standardzie. Mimo wszystko wiele przeglądarek wspiera możliwość składowania danych w relacyjnej bazie po stronie klienta. API dostępne z JavaScript pozwala na pisanie zapytań w języku SQL, a dane składowane są w pliku (podobnie jak w SQLite). Jeżeli chodzi o kompatybilność z nowoczesnymi przeglądarkami, to problem pojawia się w przypadku Firefoxa i Explorera, co wyklucza sporą część użytkowników (link).

Aby połączyć się z bazą wykorzystujemy funkcję openDatabase:

var db = openDatabase('mydb', '1.0', 'my first database', 2 * 1024 * 1024);

Jeżeli baza nie istnieje to zostaje automatycznie utworzona. Instrukcje SQL wykonywane są w funkcjach zapewniających tranzakcyjność (jeżeli którekolwiek z zapytań się nie powiedzie, cała operacja zostanie cofnięta).

$("#create").click(function(){
 db.transaction(function (tx) {
  tx.executeSql('CREATE TABLE messages (id unique, text)');
 });
});

Tranzakcje można zagnieżdżać, gdyż w callbackach odczytujemy wyniki zapytań.

$("#store").click(function(){
 var value = $("#msg").val();
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages"', [], function(tx,results){
   var i = results.rows.length + 1;
   tx.executeSql('INSERT INTO messages (id, text) VALUES (?,?)',
    [i, value]);
  });
 });
 $("#msg").val("");
});

Dane, które chcemy wyświetlić, można filtrować zgodnie ze składnią SQL.

$("#restore2").click(function(){
 var items = $("#items");
 items.html("");
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages" WHERE LENGTH(text) > 5', 
   [], function(tx,results){
   var l = results.rows.length;
   for (var i = 0; i < l; i++) {
    var item = results.rows.item(i);
    var li = $("<li>");
    $(li).html(item.id + ", " + item.text);
    items.append(li);
   };
  });
 });
});

Usuwanie z tabeli wykonuje się oczywiście przy użyciu instrukcji DELETE.

$("#removeLast").click(function(){
 var value = $("#msg").val();
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages"', [], function(tx,results){
   var i = results.rows.length;
   var sql = 'DELETE FROM "messages" WHERE "id" = ' + i;
   tx.executeSql(sql);
  });
 });
});

Na koniec warto pamiętać, że podobnie jak w przypadku IndexedDB, możemy łatwo oglądać stan naszych tabel w przeglądarce Google Chrome.