とあるWeb屋の備忘録

とあるWeb屋の備忘録。たまに雑記。

Vueで書籍管理画面っぽいものを作ってみた

Vueの学習のために書籍管理画面っぽいものを作ってみました。
書籍リストを読む処理にaxiosを使いましたがcodepen上はaxiosを使用しないソースになっています。
axiosを使用したソースはページ下部に載せておきます。

ちなみに書籍リストは以下のユニークなidを持った配列にしました。

[
    { "id":1,  "title": "book01", "price": 100 },
    { "id":2,  "title": "book02", "price": 200 },
    { "id":3,  "title": "book03", "price": 300 },
    { "id":4,  "title": "book04", "price": 400 },
    { "id":5,  "title": "book05", "price": 500 },
    { "id":6,  "title": "book06", "price": 600 },
    { "id":7,  "title": "book07", "price": 700 },
    { "id":8,  "title": "book08", "price": 800 },
    { "id":9,  "title": "book09", "price": 900 },
    { "id":10, "title": "book10", "price": 1000 }
]


仕様

  • 価格絞り込み
  • 数量絞り込み
  • 昇順/降順並び替え
  • 書籍登録
  • 書籍更新
  • 書籍削除
  • 書籍リストのリロード(リロード中にNow Loading表示)
  • 500円以下は10%OFF付ける

価格絞り込み・数量絞り込み

Vueコンストラクタ関数を使ってVueインスタンスを作成します。
Vueインスタンスにはいったんデフォルトで以下のオプションオブジェクトを渡します。

const app = new Vue({
    el: '#app',
    data: {
        budget: 2000,
        amount: 20,
        books: []
    },
)

画面上で入力した値をデータにバインドするためにv-modelディレクティブを使います。

// HTML
<input v-model.number="budget"><span>円以下</span>
<input v-model.number="amount">冊以内に絞り込む


価格絞り込みについて

computedを使います。

  1. filterメソッド(非破壊的)を使って予算以下の書籍リストを構築してreturnする
  2. returnされたリストを表示させる
// computed
matched: funciton() {
    return this.books.filter(books => {
        book.price <= this.budget
    }, this)
}
// HTML
<li v-for="book in matched" :key="book.id">

これで価格の絞り込みが完了です。


数量絞り込みについて

computedを使います。

this.matchからreturnされる配列のうち数量分をsliceする

// computed
amounted: function() {
    return this.matched.slice(0, this.amount)
}
// HTML
<li v-for="book in amounted" :key="book.id">

これで価格の絞り込みからの数量絞り込みが完了です。


昇順/降順並び替えについて

昇順/降順ボタンを押すたびに順序を切り替えるように実装します。

  1. データにflg用のプロパティを持たせてflgの状態で昇順/降順を切り替える
  2. Lodashを使って並び変える
  3. returnされる配列を表示させる
const app = new Vue({
    el: '#app',
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false // add
    },
)
// computed
sorted: function() {
    return _.orderBy( this.amounted, 'price', this.sort ? 'desc' : 'asc')
}
// HTML
<button @click="sort=!sort">
    <span v-if="sort">降順</span>
    <span v-else>昇順</span>
</button>

これで価格絞り込みと数量絞り込みをした書籍リストを昇順/降順で並び替えることができます。


書籍登録について

  1. 書籍にはそれぞれユニークなidを振っているので書籍リストのlengthに応じてidの値を自動で割り振れるようにlengthを取得するメソッドを用意する
  2. 追加する書籍のプロパティを入力する(idは1で用意したメソッドを使う)
  3. pushメソッドで書籍リストに「入力したプロパティを持つ新しい要素」を追加する
const app = new Vue({
    el: "#app",
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false,
        titleR: null, // add
        priceR: null  // add
    }
// computed
max: function() {
    return this.books.reduce((a, b)=> {
        return a > b.id ? a : b.id
    }, 0)
}
// methods
regist: function() {
    this.books.push({
        id: this.max + 1,
        title: this.titleR,
        price: this.priceR
    })
    this.titleR = null;
    this.priceR= null;
}
// HTML
<span>書籍名:</span><input v-model="titleR">
<span>価格:</span><input v-model.number="priceR">
<button @click="regist">書籍登録</button>

書籍更新について

  1. 更新したい書籍idと更新後のタイトル、価格を入力する
  2. 更新したい書籍idと一致する要素の配列インデックスを取得する
  3. その配列インデックスのプロパティを$setを使って更新する
const app = new Vue({
    el: "#app",
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false,
        titleR: null,
        titleU: null, // add
        priceR: null,
        priceU: null, // add
        id: null,     // add
    }
// methods
update: function() {
    if(this.id != "") {
        this.books.forEach(el=> {
            if(el.id == this.id) {
                let target = this.books.indexOf(el);
                this.$set(this.books, target, {
                    id: this.id,
                    title: this.titleU,
                    price: this.priceU
                })
            }
        }, this)
    };
    this.id = null;
    this.titleU = null;
    this.priceU= null;
}
// HTML
<span>書籍No.</span><input v-model.number="id">
<span>書籍名:</span><input v-model="titleU"><br class="spi">
<span>値段:</span><input v-model.number="priceU">円
<button @click="update">書籍更新</button>

書籍削除について

  1. 削除ボタンを押す
  2. 削除ボタンが押された要素の配列インデックスを取得する
  3. spliceでその配列インデックスの要素を取り除く
// methods
delate: function(el) {
    let target = this.books.indexOf(el);
    this.books.splice(target, 1);
}
// HTML
<button @click="delate(book)">削除する</button>

書籍リストのリロード(リロード中にNow Loading表示)について

※ここでは書籍リストを直書きしていますが、axiosを使ってjsonから読ませる方法も次のセクションに記載します。

  1. Vueのウォッチャを使って書籍リストの高さを監視する(データにheightを持たせてデフォルトの高さを0にしておく)
  2. 書籍リストがない場合はNow Loadingの表示を出す
  3. 一秒後に書籍リストをDOMにマウントする
  4. $nextTickを使って書籍リストがDOMにマウントされたら書籍リストの高さを取得する
  5. 取得した高さをstyle属性にバインドする
  6. リロードボタン押下時にいったん書籍リストを空にして2~5の処理を繰り返す
const app = new Vue({
    el: "#app",
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false,
        titleR: null,
        titleU: null,
        priceR: null,
        priceU: null,
        id: null,
        height: 0, // add
    }
// watch
books: function() {
    console.log(this.$refs.booksCont.getBoundingClientRect().height)
    this.$nextTick(()=> {
        console.log(this.$refs.booksCont.getBoundingClientRect().height)
        this.height = this.$refs.booksCont.getBoundingClientRect().height
    })
}
// methods
loadBooks: function() {
    this.books = []; // リロードボタン押下時のための処理
    setTimeout(()=> {
        this.books = [
            { "id":1,  "title": "book01", "price": 100 },
            { "id":2,  "title": "book02", "price": 200 },
            { "id":3,  "title": "book03", "price": 300 },
            { "id":4,  "title": "book04", "price": 400 },
            { "id":5,  "title": "book05", "price": 500 },
            { "id":6,  "title": "book06", "price": 600 },
            { "id":7,  "title": "book07", "price": 700 },
            { "id":8,  "title": "book08", "price": 800 },
            { "id":9,  "title": "book09", "price": 900 },
            { "id":10, "title": "book10", "price": 1000 }
        ]
    }, 1000)
}
// HTML

// Now Loading箇所
<div class="p-top__loading" v-if="!books.length">
    Now Loading...
</div>

// 高さをbindする箇所
<div :style="{height: height + 'px'}">
    <ul v-cloak ref="booksCont">

// リロードボタン箇所
<button @click="loadBooks" :disabled="!books.length">
    <span>リロード</span>
</button>

書籍リストをaxiosでjsonから読む方法

書籍リストをaxiosでjsonから取得する方法を以下に記載します。
やっていることはjsonから書籍リストを読み込んで1秒後にDOMにマウントする処理をcreatedで実行しています。

// JSON
[
    { "id":1,  "title": "book01", "price": 100 },
    { "id":2,  "title": "book02", "price": 200 },
    { "id":3,  "title": "book03", "price": 300 },
    { "id":4,  "title": "book04", "price": 400 },
    { "id":5,  "title": "book05", "price": 500 },
    { "id":6,  "title": "book06", "price": 600 },
    { "id":7,  "title": "book07", "price": 700 },
    { "id":8,  "title": "book08", "price": 800 },
    { "id":9,  "title": "book09", "price": 900 },
    { "id":10, "title": "book10", "price": 1000 }
]
// methods
loadBooks: function() {
    this.books = []; // リロードボタン押下時のための処理
    axios.get("./json/books.json").then(responce => {
        setTimeout(()=> {
            this.books = responce.data
        }, 1000)
    })
    .catch(e => {
        console.log(e)
    })
}
// created
created: function() {
    this.loadBooks();
}

500円以下は10%OFF付ける

// HTML
<span v-if="book.price < 500 ">10%OFF</span>

分かりづらい部分があるかもしれないので後日リライトするかもしれないです。
あと今はPromiseよりAsync/awaitを使うのが一般的かと思いますので、もう少しモダンな書き方ができるかもしれません。

機能を実装するにあたりこちらの本を参考にさせていただきました!!ありがとうございます!!!
基礎から学ぶ Vue.js
https://www.amazon.co.jp/dp/B07D9BYHMZ/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

ここまで読んでいただいてありがとうございました!!!