Vue.jsとOnsenUIを使ったオレオレなショッピングカートチュートリアル

# はじめに

JSビギナーが、Vue.jsとOnsenUIを理解するためにオレ向けチュートリアルとして何か作ってみようと思い立ちました。Vue.js使うので、リアクティブがわかりやすいショッピングカートでいきます。

# できあがり
![shoppers.gif](https://qiita-image-store.s3.amazonaws.com/0/32540/ea2dc3a7-5914-30a1-e79c-0dedc58e1215.gif)


# できるまでの話
出来上がるまでに考えたことなど、つらつらと書いていきます。Vue.jsというよりは意外と、JSそのものの知識がなくてアレでした。JSってやや使いづらいっすね。Onsenの話は少なめになってしまいました。

### プロダクトメニューを作る
プロダクトのメニューはとりあえず、こんな感じ。アイコンは `fontawsome` さまさまで。

```javascript:メニュー
      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側で渡しています。もっとうまい方法あるのだろうか？

```javascript:addCart
  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` が少しうるさいですが、貼り付けます。

```html:メニュー表示
   <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`は英語的に変ですが ） 

### カートからアイテムを削除する
カートの中のプロダクトの数を減らす部分です。カートの中は、同じプロダクトが複数入ることがあるので例えばこんな感じになるわけです。

```javascript:カートの中身
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を生成した方が良い`との意見もあるようです）

このあたりが参考になります：[\[1\]](https://qiita.com/_shimizu/items/b8eac14f399e20599818
),[\[2\]](https://qiita.com/Sekky0905/items/598b47fea2106b8c140e
),[\[3\]](https://maku77.github.io/js/array/cut.html)

結局 `Array.prototype.some()` をつかってこんな実装にしてみました。

```javascript:delCart
  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あるあるな一貫性のない言語設計のように思えました。謎は深まりますが、深追いはやめておきます。

このあたりが参考になります：[\[1\]](https://www.deep-rain.com/programming/javascript/778), [\[2\]](https://www.sejuku.net/blog/48252)

## カートの中身を集計する
カートの中身を集計して、プロダクトAをX個、プロダクトBをY個という具合にみせようと思いました。`map/reduce` などでエレガンドに書けないだろうか？と思ったが、いまのところいい感じにはできていない。思いつくままに自己流でやってみた。

まず、カートに各プロダクトが何個づつ入っているか数えることにする。

```javascript:countInTheCartById
  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\]](https://teratail.com/questions/98451)

これとメニュー `products` をマージして `cart` とは別に、`computedCart` をつくってみた。カートにないプロダクトもあるので、そのidはスキップされます。

```javascript:メニュー
      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()` が発火する。

```javascript: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` 。

```javascript:totalPriceInTheCart
  computed: {
    totalPriceInTheCart: function () {
      return total = this.cart.reduce((sum, x) => sum + x.price, 0)
    }
  }
```

`reduce` についてはこのあたりを参考にしました：[\[1\]](https://mseeeen.msen.jp/compute-sum-with-javascript-reduce-function/), [\[2\]](https://ledsun.hatenablog.com/entry/2014/03/17/200035)


### 購入履歴に残す
ここはまだ実装していません。でも、ここまでくれば、それほど手間なくできるでしょう。

# ソース
[今回つくった雑なソースはここ](https://github.com/narutaro/shoppers)

# 今後
バックエンドとのやり取りを作っていこうかなと思っています。メニュー表や購入履歴はバックエンドに置くような感じで。

## シリーズ

- [Vue.jsでSPA - [1] Element UIでベースの画面をつくる](https://qiita.com/narutaro/items/50d0e4714f93279502c9)
- [Vue.jsでSPA - [2] Element UIで各ペインの画面をつくる](https://qiita.com/narutaro/items/9abbf7881f692c985484)
- [Vue.jsでSPA - [3] vue-routerでルーティング ](https://qiita.com/narutaro/items/74178f4ea031b0ccfa44)
- [Vue.jsでSPA - [4] コンポーネントにしてみる ](https://qiita.com/narutaro/items/df793bb34129d113d9f2)
- [Vue.jsでSPA - [5] リアクティブになってる？](https://qiita.com/narutaro/items/bed4eab6e53e3b46c0b2)
- [Vue.jsでSPA - [6] サーバからのデータ取得](https://qiita.com/narutaro/items/82f4d9635c398524431c)
- [Vue.jsでSPA - [7] Vueからサーバデータ取得](https://qiita.com/narutaro/items/91da194788318c226e0e)
- [Vue.jsでSPA - [8] バックエンドとうまくやっていこうとして試したこと](https://qiita.com/narutaro/items/3b7135eed8f6e930a0e6)
- [Vue.jsでSPA - [9] 今更ながらCORSとそのエラーの回避方法](https://qiita.com/narutaro/items/26e4cbec386bc054bbd7)
- [Vue.jsでSPA - [10] Safari..お前か...3rd party cookie](https://qiita.com/narutaro/items/e53f653d73a6934046d0)
- [Vue.jsでSPA - [11] Element UIでログイン画面](https://qiita.com/narutaro/items/010009c0198371fee658)
- [Vue.jsでSPA - [12] ログイン：シングルペインからツーペインへ画面遷移](https://qiita.com/narutaro/items/fca953b3b2ed5c63360c)
- [Vue.jsでSPA - [13] モバイル向けに OnsenUI に手をだす](https://qiita.com/narutaro/items/bdc0e25c22369466bed3)
- [Vue.jsでSPA - [14] Vue.jsとOnsenUIを使ったオレオレなショッピングカートチュートリアル](https://qiita.com/narutaro/items/9502b90b565366911c57)
- [Vue.jsでSPA - [15] 世界中の人とミーティング時間を決める時に便利なやつ](https://qiita.com/narutaro/items/d6431f54050504bf1551)
- [Vue.jsでSPA - [16] へー、FirebaseでWebアプリのログインってこうやるのか](https://qiita.com/narutaro/items/3c37fe6c3b19a6143a07)