The resultできあがり
Add a few products. Each click adds a line to the cart, and the badge counts the lines.
商品をいくつか入れてみてください。押すたびにカートに1行増え、バッジが行数を数えます。
Change the codeコードを変える
In the state, replace cartCount and the old addToCart with a cart list and a handler that takes the row index:
stateのcartCountと古いaddToCartを、cartのリストと、行のインデックスを受け取るハンドラに置き換えます。
cart: [],
addToCart(event, index) {
const product = this.products[index];
this.cart = this.cart.concat({ id: product.id, name: product.name, price: product.price });
}
The badge now shows the length of the list:
バッジはリストの長さを表示します。
<p class="cart-badge">Cart: <span data-wcs="textContent: cart.length"></span></p>
And a cart panel goes into <main>, after the catalog section:
そして<main>の中、商品一覧のセクションの後ろにカートの欄を置きます。
<aside class="cart">
<h2>Cart</h2>
<ul class="cart-lines">
<template data-wcs="for: cart">
<li>
<span class="line-name">{{ .name }}</span>
<span class="line-total">${{ .price|toFixed(2) }}</span>
</li>
</template>
</ul>
</aside>
How it worksしくみ
Handlers receive the row indexハンドラは行のインデックスを受け取る
A handler gets the event first and, when its element sits inside a for: row, the row's index second. addToCart(event, index) receives 0 from House Blend's button, 1 from Ethiopia Yirgacheffe's, and so on, and reads the product with this.products[index] — reading through this is plain JavaScript.
ハンドラは1つ目の引数にイベントを、要素がfor:の行の中にあれば2つ目に行のインデックスを受け取ります。addToCart(event, index)は、House Blendのボタンからは0を、Ethiopia Yirgacheffeのボタンからは1を受け取り、this.products[index]で商品を読みます。thisを通した読み取りは、ふつうのJavaScriptです。
Assign a new array新しい配列を代入する
this.cart.concat({ … }) builds a new array with one more line, and this.cart = … assigns it to the path cart. As in step 1, the assignment is the update: the cart list and the badge — cart.length is a path too — render again. push would change the array in place without any assignment, and the page would not move. Use the methods that return a new array: concat, toSpliced, filter, map, toSorted, toReversed, with.
this.cart.concat({ … })は1行多い新しい配列を作り、this.cart = …がそれをパスcartに代入します。ステップ1と同じく、代入が更新です。カートの一覧とバッジ(cart.lengthもパスです)が描き直されます。pushは代入をせずに配列をその場で変えるので、ページは動きません。新しい配列を返すメソッドを使います:concat、toSpliced、filter、map、toSorted、toReversed、with。
Rows follow their objects行はオブジェクトについていく
When the new array arrives, wcstack matches the rows already on screen to their objects. The new array still holds the same line objects, so the existing lines stay exactly as they are and only the new line is created. There is no key attribute to write.
新しい配列が届くと、wcstackは画面にある行をそれぞれのオブジェクトと照らし合わせます。新しい配列も同じ行のオブジェクトを持っているので、既存の行はそのまま残り、作られるのは新しい行だけです。key属性を書く必要はありません。
Add Sencha twice. You get two separate “Sencha” lines — a cart that counts lines, not products. The next step merges them into one line with a quantity.
Senchaを2回入れてみましょう。「Sencha」の行が別々に2つできます。商品ではなく行を数えるカートです。次のステップで、これを数量付きの1行にまとめます。
Common mistakesよくある間違い
push and other in-place methodspushなどその場で変えるメソッド
this.cart.push(item) changes the array without assigning to cart, so nothing on the page updates. splice, sort and reverse do the same. The checker catches all of them as an error:
this.cart.push(item)はcartに代入せずに配列を変えるので、ページは何も更新されません。splice・sort・reverseも同じです。検査器はこれらをすべてエラーとして見つけます。
error wcs/array-mutation Destructive array method "push" does not trigger a reactive update (re-assigning the same reference does not reflect added/removed elements either). Use a non-destructive method with reassignment (e.g. this.cart = this.cart.concat(item)).
The index is the second argumentインデックスは2つ目の引数
Writing addToCart(index) puts the event into index, and this.products[index] is then undefined. Keep event as the first parameter even when you don't use it.
addToCart(index)と書くとindexにイベントが入り、this.products[index]はundefinedになります。使わなくても、1つ目の引数はeventのまま残します。
The whole fileファイル全体
The complete index.html of this step. npx @wcstack/lint index.html reports 0 error(s), 0 warning(s), 0 info for it.
このステップのindex.html全体です。npx @wcstack/lint index.htmlの結果は0 error(s), 0 warning(s), 0 infoです。
Show index.htmlindex.htmlを表示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Little Roastery</title>
<link rel="stylesheet" href="https://wcstack.github.io/tutorial/shop.css">
<script type="module" src="https://esm.run/@wcstack/state@4/auto"></script>
</head>
<body>
<wcs-state>
<script type="module">
export default {
products: [
{ id: 1, name: "House Blend", description: "Chocolate, caramel, a hint of citrus.", price: 14, stock: 12, icon: "☕" },
{ id: 2, name: "Ethiopia Yirgacheffe", description: "Bright and floral, with jasmine and lemon.", price: 18, stock: 5, icon: "☕" },
{ id: 3, name: "Colombia Supremo", description: "Nutty and smooth, with a cocoa finish.", price: 16, stock: 8, icon: "☕" },
{ id: 4, name: "Sencha", description: "Fresh, grassy Japanese green tea.", price: 12, stock: 20, icon: "🍵" },
{ id: 5, name: "Matcha", description: "Stone-ground, ceremonial grade.", price: 24, stock: 0, icon: "🍵" },
{ id: 6, name: "Measuring Spoon", description: "One scoop, one cup.", price: 6, stock: 30, icon: "🥄" },
{ id: 7, name: "Milk Pitcher", description: "Stainless steel, 350 ml.", price: 15, stock: 4, icon: "🥛" }
],
cart: [],
addToCart(event, index) {
const product = this.products[index];
this.cart = this.cart.concat({ id: product.id, name: product.name, price: product.price });
}
};
</script>
</wcs-state>
<header class="shop-header">
<h1>Little Roastery</h1>
<p class="cart-badge">Cart: <span data-wcs="textContent: cart.length"></span></p>
</header>
<main class="shop">
<section class="catalog">
<h2>Products</h2>
<div class="grid">
<template data-wcs="for: products">
<article class="card" data-wcs="class.sold-out: .stock|eq(0)">
<div class="card-icon">{{ .icon }}</div>
<h3>{{ .name }}</h3>
<p>{{ .description }}</p>
<p class="price">${{ .price|toFixed(2) }}</p>
<p class="badge" data-wcs="hidden: .stock|ne(0)">Sold out</p>
<button type="button" data-wcs="onclick: addToCart; disabled: .stock|eq(0)">Add to cart</button>
</article>
</template>
</div>
</section>
<aside class="cart">
<h2>Cart</h2>
<ul class="cart-lines">
<template data-wcs="for: cart">
<li>
<span class="line-name">{{ .name }}</span>
<span class="line-total">${{ .price|toFixed(2) }}</span>
</li>
</template>
</ul>
</aside>
</main>
</body>
</html>