Tworzymy Web Component, jakim jest reaktywna zmienna – koncept, który powinniśmy znać już z pierwszych lekcji o frameworkach frontendowych. Do dzieła.
Ok, co chcemy osiągnąć:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./reactivevar.js"></script>
</head>
<body>
<p>
<reactive-variable pref="Counter: " value="0"></reactive-variable>
<br>
<reactive-variable pref="FontSize : " value="0" suff="px"></reactive-variable>
</p>
<p>
<reactive-variable pref="FontSize : " value="0" suff="px"></reactive-variable>
</p>
</body>
</html>
Swoją drogą od razu uwrażliwię na oczywiste ograniczenia web componentów:
- Nie mogą być samozamykające się, a jeżeli są, to wstawienie kilku pod rząd jako rodzeństwo może sprawić, że nieprawidłowo będą wyświetlane (będzie autozamykanie)
- Nazwy atrybutów nie mogą przesłaniać nazw atrybutów z HTMLElement, z którego dziedziczymy (np. prefix, suffix)
- Będziemy operować w shadow domie, zatem atrybuty będą mogły się zmieniać, ale zapomnijmy o takim komforcie jak zmienianie atrybutów strzałkami w devtoolsach – to robić możemy, ale by zaobserwować zmianę, musimy wcisnąć enter
Ok, tworzymy naszą klasę:
class ReactiveVar extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<span></span>
`;
this.pref = null;
this.suff = null;
}
}
customElements.define('reactive-variable', ReactiveVar);
To chyba oczywiste. Teraz metoda pomocnicza, która skleja prefix, suffix i treść do siebie:
_getNewTextValue(){
let prefix = this.pref ?? '';
let suffix = this.suff ?? '';
let value = this.getAttribute('value');
return `${prefix}${value}${suffix}`;
}
Ok, teraz connectedCallback, metoda, która ruszy po podłączeniu elementu:
connectedCallback(){
if(this.hasAttribute("pref")){
this.pref = this.getAttribute("pref");
}
if(this.hasAttribute("suff")){
this.suff = this.getAttribute("suff");
}
if (this.hasAttribute('value')) {
const span = this.shadowRoot.querySelector('span');
span.textContent = this._getNewTextValue();
}
}
Czyli co robimy:
- sprawdzamy, czy jest atrybut pref i dopisujemy jego wartość
- sprawdzamy, czy jest atrybut suff i dopisujemy jego wartość
- sprawdzamy, czy jest value i wpisujemy jako textContent do naszego spana z shadow roota korzystając z metody pomocniczej
Swoją drogą jeżeli lubimy tak jak ja czytać sobie do poduszki dokumentację MDN to możemy przypomnieć sobie, że klasa Node ma taką property isConnected:
let test = document.createElement("p");
console.log(test.isConnected); // Returns false
document.body.appendChild(test);
console.log(test.isConnected); // Returns true
To powinno nam nieco rozjaśnić, czym jest connectedCallback. Ok, teraz obsługa zmiany atrybutów – tylko value chcemy obserwować:
attributeChangedCallback(name, oldValue, newValue) {
if(oldValue === newValue)
return;
if(name === "value"){
this.shadowRoot.querySelector('span').textContent = this._getNewTextValue();
}
}
static get observedAttributes() {
return ['value'];
}
I tylko value obserwujemy (statyczna metoda observedAttributes). Sam observer sprawdza, czy wartości się od siebie różnią, jeżeli tak, to wykonuje getNewTextValue.
Chyba proste, mam nadzieję, że coraz lepiej web komponenty ogarniamy.