はじめに
JSビギナーが、Vue.jsとOnsenUIを理解するためにオレ向けチュートリアルとして何か作ってみようと思い立ちました。Vue.js使うので、リアクティブがわかりやすいショッピングカートでいきます。
できあがり
できるまでの話
出来上がるまでに考えたことなど、つらつらと書いていきます。Vue.jsというよりは意外と、JSそのものの知識がなくてアレでした。JSってやや使いづらいっすね。Onsenの話は少なめになってしまいました。
プロダクトメニューを作る
プロダクトのメニューはとりあえず、こんな感じ。アイコンは fontawsome
さまさまで。
products: [
{ id: 1, name: "Adventure's Guide", price: 20, icon: "fa-book" },
{ id: 2, name: "Magical Looking Gem", price: 100, icon: "fa-gem" },
{ id: 3, name: "Broken Compass", price: 10, icon: "fa-compass" },
{ id: 4, name: "Map Human Heart", price: 30, icon: "fa-map" },
{ id: 5, name: "Flying Broom", price: 50, icon: "fa-broom" },
{ id: 6, name: "Everyday's IPA Beer", price: 7, icon: "fa-beer" },
{ id: 7, name: "Medicinal Plants", price: 10, icon: "fa-leaf" },
{ id: 8, name: "Extremely Dried Fish", price: 15, icon: "fa-fish" },
]
メニューから購入するプロダクトと個数を選んでもらう
リストをタップされた時に何番目のプロダクトがクリックされたのかを認識する必要があるので、以下のようにしてみた。引数 id
はHTML側で渡しています。もっとうまい方法あるのだろうか?
methods: {
addCart: function (id) {
var selectedProdct = this.products.find((prod) => {
return (prod.id === id);
});
this.cart.push(selectedProdct)
}
}
HTML側は、以下のようにv-for
で回して、描くリストアイテムの @click="addCart(prod.id)"
のところに引数を渡すようにしています。Onsen UI
の class
が少しうるさいですが、貼り付けます。
<ons-list-item class="list__item" v-for="prod in products" :key="prod.id">
<div class="left list__item__left">
<!-- <div class="product-icon"></div> -->
<ons-icon v-bind:icon="prod.icon" size="28px"></ons-icon>
</div>
<div class="center list__item__center">
<span class="list-item__title">{{ prod.name }}</span>
<span class="list-item__subtitle">{{ prod.price }} Gold</span>
</div>
<div class="right list__item__right">
<span class="toolbar-button">
<ons-icon class="icon" icon="fa-plus" @click="addCart(prod.id)"></ons-icon>
</span>
<span class="notification badgeToolbar product-count">{{ countInTheCartById(prod.id) }}</span>
<span class="toolbar-button">
<ons-icon class="icon" icon="fa-minus" @click="delCart(prod.id)"></ons-icon>
</span>
</div>
</ons-list-item>
選んだものをカートに入れる
カートは配列にして、とりあえず products
の中の各オブジェクトをクリックするたびにそのままaddCart
することにしました。( addCart
は英語的に変ですが )
カートからアイテムを削除する
カートの中のプロダクトの数を減らす部分です。カートの中は、同じプロダクトが複数入ることがあるので例えばこんな感じになるわけです。
cart = [
{"id":1,"name":"Adventure's Guide","price":20,"icon":"fa-book"},
{"id":2,"name":"Magical Looking Gem","price":100,"icon":"fa-gem"},
{"id":1,"name":"Adventure's Guide","price":20,"icon":"fa-book"},
{"id":1,"name":"Adventure's Guide","price":20,"icon":"fa-book"}
]
こんな配列から、id=1を1つだけカートから削除したい。ちょっとググると、arrayのidを指定した要素削除は delete
とか splice
がでてくるのですが、要素を削除したあとに代わりに undefined
をいれてしまって、配列の長さは変えない(よってあとでカートの中身を数える時におかしくなる)とか、指定したidを持つ要素を全て削除してしまうとか、気の利いたメソッドがなくてややめんどくさいところでした。
(配列は削除じゃなくて、ES6ならfilterで新しいarrayを生成した方が良い
との意見もあるようです)
結局 Array.prototype.some()
をつかってこんな実装にしてみました。
methods: {
delCart: function (id) {
this.cart.some (function(element, i) {
if (element.id == id) {
vm.cart.splice(i, 1)
return true
}
});
}
}
some()
は true
を返すとそこでループが break
するという謎仕様です。とりあえず動きます。これで、**「1つ削除したらすぐにループから抜ける」**ことができました。少し調べたところfor
は break
できるが、forEach
はできない(return true
せよ) など、JSあるあるな一貫性のない言語設計のように思えました。謎は深まりますが、深追いはやめておきます。
カートの中身を集計する
カートの中身を集計して、プロダクトAをX個、プロダクトBをY個という具合にみせようと思いました。map/reduce
などでエレガンドに書けないだろうか?と思ったが、いまのところいい感じにはできていない。思いつくままに自己流でやってみた。
まず、カートに各プロダクトが何個づつ入っているか数えることにする。
computed: {
productCountById: function () {
count = {}
this.cart.forEach(function(item) {
count[item.id] = (count[item.id])? count[item.id] + 1 : 1 ;
});
return count
// => プロダクトid毎の個数 eg.) {1: 3, 2: 2, 4: 1, 5: 1, 7: 2, 8: 3}
}
}
id毎に個数を数えるのは、このあたりを参考にしました:[1]
これとメニュー products
をマージして cart
とは別に、computedCart
をつくってみた。カートにないプロダクトもあるので、そのidはスキップされます。
computedCart: [
{ id: 1, name: "Adventure's Guide", price: 20, icon: "fa-book", count: 3 },
{ id: 2, name: "Magical Looking Gem", price: 100, icon: "fa-gem", count: 2 },
{ id: 4, name: "Map Human Heart", price: 30, icon: "fa-map", count: 1 },
{ id: 5, name: "Flying Broom", price: 50, icon: "fa-broom", count: 1 },
{ id: 7, name: "Medicinal Plants", price: 10, icon: "fa-leaf", count: 2 },
{ id: 8, name: "Extremely Dried Fish", price: 15, icon: "fa-fish", count: 3 },
]
メソッド的にはこんな感じ。カートのボタンを押したら viewCart()
が発火する。
methods: {
viewCart() {
var countForCheckOut = []
for (id in this.productCountById){
this.products.find(function(item) {
if (item.id == id) {
item["count"] = vm.productCountById[id]
countForCheckOut.push(item)
}
});
}
this.computedCart = countForCheckOut
}
}
ただ、合計金額だけはこの computedCart
より cart
から全部足し算した方が簡単なので、reduce
つかってやってみました。スッキリ書けて、いいですね、reduce
。
computed: {
totalPriceInTheCart: function () {
return total = this.cart.reduce((sum, x) => sum + x.price, 0)
}
}
reduce
についてはこのあたりを参考にしました:[1], [2]
購入履歴に残す
ここはまだ実装していません。でも、ここまでくれば、それほど手間なくできるでしょう。
ソース
今後
バックエンドとのやり取りを作っていこうかなと思っています。メニュー表や購入履歴はバックエンドに置くような感じで。
シリーズ
- Vue.jsでSPA - [1] Element UIでベースの画面をつくる
- Vue.jsでSPA - [2] Element UIで各ペインの画面をつくる
- Vue.jsでSPA - [3] vue-routerでルーティング
- Vue.jsでSPA - [4] コンポーネントにしてみる
- Vue.jsでSPA - [5] リアクティブになってる?
- Vue.jsでSPA - [6] サーバからのデータ取得
- Vue.jsでSPA - [7] Vueからサーバデータ取得
- Vue.jsでSPA - [8] バックエンドとうまくやっていこうとして試したこと
- Vue.jsでSPA - [9] 今更ながらCORSとそのエラーの回避方法
- Vue.jsでSPA - [10] Safari..お前か...3rd party cookie
- Vue.jsでSPA - [11] Element UIでログイン画面
- Vue.jsでSPA - [12] ログイン:シングルペインからツーペインへ画面遷移
- Vue.jsでSPA - [13] モバイル向けに OnsenUI に手をだす
- Vue.jsでSPA - [14] Vue.jsとOnsenUIを使ったオレオレなショッピングカートチュートリアル
- Vue.jsでSPA - [15] 世界中の人とミーティング時間を決める時に便利なやつ
- Vue.jsでSPA - [16] へー、FirebaseでWebアプリのログインってこうやるのか