Wypróbuj

Nozbe - get things done

i zwiększ swoją produktywność

Podstawy Polymera czyli wstęp do Web Components

3

Dobra, starczy już tych wpisów podsumowujących! Dziś proponuję Ci trochę więcej „mięcha”… Na chwilę odejdziemy jednak od mojej ulubionej tematyki ReactJS. Przedstawię dziś bowiem podstawy Polymera i będzie to jednocześnie wprowadzenie do zagadnienia Web Components. Oprócz dzisiejszego wpisu, temat ten poruszę w tym tygodniu jeszcze w dwóch kolejnych wpisach. Być może jeden z nich przyjmie formę video…

Skąd mi się w ogóle wziął pomysł na wpis przedstawiający podstawy Polymera? No cóż, miałem przez chwile okazję pracować w tej technologii dla klienta firmy, w której ostatnio pracowałem. Nie powiem, nie byłem szczególnie szczęśliwy porzucając React na rzecz Polymera i Web Components. Z drugiej strony była to jednak okazja do wypróbowania czegoś z czym wcześniej nie miałem do czynienia… A poza tym dzięki temu mam teraz temat na blog posta!

No dobra, dość „pitolenia” – przechodzimy do rzeczy!

Kilka słów na temat Web Components

No cóż… Web Components to nie jest żadna super nowość w świecie web developmentu. Ogólnie rzecz biorąc jest to koncepcja re-używalnych kawałków kodu (komponentów), które zawierają szablon widoku HTML i które mogą być następnie zaimportowane w innych komponentach. Po zaimportowaniu web komponentu można używać go w kodzie tak, jakby był on zwykłym tagiem HTML. Główną intencją twórców tej koncepcji było stworzenie z Web Components standardu obsługiwanego przez wszystkie przeglądarki internetowe. Dzięki temu nie byłoby potrzebne użycie zewnętrznych bibliotek.

Niestety dobrymi chęciami jest piekło wybrukowane (czy jakoś tak). Na tę chwilę Web Components nie są oczywiście w pełni wspierane przez wszystkie przeglądarki. Z tego powodu, jeśli chcemy wykorzystać w projekcie Web Components, zmuszeni jesteśmy skorzystać z polyfilli (biblioteka implementująca coś czego nie potrafi przeglądarka). Zwykle używa się zestawu polyfilli o nazwie webcomponentsjs. W sumie nie wiem czy jest jakaś alternatywa na rynku…

Oto jak polyfille webcomponentsjs opisane są na stronie projektu:

A suite of polyfills supporting the Web Components specs:

  • Custom Elements: allows authors to define their own custom tags.
  • HTML Imports: a way to include and reuse HTML documents via other HTML documents.
  • Shadow DOM: provides encapsulation by hiding DOM subtrees under shadow roots.

This also folds in polyfills for MutationObserver and WeakMap.

Powyższy opis pokazuje, że dzięki webcomponentsjs, jesteśmy w stanie korzystać ze wszystkich funkcji komponentów webowych, we wszystkich przeglądarkach internetowych.

Czym jest Polymer?

Ok, tyle na temat komponentów webowych. Czas przejść do głównego tematu tego wpisu jakim są podstawy Polymera… Ale czym właściwie jest Polymer?

No więc… jest to biblioteka JavaScript, która zbudowana jest w oparciu o Web Components API. Poza wszystkimi zaletami jakie dają nam komponenty webowe, Polymer dostarcza dodatkowy zestaw narzędzi, które czynią tworzenie własnych komponentów łatwiejszym. Dla przykładu: biblioteka ta dostarcza deklaratywną składnię, która pomaga w definiowaniu struktury elementów, nadawaniu im styli CSS oraz dodawaniu zachowań tworzonych w języku JavaScript. Innym przykładem usprawnień Polymera dla komponentów webowych jest dostarczanie mechanizmu data-bindingu. Ale o tym akurat napiszę więcej w kolejnym wpisie.

Plik index.html

No dobra, tyle na temat nudnej teorii. Czas wreszcie trochę „pokodzić”… Jak wiadomo, najlepiej zacząć od przykładu typu „Hello world” i taki właśnie komponent stworzymy za chwilę. Najpierw musimy jednka utworzyć plik index.html i załadować polyfille:

<!doctype html>
<html>
  <head>
    <!-- import latest release version of all components from polygit -->
    <script 
      src="https://polygit.org/components/webcomponentsjs/webcomponents-lite.js">
    </script>
  </head>
  <body>
  </body>
</html><code class="xml plain"></code>

Jak widzisz, jedyne co trzeba zrobić, to załadować plik projektu webcomponentsjs.

Pierwszy własny komponent

Ok, stwórzmy więc teraz prosty komponent. Aby to zrobić, musimy stworzyć nowy plik, niech to będzie powiedzmy hello-world.html (tak, web komponenty to zwyczajne pliki HTML):

<link 
  rel="import" 
  href="https://polygit.org/components/polymer/polymer.html">
 
<dom-module id="hello-world">
  <template>
    <p>Hello world!</p>
  </template>
 
  <script>
    Polymer({
      is: 'hello-world'
    });
  </script>
</dom-module><code class="xml plain"></code>

W pierwszej linii powyższego przykładu zobaczyć możesz, że dokonujemy „importu” komponentu polymer.html. Trzeba to zrobić w każdym komponencie, który tworzysz. Możesz też w ten sam sposób importować również inne, w tym własne, komponenty webowe. Dzięki takiemu właśnie importowi, możliwe jest ich użycie w innych komponentach. Zresztą pokażę to jeszcze za moment.

Kolejna interesująca sprawą, którą możesz zauważyć jest to, że cała implementacja komponentu znajduje się wewnątrz tagu dom-module. Jest on dla nas dostępny właśnie dzięki zaimportowaniu wcześniej komponentu polymer.html. Nazwa tworzonego przez nas komponentu przypisana jest do atrybutu id elementu dom-module. Zwróć uwagę na jej zapis: używamy tutaj myślników, ponieważ nazwa ta będzie później używana jako nazwa tagu HTML reprezentującego nasz komponent.

Idźmy dalej… Wewnątrz tagu dom-module znajduje się element template. Zawiera on szablon widoku naszego komponentu. Jak przystało na wpis przedstawiający podstawy Polymera, nasz komponent ma jedynie wyświetlić tekst „Hello world!”. Dlatego też szablon ten zawiera jedynie akapit z tym właśnie tekstem.

W dalszej części komponentu znajduje się element script. W naszym bardzo prostym przykładzie zawiera on tylko to co każdy web komponent zawierać musi. Jak więc widzisz, mamy tutaj wywołanie konstruktora Polymer, do którego przekazujemy obiekt. Obiekt ten zawiera właściwość is, do której przypisana jest nazwa naszego komponentu. Dzięki przekazaniu takiego obiektu do konstruktora Polymer następuje rejestracja naszego komponentu w Polymerze. Obiekt ten może zawierać więcej właściwości o czym piszę więcej w dalszej części tego wpisu.

Użycie komponentu w praktyce

Skoro komponent jest już przygotowany, czas użyć go w praktyce. Można to zrobić chociażby w pliku index.html, co też uczyniłem – poniżej jego nowa wersja:

<!doctype html>
<html>
  <head>
    <!-- import latest release version of all components from polygit -->
    <script 
      src="https://polygit.org/components/webcomponentsjs/webcomponents-lite.js">
    </script>
 
    <!-- import custom component -->
    <link rel="import" href="./hello-world.html">
  </head>
  <body>
    <hello-world></hello-world>
  </body>
</html>

Jak widzisz, zaimportowałem komponent hello-world.html w nagłówku strony w ten sam sposób w jaki wcześniej importowałem komponent polymer.html. Jeśli spojrzysz teraz na body pliku index.html zauważysz, że użycie naszego komponentu sprowadza się do użycie go jak by był zwykłym tagiem HTML.

W tym momencie mamy już wszystko! Jeśli teraz otworzyłbyś plik index.html w przeglądarce, na ekranie powinien pojawić się napis „Hello world!”. Jeśli jesteś ciekawski to na pewno zajrzałeś do kodu wyrenderowanego przez przeglądarkę… Ja zrobiłem to już za ciebie – poniżej go prezentuję:

<!doctype html>
<html>
  <head>
    <!-- Shady DOM styles for custom-style -->
    <!-- Shady DOM styles for dom-template -->
    <!-- Shady DOM styles for dom-repeat -->
    <!-- Shady DOM styles for array-selector -->
    <!-- Shady DOM styles for dom-if -->
    <!-- Shady DOM styles for hello-world -->
 
    <style>
      body {transition: opacity ease-in 0.2s; } 
      body[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } 
    </style>
 
    <!-- import latest release version of all components from polygit -->
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-lite.js"></script>
 
    <!-- import custom component -->
    <link rel="import" href="./hello-world.html">
 
    <style>.pkt_added {text-decoration:none !important;}</style>
  </head>
  <body>
    <hello-world>
      <p class="style-scope hello-world">Hello world!</p>
    </hello-world>
  </body>
</html>

Oczywiście Polymer pododawał różnego rodzaju komentarze, style itp. Najważniejsze jest to co się stało w body. Jak widzisz, z pewnymi modyfikacjami, zawartość szablonu naszego komponentu została tutaj wstrzyknięta. Generalnie więc działa to tak, że wszystko finalnie trafia do głównego pliku aplikacji.

Przekazywanie zmiennych do komponentu

Z założenia miałem Ci jedynie przedstawić podstawy Polymera, mógłbym więc zakończyć ten wpis już w tym momencie. Pokażę Ci jednak jeszcze kilka istotnych zagadnień związanych z Polymerem. Taki dziś jestem dobry…

Na pierwszy ogień weźmy przekazywanie zmiennych do komponentu. Nie jest to nic trudnego – zmodyfikujmy co nieco nasz przykładowy komponent hello-world.html:

<link rel="import" href="https://polygit.org/components/polymer/polymer.html">
 
<dom-module id="hello-world">
  <template>
    <p>Hello world and {{andWho}}!</p>
  </template>
 
  <script>
    Polymer({
      is: 'hello-world',
      properties: {
        andWho: {
          type: String
        }
      }
    });
  </script>
</dom-module>

Po pierwsze, zajrzyj do skryptu. Jak widzisz, dodałem dodatkową właściwość do obiektu przekazywanego do konstruktora Polymer. Nazywa się ona properties i zawiera obiekt, który z kolei zawiera właściwość andWho. Jest to deklaracja zmiennej, jaką można przekazać do tego komponentu. Do właściwości andWho przypisujemy obiekt, który określa parametry tej zmiennej. W naszym przypadku definiujemy, że jest to zmienna o typie String. Więcej na temat możliwych parametrów w kolejnym wpisie (na temat data-bindingu).

Po drugie, spójrz teraz na definicję szablonu. Jak możesz łatwo zauważyć, użycie takiej zmiennej odbywa się podobnie jak w większości innych frameworków, za pomocą podwójnych nawiasów klamrowych. W przedstawiony sposób będziemy mogli przekazać do tego komponentu jakąś zmienną tekstową, która wyświetli się w zamiast „placeholdera” {{andWho}}.

Świetnie, to jak teraz przekazać tę wartość do komponentu? Bardzo prosto:

<hello-world and-who="Bartek"></hello-world>

Proste prawda? Zwróć tylko uwagę, że w komponencie zmienną deklarowaliśmy w stylu „camelCase”. W atrybutach HTML stosujemy oczywiście inną konwencję, więc poszczególne słowa w nazwie oddzielamy myślnikiem. Polymer będzie wiedział o co chodzi.

Nadawanie stylów komponentom

Na koniec pokażę jeszcze jak można nadawać style własnym komponentom Polymera. Każdy komponent może mieć wewnętrznie własne style CSS (czy jest to lepsze rozwiązanie niż style definiowane w arkuszach, to temat na osobny wpis).

Dodanie definicji stylów do komponentu ogranicza się tak na prawdę do dodania elementu style do szablonu komponentu:

<link rel="import" href="https://polygit.org/components/polymer/polymer.html">
 
<dom-module id="hello-world">
  <template>
    <style>
      :host p {
        color: red;
      }
    </style>
 
    <p>Hello world and {{andWho}}!</p>
  </template>
 
  <script>
    Polymer({
      is: 'hello-world',
      properties: {
        andWho: {
          type: String
        }
      }
    });
  </script>
</dom-module>

Nie ma tu za wiele do wyjaśniania, poza jednym. Zauważyłeś pewnie w stylach selektor :host p. Dzięki dodaniu :host wymuszamy aby dany styl dotyczył tylko tego konkretnego komponentu. Jeśli by go zabrakło, styl ten mógłby mieć wpływ również na inne komponenty.

Podstawy Polymera – podsumowanie

To tyle w temacie „podstawy Polymera”. To jednak nie koniec wpisów na temat tej biblioteki. W środę napiszę co nieco na temat data bindingu w Polymerze – zapraszam do czytania!

P.S. Prezentowany dziś przykład dostępny jest w moim repozytorium GitHub: https://github.com/burczu/polymer-introduction! Przeczytajcie też koniecznie komentarze pod tekstem – jest tam kilka wartościowych uwag dotyczących Polymera!

CHCESZ DARMOWEGO E-BOOKA?

Jeśli chcesz otrzymać mojego e-booka: Rozmowa Kwalifikacyjna - pytania z podstaw JavaScript zostaw mi swój e-mail:

Oprócz tego co poniedziałek dostaniesz maila z listą moich wpisów z poprzedniego tygodnia!

  • marioosh

    Przekonałem się do Polymera podczas długiej wędrówki w obecnym projekcie 😀 Dwie warte odnotowania rzeczy:
    1. Tak na prawdę nie musisz importować `polymer.js` w każdym komponencie, wystarczy w jednym z „rodziców”.
    2. Jeżeli definiujesz property bez żadnych dodatkowych opcji, domyślnych wartości czy obserwerów to możesz pominąć tworzenie obiektu i uprościć to do lakonicznego `andWho: String`, poza tym, jeżeli tego property nie będziesz używał z poziomu javascriptu, to nie musisz go deklarować w ogóle, a on i tak będzie widoczny w innym komponencie przez binding `{{andWho}}` 😀

    • hej! dzięki za komentarz i zwrócenie uwagi na niuanse – na pewno komuś może się to przydać! 😉

  • Polymer jest w miarę fajny do tworzenia komponentów, z tym, że mało ma wspólnego tak po prawdzie z Web Components – a zwłaszcza jego wersja 1.0. Sam się na tym sparzyłem swego czasu: http://tutorials.comandeer.pl/polymer.html

    Używanie Polymera z wersją lite polyfillu eliminuje największą zaletę nowego standardu: enkapsulację przy pomocy Shadow DOM. Zamiast tego wskakuje nam tutaj Shady DOM Polymera, który jest bardzo ograniczonym dalekim kuzynem (nawet nie odpowiednikiem). Do prawdziwego Shadow DOM-u nie wpływają żadne style ze strony. Do Shady DOM-u – a i owszem. Nie jest to przecież odizolowane pod-drzewko, na co wskazuje zrzut wygenerowanego HTML-a.

    > Dzięki dodaniu :host wymuszamy aby dany styl dotyczył tylko tego konkretnego komponentu.

    Nie. Dzięki temu wymuszany, żeby styl dotyczył konkretnego Shadow DOM, co w kontekście tego, co napisałem powyżej, brzmi po prostu śmiesznie.

    Pomijam już fakt, że HTML Imports zostało zaimplementowane tylko w Chrome i tylko tam będzie, bo reszta przeglądarek powiedziała temu rozwiązaniu „nie” i widzi rozwiązanie w modułach ES. A cały problem wczytywania w ten sposób komponentów potęguje fakt, że importy są z założenia synchroniczne (chyba że do link doda się [async]), co rozwałkowuje całkowicie system wczytywania zależności w przeglądarkach, które nie implementują HTML Imports (tam z konieczności importy lecą asynchronicznie, bo są rozwiązywane Ajaksem). Zresztą importy stosuje się niemal wyłącznie do wczytywania custom elementów – a to można rozwiązać dzisiaj o wiele, wiele bardziej elegancko z powodu nowości wprowadzonej w Custom Elements v1: customElements.whenDefined → https://www.w3.org/TR/custom-elements/#dom-customelementregistry-whendefined

Real Time Web Analytics