俺流?vue.jsの選択ダイアログを作る方法のまとめの話
はい、小ネタです。
ツンデレ本管理プロジェクトの管理画面周りにてvue.jsの書籍選択ダイアログを作っていた時の覚書です。
ポイントは、呼び出し元とはリンクしないということですね。最初、オブジェクトをそのまま投げていて、かなりきつかったです。
作成したダイアログはこんな感じです

モジュールとしてvue-js-modalを使っていますよ
"vue-js-modal": "^2.0.1"
動きをまとめますと
このダイアログでは、シーン一覧からシーンを選択して対象の書籍を変更します
動きとしては
- 読書シーン(画面ではシーン(読書中))を送ります : メソッド showReadingBook ら辺
- 送られたシーンからタイトル、書籍を表示します
- 変更後の書籍一覧から対象の書籍は外します
- 変更後書籍を選んで「選択」を押下すると画面が閉じて更新処理が発生します :
@select="selectedBook($event)"
- 既読ボタンを押下すると読書中の書籍を既読フラグをあげます:
@readed="readed($event)"
ざっくりこんな感じ、ポイントは
- シーン情報はポインタ渡し?ではなく、値渡し
ポインタ渡し?の場合はbind(props)を使ってやればOK。
ってかそれが一番の悩みところです。
ポインタ渡し?はこんな感じで呼び出し先のpropsにバインとしてやればOKです。
ただし、呼び出し先で変更すると、呼び出し元にも影響があるので悩ましいです
例では:bookにバインドしています
値だけを渡すには、
- modalの呼び出し後のメソッド(@before-open)を指定してやる
- 呼び出し時にparamsにオブジェクトを入れるってということ
です。
呼び出し時に
this.$modal.show('selectReadingBookModal',{reading_book : readingBook});
呼び出し後に値を引き継いでいます
呼び出し先に内部変数に値を入れます。
beforeOpen($event){
this.reading_book = $event.params.reading_book;
this.init_items();
}
この画面では、画面に表示している書籍一覧をapiから取ってきているので書籍情報を初期化するメソッド(init_items)をコールしています。
この部分がポイントで、bindではうまいこと表示が出ません…
ってかmountedでしか書籍一覧が更新されずに… 数日悩みました
明瞭的にダイアログ表示時にメソッドをコールする必要が楽だったです
updatedとかだったら無限ループに…
ともあれ、試行錯誤した結果がこれです
vue.jsの本を肘置きに悩んだ結果ですw(早く読めw)
次回に続く?
呼び出し元ソース(抜粋)
<template>
<div>
<div class="properties-row d-flex justify-content-center">
<div class="col-4" v-for="readingBook in readingBooks">
<div class="card">
<div class="card-body" >{{ readingBook.scene.title }}</div>
<div class="row card-body text-primary text-center d-flex justify-content-center">
<reading-book class="col-12" :book="readingBook.book" ></reading-book>
</div>
<div class="card-body">
<button class="col-12 btn btn-dark" v-on:click="showReadingBook(readingBook)">変更</button>
</div>
</div>
</div>
</div>
<select-reading-book-modal @select="selectedBook($event)" @readed="readed($event)"/>
</div>
</template>
<script>
import SelectReadingBookModal from "./modals/SelectReadingBookModal";
export default {
name: 'ReadingBookListComponent',
data () {
return {
readingBooks : []
}
},
methods: {
/**
* 読書中のモーダルダイアログでの選択ボタンアクション
* @param $event イベント
* @returns {Promise<void>}
*/
async selectedBook($event){
const currentBookData = $event.book !== null ? $event.book.id !== undefined ?{'id' : $event.book.id , 'asin' : $event.book.asin} : null : null;
const scene = $event.scene.id !== undefined ?{'id' : $event.scene.id , 'title' : $event.scene.title} : null;
const data = {
id : $event.id
, scene : scene
, book : currentBookData
};
const url = API_URL + 'api/reading/save'
await axios.post(url, data).then(x => {
// 保存後のアクション
// 読書中シーンを初期化します
this.initReadingBooks();
})
},
/**
* 読書中書籍の状態を変更するボタンのアクション
* @param readingBook 対象の書籍
*/
showReadingBook (readingBook) {
this.$modal.show('selectReadingBookModal',{reading_book : readingBook});
},
},
}
ダイアログソース
<template>
<modal name="selectReadingBookModal"
:width="600"
:height="240"
@before-open="beforeOpen">
<div class="modal-body bg-warning">
{{ reading_book.scene ? reading_book.scene.title : "" }}
</div>
<div class="modal-body book row">
<div class="col-3">
読書中
</div>
<div class="col-9 form-group">
<div v-if="reading_book.book" class="truncate"> 【 {{ reading_book.book.asin }} 】{{ reading_book.book.title }}</div>
<div v-else>読書中書籍なし</div>
</div>
<div class="col-3">
変更後
</div>
<div class="col-9 form-group">
<select class="form-group col-12 custom-select" v-model="select_item">
<option value="null">読書中なし</option>
<optgroup label="専門書">
<option v-for="book in technical_items" :value="{ id : book.id , asin : book.asin }" >【 {{ book.asin }} 】 {{ book.title }}</option>
</optgroup>
<optgroup label="コミック">
<option v-for="book in comic_items" :value="{ id : book.id , asin : book.asin }" >【 {{ book.asin }} 】 {{ book.title }}</option>
</optgroup>
<optgroup label="小説">
<option v-for="book in novel_items" :value="{ id : book.id , asin : book.asin }" >【 {{ book.asin }} 】 {{ book.title }}</option>
</optgroup>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<div class="col-4">
</div>
<div class="col-4">
<button class="btn btn-secondary form-group col-12" v-if="reading_book.book" v-on:click="readed" >既読</button>
</div>
<div class="col-4 d-flex justify-content-end">
<button class="btn btn-secondary form-group col-12" v-on:click="select">選択</button>
</div>
</div>
</div>
</modal>
</template>
<script>
import axios from "axios";
const API_URL = document.getElementsByName('API_URL')[0].content;
export default {
name: 'SelectReadingBookModal',
data() {
return {
select_item: [],
technical_items: [],
comic_items: [],
novel_items: [],
reading_book: []
};
},
methods: {
hide() {
this.$modal.hide('selectReadingBookModal');
},
select() {
const data = {
id: this.reading_book.id
, scene: this.reading_book.scene
, book: this.select_item
};
this.$emit("select", data);
this.hide();
},
readed() {
const data = {
id: this.reading_book.id
, scene: this.reading_book.scene
, book: this.reading_book.book
};
this.$emit("readed", data);
this.hide();
},
/**
* 読書中ダイアログの書籍一覧を更新します
* @returns {Promise<void>}
*/
async init_items() {
const data = {
filter: {
readed: false,
},
};
const url = API_URL + "api/book/list";
await axios.post(url, data).then(x => {
this.technical_items = [];
this.comic_items = [];
this.novel_items = [];
x.data.books.forEach(book => {
// 選択中の書籍は表示対象から外す
if (this.reading_book.book !== null) {
if (book.asin === this.reading_book.book.asin) {
return;
}
}
// 書籍種別による格納配列切り替え
switch(parseInt(book.kind_id)){
case 1 :
this.technical_items.push(book);
break;
case 2 :
this.comic_items.push(book);
break;
case 3 :
this.novel_items.push(book);
break;
}
});
})
},
beforeOpen($event){
this.reading_book = $event.params.reading_book;
this.init_items();
}
},
}
</script>