sobota, września 27, 2014

Najkrótsza droga do przyszłości - Polymer ;-)

Sieć Web urodziła się tak między 1989 a 1991 rokiem, kiedy to Tim Berners-Lee opublikował "HTML Tags".
Dwa lata później Internet Engineering Task Force opublikowało draft pierwszej wersji specyfikacji HTMLa, a NCSA zbudowało pierwszą popularną przeglądarkę internetową wspierającą HTMLa.

HTML był wielki.
Był prosty i elegancji. Świetnie nadawał się do tego do czego go zaprojektowano czyli do dzielenia się dokumentami i linkowania między nimi. To drugie znalazło nawet swoje miejsce w nazwie, bo HTML to nic innego jak HyperText Markup Language, czyli język znaczników do tworzenia hypertekstu. A ten hypertekst to właśnie możliwość wskazywania na inne dokumenty.

Mniej więcej w tym samym czasie czyli w 1993 roku pojawiły się pierwsze sytuacje gdzie dokumenty przestały wystarczać.
Pojawiła się specyfikacja CGI, która pozwalała na uczynienie stron "dynamicznymi", chociaż bardziej po prostu zmiennymi.
Chwilę później powstał PHP.

W 1995 do Netscape Navigatora dohackowano język skryptowy, podobno zajęło to tydzień czy dwa.
Język nazywał się przez chwilę Mocha, następnie LiveScript by w końcu zostać JavaScriptem.

Na początku służył do różnych mało istotnych rzeczy, pokazywania czasu od ostatniej zmiany strony, animowania elementów (wtedy gadał z pradziadem DOMa).

Ale w 1999 w Microsoftcie powstał XMLHttpRequest, wtedy się jeszcze inaczej nazywał, ale pojawił się pierwszy w miarę wygodny sposób "gadania" z JavaScriptu z serwerem.
Były też inne sposoby, ale XMLHttpRequest był najprostszy.

Zaczęły się czasy AJAXu... chociaż nikt tego nie zauważył do kwietnia 2004 roku gdy pojawił się GMAIL ;-)

W 2000 roku DOM na tyle dorósł, że pojawił się DOM Level 2.

No i od wtedy niewiele się zmieniło.

Pojawiały się nowe specyfikacje HTMLa, przez chwilę chciano go już nawet porzucić i zastąpić bardziej informatycznym XHTMLem (co się nie udało), wrócono znów do HTMLa w postaci HTML5.

Ale to były standardy, a rzeczywistość poszła przez dobudowywanie frameworków.
Ponieważ podstawowy framework jakim był DOM był implementowany różnie w różnych przeglądarkach zaczęły pojawiać się abstrakcje ukrywające DOM, pojawiło się jQuery, później AngularJS i inne.

W 2006 roku Google stworzyło GWT i miejmy nadzieję, że ktoś im to kiedyś wybaczy ;-)

Ale to wszystko było tylko próbami łatania tego co pojawiło się między 1991 a 2000 rokiem.

Przez to współczesne aplikacje wyglądają tak:


Są niczym innym jak zbiorowiskami DIVów (zwanymi ładnie po angielsku DIV Soup), w których istnieje jakaś hierarchia, ale nawet autorzy takich stron nie potrafią czytać kodu który interpretuje przeglądarka.

HTML/JavaScript stają się swoistym assemblerem, którego nikt zbyt dobrze nie rozumie i wszyscy dotykają się z nim przez różne fasady, które ukrywają pod sobą to co tam się dziej....

Nie jest to fajne.

Dlatego parę lat temu pojawiła się idea Web Componentów.

Web Components składają się z 4 oddzielnych specyfikacji.

Pierwszą są Custom Elements, które są niczym innym jak daniem programistom możliwości używania i tworzenia nowych elementów HTMLa.

Dzięki temu chcąc zobaczyć mapę Google w naszym kodzie możemy mając "gdzieś" zdefiniowany nowy tag użyć go:

To pozwala już zrobić zresztą wiele frameworków, które nie używają web componetów, np. AngularJS.

Drugą rzeczą, która składa się na Web Components są HTML Imports.

Od dawna tworząc stronę można było w niej wskazywać oddzielne pliki w których były skrypty JavaScript albo arkusze stylów CSS:


Jednak nie dawało się tego zrobić z HTMLem....

Do tego trzeba było używać rozwiązań server side takich jak np. JSP:


Ale dzięki HTML Imports teraz już nie trzeba tak kombinować, można użyć po prostu importu na HTMLu:

Trzecim filarem są szablony, czyli Templates.

Dzięki nim można dodawać do HTMLa fragmenty, które nie będą renderowane, ani nawet interpretowane.
Jeśli w dokumencie wstawimy kod:


<img src="http://example.com/image.jpg">

To przeglądarka nie wykona requestu pod wskazany adres! 

Dzięki temu nie trzeba już będzie robić takich rzeczy jak w AngularJS, czyli np. dodawania dodatkowych atrybutów:


<img ng-src="{{link}}">

Których AngularJS używa by "oszukać" przeglądarkę i by ta nie próbowała ładować obrazka {{link}}.

Ostatnim filarem jest Shadow DOM.

Z zewnątrz wygląda niepozornie:


Jak widzimy, w podglądzie elementów na stronie "nasz" tag, my-map jest widoczny tak jak w źródle jako <my-map>. 
Gdyby przejść przez drzewo DOM to gdy doń dotrzemy nie zobaczymy wśród jego dzieci tagu <style>, <geo-location> i im podobnych.
Dopiero gdy spojrzymy na #shadow-root to dostrzeżemy zawartość naszego taga.
To co jest w #shadow-root jest ukryte przed światem zewnętrznym, samo też nie widzi świata zewnętrznego.
Pozwala to na dobrą enkapsulację komponentów.
Style w całym dokumencie nie robią krzywdy komponentom.

Te cztery rzeczy pozwalają na tworzenie Web Componentów.

Ale używanie Web Components jest dość nieprzyjemne gdy robi się to bezpośrednio.

Dodatkowo wszystkie te 4 specyfikacje na dzień dzisiejszy implementuje tylko Chrome i Opera (przez to, że używa Chromium), Firefox wspiera natywnie tylko Custom Elements i jego zespół pracuje nad HTML Imports.

Wiemy więc, że przyszłość będzie słodka, ale jeszcze nam do niej daleko...

I tutaj pojawia się Polymer ;-)

Czyli biblioteka (bo jak podkreślają frameworkiem jest ciągle DOM), która pozwala na okiełznanie tych 4 specyfikacji w sposób prosty i przyjemny, do tego dodając jeszcze kilak miłych rzeczy takich jak data binidng, layouty, zbiór podstawowych elementów i od niedawna także Paper Elements, czyli elementy UI które pozwalają na tworzenie aplikacji zgodnych z Material Design.

Użycie tagów w Polymerze jest proste.

Wystarczy:
1) tag znaleźć (stworzyć albo znaleźć w tym katalogu tagów, lub w tym)
2) zaimportować
3) użyć

Tworzenie jest także proste.

Zaczynamy od zaimportowania Polymera i powiedzenia jak nazywa się nasz tag:


1
2
3
4
5
6
7
8
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="new-tag">
<template>
</template>
<script>
Polymer('new-tag', { });
</script>
</polymer-element>

Jak widać zaczynamy w pierwszej linii od zaimportowania Polymera (który w tym przypadku jest w katalogu bower_componets/polymer.
W linii 2 mówimy Polymerowi, że mamy nowy tag o nazwie "new-tag".
W linii 3 zaczyna się szablon, który kończy się w linii 4.
W liniach 5-7 mamy kawałek JavaScriptu który rejestruje nasz tag.
W końcu w linii 8 kończymy definicję naszego tagu.

Teraz wypada dodać kod do naszego tagu :-)


 1
2
3
4
5
6
7
8
9
10
11
12
13
14

<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="new-tag">
<template>
<style>
h1 {
color: red;
}
</style>
<h1><content></content></h1>
</template>
<script>
Polymer('new-tag', { });
</script>
</polymer-element>
W liniach 4-8 dodaliśmy styl, a w linii 9 kod naszego tagu.

Jego użycie:
<new-tag>Toster</new-tag>
Spowoduje pojawienie się na stronie:

W drzewie DOM wygląda to tak:

Co się tu dzieje?

Przeglądarka renderując tag <new-tag> wstawiła tekst w <h1> na czerwono.
Za <h1> jest odpowiedzialna linia 9, która mówi tyle by content tagu, czyli Toster wstawić w to miejsce, style w liniach 4-8 mówią zaś, że wszystko co jest w <h1> było czerwone.

Nie jesteśmy oczywiście skazani na wstawianie naszego tekstu tylko w jedno miejsce, możemy użyć <content select="..."> by wybrać to co ma się pojawić.
Gdy użycie tagu będzie wyglądać tak:
<new-tag>
<span>Toster</span>
Urządzenie do tostowania
</new-tag>
A sam tag tak:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
<polymer-element name="new-tag">
<template>
<style>
h1 {
color: red;
}
</style>
<h1><content select="span"></content></h1>
<h3><content></content></h3>
</template>
<script>
Polymer('new-tag', { });
</script>
</polymer-element>
To efektem będzie:

Dzieje się tak ponieważ wszystko co było w <span> zostało złapane przez <content select="span">, reszta zaś trafiła do <content>.
Jeżeli jednak chcielibyśmy by nasz tag był w stanie przyjmować parametry nic nie stoi na przeszkodzie, niech nasz <new-tag> zacznie udawać okrągły obrazek:


Jego użycie wygląda tak:

 <new-tag src="tn_Buffy.jpg"></new-tag>
A sam tag tak:
<polymer-element name="new-tag" attributes="src">
<template>
<style>
img {
border-radius:50px;
}
</style>
<img src="{{src}}">
</template>
<script>
Polymer('new-tag', { });
</script>
</polymer-element>
Tutaj widzimy, że nasz <new-tag> ma atrybut src, który jest ustawiany i jego zawartość jest używana w {{src}}.
Te klamrowe nawiasy {{ }} wiążą się z kolejną  rzeczą, którą daje nam Polymer, czyli z data bindngime i event bindingiem.
Jeśli mowa o eventach to ich użycie jest bardzo proste.
Niech nasz tag teraz udaje licznik:



Co zapewni nam taki kod:


 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<polymer-element name="new-tag">
<template>
<style>
h1 { color: red; }
</style>
<h1>{{counter}}</h1>
<button on-click="{{increment}}">Increment</button>
</template>
<script>
Polymer('new-tag', {
created: function() {
this.counter = 0;
},
increment: function() {
this.counter++;
}
});
</script>
</polymer-element>
Jak widzimy do tagu <button> (w linii 7) przypięliśmy metodę increment, która dokonuje (w linii 15) inkrementacji zmiennej counter.
Data binding to coś co sprawia, że Polymer nabiera kolorów.
Wyobraźmy sobie taki oto kod:
<time-source time="{{time}}"></time-source>
<digital-clock time="{{time}}"></digital-clock>
Gdzie tag <time-source> "emituje" coś do {{time}}, a <digital-clock> to "konsumuje".
Oczywiście ta emisja i konsumpcja to coś umownego, oba tagi mogą robić obie te rzeczy.

Jeśli nasz tag chce reagować na zmianę atrybutu to musimy dodać doń metodę nazwaParametruChanged, czyli dla time to będzie timeChanged:

      timeChanged: function() { }

Tu zapraszam na mojego GitHuba by obejrzeć to dokładniej.
Kolejną rzeczą, którą dodaje Polymer są layouty....

To jest akurat rzecz, której nie do końca należy jeszcze używać bo się zmieniają.

Ale istota jest taka, że Polymer dodaje zbiór atrybutów które można stosować przy wszystkich wizualnych elementach tak, że są odpowiednio ustawiane na ekranie.
Tu odsyłam do strony Polymera, bo lepiej to tłumaczą.
Do tego wszystkiego Polymer dostarcza zbioru podstawowych elementów.
Są to tak elementy graficzne jak np. <core-icon>, który dostarcza ikonki (może to być nasz obrazek lub jedna z dostarczonych ikon), jak i elementy nie graficzne, takie jak np. <core-localstorage> czy <core-ajax>.

Przykładem takiego niewizualnego tagu jest tag, którym się tu już kiedyś dzieliłem czyli tag <geo-location>:
<polymer-element name="geo-location" attributes="lat lon auto">
<template>
<style>
:host {
display: none;
}
</style>
</template>
<script>
Polymer('geo-location', {
lat: 0.0,
lon: 0.0,
created: function() {
if (this.hasAttribute("auto")) {
this.getLocation();
}
},
getLocation: function() {
var t = this;
function updatePosition(position) {
var lat = position.coords.latitude;
var lon = position.coords.longitude;
t.lat = lat;
t.lon = lon;
}
navigator.geolocation.getCurrentPosition(updatePosition);
}

});
</script>
</polymer-element>
Tag ten ma trzy atrybuty, z czego w lat i lon znajduje się geolokalizacja komputera, a auto służy do włączenia taga. Dodatkowo tag ma metodę getLocation(), którą na nim można zawołać tak jak to się robi w JavaScript'cie.
Czyli najpierw mamy:

<geo-location auto id="loc" lat="{{lat}}" lon="{{lon}}"></geo-location>

A później w skrypcie:
this.$.loc.getLocation();
Jak widać używamy tutaj "czystego" JavaScript'u.

Do tego wszystkiego Polymer dostarcza teraz zestaw elementów UI zgodnych z Material Design.

Polymer dostarcza także warstwę pośrednią, która sprawia, że wszystkie te smakołyki działają na wszystkich wiodących przeglądarkach (zwykle 2 wersje w tył).
Na Chrome i Operze są wspierane natywnie, na innych dopóki nie będą wspierane natywnie są wspierane przez wypełniacze.

Było to dość długie, ale chyba warto ;-)

A ja sobie przygotowałem chociaż jakąś wizję mojej prezentacji (bo poprzednia wersja mi się nie podobała ;-)) o Polymerze.




Podobne postybeta
Data binding w Polymerze jest oszukany ;-)
Polymeryzacja AngularJS ;-)
ToDo od Trello jako "oddzielna aplikacja"
Robimy widget do Windows 7 :-)
Z NMEA do KML (chyba najbardziej kryptyczny tytuł postu jak stworzyłem ;-))