En webbkomponent
Förkunskaper
Section titled “Förkunskaper”Du bör ha jobbat igenom kmom04, lämnat in och skapat en ny branch kmom05. Du kan med fördel göra nedanstående direkt i ditt webshop-repo.
Web komponenter
Section titled “Web komponenter”När vi utvecklar mjukvara är det ett antal olika principer och best-practices som har vunnit mark och som karakteriserar bra mjukvara. Ofta har de lite olika roliga förkortningar och nedan är ett par av de som web components är ett försök på att efterleva.
DRY: Do not Repeat Yourself [1]
SRP: Single Responsibility Principle [2]
Encapsulation [c.f. 3]
Vi vill alltså kapsla in vår kod som har exakt ett (1) ansvarsområde för att kunna återanvända. I denna övningen kommer vi byta ut de enskilda produkterna i produktlistningen mot komponenter, i uppgiften “Webbshoppen del 5” gör ni sedan ett liknande arbete för produkterna som ligger i varukorgen.
Produktlistningen
Section titled “Produktlistningen”Vi vill nu kapsla in all funktionalitet för varje enskild produkt i produktlistningen i en komponent. Jag har i min views/main.js en funktion renderProducts som skriver ut boxar med produkterna. Ni bör ha något liknande kan dock ligga i andra filer eller se ut på ett annat sätt.
function renderProducts(products) { return products.map((product) => { return `<div class="product"> <img src="${product.image_url}" alt="Album art ${product.name}" /> <div class="bottom"><p>${product.name}</p><span class="add-to-cart" id="${product.id}">+</span></div></div>` }).join("\n")}I koden ovan ser vi att denna koden är väl lämpad för att göras om till en komponent då den återupprepas ett antal gånger med samma kod även om denna koden ligger i en loop (map()). Spara undan koden som finns efter return för att återanvändas i ett senare skede. Vi ersätter sedan allt med en webbkomponent, som än så länge ser ut som ett helt vanligt HTML-element, förutom att det innehåller ett bindestreck vilket vanliga HTML-element inte gör.
function renderProducts(products) { return products.map((product) => {// return `<div class="product">// <img src="${product.image_url}" alt="Album art ${product.name}" />// <div class="bottom"><p>${product.name}</p><span class="add-to-cart" id="${product.id}">+</span></div>// </div>` return `<single-product></single-product>` }).join("\n")}Om vi laddar om förstasidan misstänker jag att det i stor utsträckning blir helt tomt på förstasidan av din webbshop, men tar vi en titt i View Source eller inspect ser det annorlunda ut.
Vi vill nu skicka med data till komponenten så vi har möjlighet att faktiskt visa upp något vettigt. Vi använder JSON.stringify för att göra om ett objekt med de attribut vi behöver i komponenten till en sträng, som vi sedan i komponentens kod kan göra en JSON.parse av för att få värdena som ett objekt.
function renderProducts(products) { return products.map((product) => {// return `<div class="product">// <img src="${product.image_url}" alt="Album art ${product.name}" />// <div class="bottom"><p>${product.name}</p><span class="add-to-cart" id="${product.id}">+</span></div>// </div>` return `<single-product product='${JSON.stringify({ id: product.id, name: product.name, image_url: product.image_url, })}'></single-product>` }).join("\n")}Webbkomponentens klass
Section titled “Webbkomponentens klass”Låt oss nu registrera komponenten och skapa en JavaScript klass (Dokumentation för class) som behövs för att vi kan lägga till funktionalitet för komponenten.
Högst upp i views/main.js lägger jag till att jag importerar en komponent och sedan registrerar den genom att koppla den till komponentens namn i detta fallet single-product, som vi använde ovan.
import SingleProduct from "../components/single-product.js"
customElements.define('single-product', SingleProduct)Vi skapar sedan katalogen components och filen components/single-product.js som vi fyller med följande kod från första början. I koden skapar vi först klassen SingleProduct som ärver från prototypen HTMLElement. Detta gör att vi kan komma åt attribut som this.innerHTML och att våra komponenter är som ett “riktigt” HTML-element i våra sidor. Vi exporterar direkt klassen som vår default-export från filen. I alla komponenter som vi skapar vill vi ha instansmetoden connectedCallback, den funktionen är en livs-cykel funktion (Custom element lifecycle callbacks) som anropas när en komponent läggs till i dokumentet.
Vår kod gör sedan inte mycket mer än att sätta att innerHTML för vårt element är ett stycke med texten produkt. Om du laddar om sidan nu, bör du alltså nu se så många stycken med texten Produkt som du har produkter i Lager-API:t.
export default class SingleProduct extends HTMLElement { // connect component connectedCallback() { this.innerHTML = `<p>Produkt</p>` }}För att vi ska få till nån sorts funktionalitet i komponenten och på vår sida lägger vi till ett antal andra metoder. Först observedAttributes som definierar vilka attribut vi kunna hämta data ifrån, utöver de som i vanliga fall definieras för ett HTML-element till exempel class, id osv. Sedan skapar vi en getter-funktion som gör JSON.parse på produkten som vi tidigare skickade med till komponenten.
I connectedCallback använder vi sedan den kod som vi tidigare hade i renderProducts, men byter ut product mot this.product för att referera tillbaka mot klassen getter-funktion product().
export default class SingleProduct extends HTMLElement { static get observedAttributes() { return ['product'] }
get product() { return JSON.parse(this.getAttribute("product")) }
connectedCallback() { this.innerHTML = `<div class="product"> <img src="${this.product.image_url}" alt="Album art ${this.product.name}" /> <div class="bottom"><p>${this.product.name}</p><span class="add-to-cart" id="${this.product.id}">+</span></div> </div>` }}Vi bör nu kunna se våra produkter på första sidan av vår webbshop.
Referenser
Section titled “Referenser”[1] Thomas, D., & Hunt, A. (2019). The pragmatic programmer. Addison-Wesley Professional.
[2] Martin, R. C. (2009). Clean code: a handbook of agile software craftsmanship. Pearson Education.
[3] Snyder, A. (1986, June). Encapsulation and inheritance in object-oriented programming languages. In Conference proceedings on Object-oriented programming systems, languages and applications (pp. 38-45).