Laskurit
Yhteensä:
-
Nollaa molemmat
CSS
/* Esikatselualue määrittelee sekä taustan että tekstin värin, jotta pari on varmasti luettava kummassakin teemassa. */ #alue { background-color: #ffffff; color: #1a1a1a; padding: 0.5rem; } @media (prefers-color-scheme: dark) { #alue { background-color: #1e1e1e; color: #f0f0f0; } } laskuri-elementti { display: block; margin-bottom: 0.5rem; padding: 0.3rem; border: 1px solid currentColor; } laskuri-elementti:not(:defined) { visibility: hidden; } /* Komponentin sisältö on tavallista DOM-puuta, koska shadow rootia ei ole. Siksi sivun tyylit pätevät siihen. */ laskuri-elementti .arvo { display: inline-block; min-width: 3rem; font-weight: bold; }
HTML
<div id="alue"> <h2>Laskurit</h2> <laskuri-elementti arvo="10" askel="5"></laskuri-elementti> <laskuri-elementti arvo="0" askel="1"></laskuri-elementti> <p>Yhteensä: <output id="summa">-</output></p> <p><button type="button" id="nollaa">Nollaa molemmat</button></p> </div>
Javascript
/* suorita tämä koodi Run-painikkeella */ /* Pohja luodaan kertaalleen eikä joka elementille erikseen. innerHTML on tässä turvallinen, koska merkkaus on kirjoitettu itse eikä se sisällä käyttäjän dataa. */ if (!customElements.get("laskuri-elementti")) { const POHJA = document.createElement("template"); POHJA.innerHTML = ` <output class="arvo"></output> <button type="button" class="vahenna">-</button> <button type="button" class="kasvata">+</button> `; class Laskuri extends HTMLElement { /* Vain tässä listatut attribuutit laukaisevat takaisinkutsun */ static get observedAttributes() { return ["arvo", "askel"]; } constructor() { super(); /* Käsittelijä sidotaan, jotta sen voi myöhemmin poistaa samalla viitteellä */ this.klikattiin = this.klikattiin.bind(this); } /* Attribuutti ja ominaisuus kulkevat käsi kädessä: arvosta on vain yksi totuus, ja se on attribuutissa. */ get arvo() { return Number(this.getAttribute("arvo") ?? 0); } set arvo(uusi) { this.setAttribute("arvo", uusi); } get askel() { /* Attribuutit ovat aina merkkijonoja, joten numero on muunnettava */ return Number(this.getAttribute("askel") ?? 1); } connectedCallback() { /* Sisältö rakennetaan vain kertaalleen: connectedCallback laukeaa uudelleen, jos elementti siirretään DOM-puussa. */ if (this.naytto === undefined) { this.replaceChildren(POHJA.content.cloneNode(true)); this.naytto = this.querySelector(".arvo"); this.paivita(); } this.addEventListener("click", this.klikattiin); } disconnectedCallback() { this.removeEventListener("click", this.klikattiin); } attributeChangedCallback(nimi, vanha, uusi) { /* HUOM. Tämä laukeaa jo alkuarvojen asettamisessa, ENNEN connectedCallback-metodia - eli ennen kuin näyttöä on olemassa. Ilman tarkistusta ensimmäinen kutsu kaatuisi. */ if (vanha === uusi || this.naytto === undefined) { return; } this.paivita(); } klikattiin(e) { const nappi = e.target.closest("button"); if (nappi === null) { return; } /* Setterin kautta: se kirjoittaa attribuutin, ja päivitys tulee attributeChangedCallback-metodista */ this.arvo = nappi.classList.contains("kasvata") ? this.arvo + this.askel : this.arvo - this.askel; } paivita() { this.naytto.textContent = this.arvo; /* Ulospäin kerrotaan tapahtumalla, ei kutsumalla vanhemman funktioita. bubbles: true tarvitaan, jotta tapahtuma kuplii. */ this.dispatchEvent(new CustomEvent("laskuri-muuttui", { detail: { arvo: this.arvo }, bubbles: true })); } } customElements.define("laskuri-elementti", Laskuri); } /* ---- tämä osa suoritetaan uudelleen joka Run-painalluksella ---- */ const alue = document.getElementById("alue"); const summa = document.getElementById("summa"); const laskeSumma = function () { const laskurit = [...alue.querySelectorAll("laskuri-elementti")]; /* Komponentin arvoon pääsee ominaisuuden kautta kuten selaimen omissa elementeissä */ summa.textContent = laskurit.reduce((yht, l) => yht + l.arvo, 0); }; /* Tapahtuma kuplii, joten yksi käsittelijä riittää koko alueelle */ alue.addEventListener("laskuri-muuttui", function (e) { console.log("laskuri-muuttui:", e.detail.arvo, "| lähde:", e.target.nodeName); laskeSumma(); }); document.getElementById("nollaa").addEventListener("click", function () { for (const laskuri of alue.querySelectorAll("laskuri-elementti")) { /* Ominaisuuden asettaminen kirjoittaa attribuutin, joka päivittää näytön ja laukaisee tapahtuman */ laskuri.arvo = 0; } }); laskeSumma();
Konsoli