Webcomponents met Polymer – Een introductie

About

Webcomponents met Polymer – Een introductie

In dit artikel introduceren we het concept van webcomponents. Na een introductie over webcomponents gaan we inzoomen op Polymer. Dit is een webcomponent implementatie en we laten zien wat dat toevoegt om webcomponents te ontwikkelen.

Auteur: Ben Ooms

Het idee achter webcomponents is om herbruikbare componenten te hebben met de volgende eigenschappen:

  • Combineerbaar; webcomponents kunnen met elkaar gecombineerd worden. De toegevoegde waarde hierin is dat jouw webapplicatie een compositie wordt van third party en eigen wegcomponenten.
  • Encapsulatie; webcomponents encapsuleren style en HTML mark-up waardoor deze niet uitlekken in andere componenten.
  • Herbruikbaarheid; webcomponents kunnen in verschillende applicaties gebruikt worden en ook los gepubliceerd worden. Op http://webcomponents.org kun je wegcomponenten vinden en zelfs jouw eigen webcomponents publiceren.

Het doel van webcomponents is een standard te zijn om elementen voor het web te ontwikkelen. Nog niet alle browsers ondersteunen webcomponents. Om dit probleem op te lossen, zijn polyfills beschikbaar. Polyfills zijn Javascript libraries die functionaliteit toevoegen aan de browser.

Webcomponents zijn een set API’s om html tags te maken en/of aan te passen. Momenteel worden de HTML en DOM specificaties aangepast om webcomponents te onderteunen. Webcomponents zijn gebaseerd op vier specificaties. Deze zullen we stuk voor stuk toelichten. Met webcomponents maak je dus herbruikbare elementen met geïncapsuleerde styling en templates. Je heb dus de garantie dat de context waarbinnen deze componenten zich bevinden geïsoleerd zijn.

De Custom element specificatie

De “Custom element” specificatie is de specificatie die gebruikt wordt om custom elementen te kunnen ontwikkelen. Met deze API kun je jouw eigen elementen definiëren, zoals bijvoorbeeld <mijn-tag></mijn-tag>. Custom elementen bundelen gedrag en structuur.

Er zijn een aantal belangrijke regels:

  • de tag naam moet een streepje bevatten. De reden hiervoor is dat de browser onderscheid moet kunnen maken tussen reguliere tags, zoals bijvoorbeeld een <p> tag, en een custom element.
  • Custom elementen mogen maar eenmalig gedefinieerd worden.
  • Custom elementen mogen niet zelfsluitend zijn, zoals bijvoorbeeld <mijn-tag />.

De basis voor het maken van een custom tag is in listing 1 beschreven. Interessant om te noteren, is dat je hier geen Library voor nodig hebt.

Listing 1

 

Om een custom element te maken, extend je de klasse HTMLElement zoals je ziet in regel 6. Daarna registreer je deze, zoals te zien is in regel 13. In dit voorbeeld is dit inline in het HTML document verwerkt, dit is alleen voor het demonstratie doeleinde. Als je customelement.html in je browser laad, dan zal deze het element laden en de alert tonen.

Custom elementen hebben ook een lifecycle. Deze zijn:

Constructor:

De constructor wordt aangeroepen wanneer het element is gecreëerd of ge-upgrade. Het upgraden is een uitleg waardig. Wanneer in je HTML pagina een element gevonden wordt, maar het element nog niet in de browser bekend is, dan zal het element een standaard HTML element zijn en niets tonen. Wanneer de specificatie gevonden wordt, dan wordt het element ge-upgrade. Als je het voorbeeld in listing 1 neemt, dan zal bij het laden van de pagina tot regel 13 <my-custom-tag> een regulier HTML element zijn. Na het uitvoeren van regel 13 zal het element ge-upgrade worden. De constructor is de plek om de state te initialiseren, event listeners toe te voegen en de shadow DOM te laden.

Er zijn wel een aantal regels waar je jezelf aan moet houden:

  • De eerste regel van de constructor moet een parameterloze call naar de super doen om ervoor te zorgen dat de prototype chain correct geïnitialiseerd wordt.
  • Er mag geen return statement in voorkomen, behalve wanneer er een vroege escape return is of een ‘return this’ statement.
  • Je kunt de child elements niet benaderen in de constructor, immers de child nodes kunnen nog in een niet ge-upgrade toestand verkeren.
  • Het advies is om werk uit te stellen tot de connectedCallback. Let op, de connectedCallback kan meermaals aangeroepen worden tijdens de lifecycle. Als je een eenmalige actie wilt uitvoeren, da zal je een safeguard moeten inbouwen.

connectedCallback

De connectedCallback wordt aangeroepen wanneer het element aan de DOM toegevoegd wordt. Dit is de plek om bijvoorbeeld resources te laden of de rendering te doen.

disconnectedCallback

De disconnectedCallback wordt aangeroepen wanneer het element uit de DOM gehaald wordt. Dit is de plek om resources op te schonen.

attributeChangedCallback

De attributeChangedCallback wordt aangeroepen bij elke html attribuut wijziging. De methode heeft als parameters de attribuut naam, oude en nieuwe waarde.

Erg belangrijk om te noteren, is dat deze lifecycle events synchroon uitgevoerd worden. Wanneer een attribuut wijzigt, dan zal onmiddellijk de attributeChangedCallback aangeroepen worden. Vergeet de super niet aan te roepen in de eerste regel van de callback. Voor de constructor is dit een parameter loze call naar super(), voor de andere callbacks is dit super.<callback naam>, zoals bijvoorbeeld super.connectedCallback().

De shadow DOM

De shadow DOM specificatie wordt gebruikt om styling en mark-up te encapsuleren. Zoals je kunt zien in het voorbeeld in listing 1, hoef je de shadow DOM niet te gebruiken in een webcomponent. De shadow DOM gebruik je om een geïsoleerde DOM en css encapsulatie toe te voegen. Maar wat is de shadow DOM?

Ik ga ervan uit dat je weet wat de DOM is, maar een snelle uitleg is: de DOM is de representatie van het html document in een tree. Door middel van de DOM kun je het document manipuleren. Als je de DOM manipuleert, dan kan je de verandering direct in het document zien. Manipulatie van de DOM is een dure operatie. DOM manipulaties wil je daarom zo min mogelijk toepassen.

Met de shadow DOM creëer je een gescoopte DOM tree die gehost is op een element. Dit betekent dat een subset van de DOM aan het element hangt, maar niet door de browser gezien wordt. Manipulaties zullen dan de echte DOM dus niet direct manipuleren. Het element wordt dan de shadow host genoemd. Alles wat je aan de shadow DOM toevoegt, is lokaal in het element. Een interessant concept van de shadow DOM is het <slot> element. Door middel van slots geef je de gebruikers van jouw custom element de mogelijkheid om html content toe te voegen. Denk bijvoorbeeld aan een zelfgemaakte tab component, dan wil je de gebruiker van dit component de mogelijkheid bieden om tab content toe te voegen.

Een andere functie van de shadow DOM is de css scoping. Via css scoping wordt de styling van jouw component niet geraakt door de document styling, maar de gedeclareerde styling in jouw component lekt ook niet naar buiten. Dit klinkt goed maar wat als je de styling wel van buitenaf benaderbaar wilt maken? Dan kun je gebruik maken van css properties. Door middel van css properties kun je specifieke styling overschrijfbaar maken voor de gebruiker van jouw component.

HTML imports

De HTML import specificatie maakt het mogelijk HTML te kunnen importeren. Dit beperkt zich niet alleen tot het kunnen laden van componenten en afhankelijkheden, maar ook om alles te importeren wat in een HTML document kan worden geladen, zoals css en Javascript.

HTML templates

De HTML template specificatie definieert hoe je fragmenten en opmaak kunt laden wanneer je deze nodig hebt. Door middel van dit mechanisme wordt de content gedefinieerd in een <template> tag niet getoond bij het laden, maar later wanneer dit gevraagd wordt via Javascript.

Polymer

Polymer is een lichtgewicht library die het makkelijker maakt om webcomponents te ontwikkelen. Als je webcomponents wilt bouwen zonder library, dan zal je zelf veel handmatig moeten regelen. Denk aan het beheren van listeners, het binden van properties en het werken met templates. Polymer voegt een aantal features toe aan custom elementen om het ontwikkelen van webcomponents makkelijker te maken. Hieronder zullen we een aantal van deze features toelichten.

HTML templating

Als je een webcomponent ontwikkelt, dan zal je impliciet een shadow DOM definiëren in je component. Als je geen library gebruikt, dan zal dat direct in de shadow DOM innerHTML gedaan moeten worden. Polymer bied een drietal mogelijkheden voor HTML templating:

  • DOM module;
  • Definieer een template property in de constructor;
  • Overerf een DOM template van een andere Polymer element.

In listing 2 zie je het voorbeeld gebruik van de dom module. Een dom module heeft drie secties. In de template sectie komt de html en in de script sectie de behorende Javascript code. In dit voorbeeld is alle code in een enkele HTML bestand gestopt, dit kun je in verschillende bestanden uitsplitsen. Let er wel op dat de HTML template als eerste ingeladen wordt.

Listing 2

 

Een andere manier is door middel van overerving. Wanneer je een component maakt die een andere component als basis heeft, dan kan je als je geen template definieert, gebruik maken van de parent component template. Definieer je wel een template, dan zal deze de parent template niet overerven.

Een component hoeft niet verplicht een HTML template te hebben met een daarbij horende shadow DOM. In dit geval is het voldoende om een dom-module te declareren zonder template sectie of de template getter te overriden van het element die je extend. Dit is handig voor custom elementen die niet de DOM aanpassen, maar andere functionaliteit aanbieden. Polymer heeft een collectie elementen genaamd “iron elements” die functionaliteit biedt die niet direct voor rendering gebruikt worden. Denk bijvoorbeeld aan support voor ajax, applicatie routing of multilingual support.

Het data system

Polymer biedt een data systeem dat het mogelijk maakt om te reageren op data wijzigingen. Het biedt Observers callbacks, het bijwerken van computed properties (berekende properties die afhankelijk zijn van andere properties) en data binding.

Listing 3

 

Om het data system te begrijpen, is listing 3 als voorbeeld genomen. In dit voorbeeld is een property data gedeclareerd in regel 10. Deze property wijst naar het Javascript object this.data. Door middel van de accolades zijn twee data bindings in de template opgenomen. Data binding werkt door middel van paden. De reden hierachter is dat het sneller is om een waarde in een DOM pad te wijzigen, dan een potentieel groter deel van de DOM.

In dit voorbeeld zal Polymer deze data bindings observeren en propageren. Dit doet Polymer door getters hiervoor aan te maken.

Er zijn een aantal scenario’s waarbij Polymer dit niet automatisch kan doen. Dit is bij sub properties en bij het muteren van arrays. Stel je voor dat we een genest Javascript object hebben, zoals een user met een adres object met een straatnaam en huisnummer. Polymer zal een setter voor het pad user.adres maken, maar niet voor user.adres.straatnaam. Dus als de straatnaam wijzigt, dan zal Polymer dit niet propageren. Een ander scenario is het muteren van een array. Polymer maakt wel een setter voor de array, maar mutaties op de array zullen niet gezien worden, omdat de array setter niet aangeroepen wordt.

Voor beide scenario’s biedt Polymer wel helpers. Voor het observeerbaar wijzigen van een sub property kun je this.set(‘user.adres.straatnaam’, ‘de nieuwe waarde’ ) gebruiken.

Bij het wijzigen van een array gebruik je bijvoorbeeld this.push(‘array pad naam’, ‘de waarde die je wilt pushen’)

Een pad werkt altijd relatief aan het element. Dit betekent dat je geen paden direct kan benaderen van andere componenten. Wil je dit wel, dan kan dit door middel van data bindings opgelost worden. In listing 4 staat een voorbeeld van two way databinding.

Listing 4

 

In het voorbeeld van listing 4 hebben we een user profile component met een address child component. Zonder de binding address={{primaryAddress}} zou een wijziging in de address component niet zichtbaar zijn in de user profile component wanneer deze aangepast zou worden.

Voor het propageren van data wijzigingen maakt Polymer gebruik van het mediator pattern waarbij de host node de data flow regelt tussen zichzelf en de child nodes. Wanneer een host node en een child node dezelfde property delen, dan lijkt het alsof een wijziging in de property direct in de andere node gezien wordt. Dit is niet het geval, wanneer een node de property wijzigt wordt de wijziging eerst gepropageerd naar de host waarbij de host de child nodes notificeert.

De data stromen zijn synchroon. Dit betekent dat de wijzigingen direct uitgevoerd worden voordat de volgende regel uitgevoerd wordt.

Het data systeem kent twee soorten data bindings. De automatische two way binding en one way binding. De automatische binding geef je aan door middel van dubbele accolades. Als je deze gebruikt, dan zijn wijzigingen zowel van host naar children als ook van children naar de host mogelijk.

De eenrichting binding geef je aan door middel van dubbele brackets en deze geven alleen de mogelijkheid om wijzigingen te propageren van host richting children.

De ready callback

Custom elementen bieden geen eenmalige initialisatie toe wanneer een element aan de DOM toegevoegd wordt. Polymer biedt hiervoor de ready() callback die eenmalig aangeroepen wordt wanneer een element voor het eerst aan de DOM wordt toegevoegd. Polymer doet een aantal dingen in de ready callback:

  • Creëren en initialiseren van de shadow DOM;
  • Initialiseren van het data systeem, zoals het propageren van initiële waarden aan data bindings;
  • Het mogelijk maken voor observers en computed properties om uitgevoerd te worden.

Tooling

Polymer biedt een cli om Polymer projecten te ondersteunen. De polymer cli kan twee soorten projecten voor je genereren, namelijk: een project bedoeld om een polymer element te distribueren of een project om een complete web applicatie te maken. De cli kan je gebruiken om:

  • een build pipeline te maken;
  • boilerplate code te genereren;
  • een development server te starten;
  • een test runner te maken.

De Polymer cli maakt gebruik van een polymer.json voor configuratie instellingen. Daarnaast heeft het Polymer team een end-to-end test tool gemaakt genaamd Webcomponent tester. Daarmee kan je jouw elementen testen tegen de lokaal geïnstalleerde browsers of remote doormiddel van Saucelabs.

Tot slot

In dit artikel hebben we een introductie gegeven in webcomponents en hoe deze te gebruiken. Daarnaast hebben we Polymer geïntroduceerd als library om ondersteuning te bieden voor webcomponent ontwikkeling. Webcomponents bieden door hun encapsulatie een mogelijkheid tot het ontwikkelen van echte webcomponenten die geïsoleerd werken. We zijn niet op alle mogelijkheden ingegaan, maar de Polymer documentatie is zeer uitgebreid en goed te lezen. Ik hoop door middel van dit artikel je een beeld gegeven te hebben over webcomponents en hoe deze in de basis werken.

Share
May 2024
June 2024
No event found!

Related Topics