背景
サーバーレスで自分用の家計簿的なwebサービスを勉強も兼ねて開発中。
データ入力画面、一覧画面と開発が終わり、次はデータ編集機能となるが、その時の実現方法は悩みどころ。別ページに移っての編集も選択肢だけど、以下の観点から、モーダルダイアログを採用する事に決定。
- 一覧画面から編集に移って、編集終了後は一覧画面に戻りたい。
- 戻った際はもちろん編集内容反映したいが、再読み込みするのは表示位置が戻ったり、動作が遅くなる一因なので嫌。
- 編集する要素数は少ない(5つ程度)。
Let's try
情報集め
element.ui公式サイト
ダイアログページを見てみる。サンプルはもちろん普通に動いているが、サンプルソースからすると、ダイアログは独立していなくて、それを使用するページのソースに書き込む感じ。入力ダイアログは複数画面で同一のものを使いまわしたい。
色々さがしていたら、以下の資料を発見。まさにやりたかった事。
揮発性の高いコンポーネントを作る話
以下、Vueコンポーネントのライフサイクルに関する情報も重要そう。公式ページかな?
コンポーネントの基本
mixinという技術もある様子。同じ処理を複数のコンポーネントに埋め込めるみたい。ただ、今回はダイアログ側Vueに組み込んで使いまわす形にしたいので、使わなさそう。
ミックスイン
方針
前述の揮発性の高いコンポーネントを作る話を基本にしたい。ただ、自分がやりたかった、入力ダイアログで入力したらその値をメイン画面に反映 という機能で必要となる、ダイアログ内の値の渡し方がちょっと解らなかった。自分にVueの知識が十分にあればこれを拡張できたと思うが、まだその段階に至っていない。一旦これを参考に、以下方針で実装する。
- ダイアログの要素、共通処理はダイアログ用Vueソースにまとめる。
- 呼び出し側はそれを作成、パラメーター設定、呼び出し、後処理するだけにする。
- 使う度に生成、破棄したいが、今回は、画面起動時に1回だけ作成する形にする(破棄を考慮しなくて良い様に)。
ポイント
Dialog側
- パラメーターにcallbackを使ってダイアログ終了時処理にデータを渡せるように
- オブジェクト生成時に、親要素にDOM要素追加
- 処理成功時には、前述callbackを実行。その際に、ダイアログで編集したデータも渡す。
呼び出し側
- Vue.extend 命令を使う事で、コンストラクタを生成
- そのコンストラクタで、指定パラメーターでの動的生成
- 自分の生成時に、ダイアログも生成
- callback関数でダイアログ成功時の処理を受け取る
- this.$forceUpdate() を使うと強制レンダリングできる。配列の要素の中身の更新ではレンダリングかからないらしく、これで処理した。
実際のソース
<template>
<el-dialog title="入力" :visible.sync="slipDialogVisible">
<el-form ref="form" :model="slip" label-width="60px">
<!-- 中略 -->
</el-form>
<div class="dialog-footer">
<el-button type="warning" :plain="true" @click="ondelsubmit">削除</el-button>
<el-button type="danger" :plain="true" @click="slipDialogVisible = false">中止</el-button>
<el-button type="primary" @click="onupdsubmit">登録</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'SlipInputDlg',
// 引数
props: {
// 親要素
parent: {
type: Element,
require: true
},
// ・・中略・・
// 更新時に親側に入力データを渡すためのcallback
callbackprm: {
type: Function,
require: false
}
},
data () {
return {
slipDialogVisible: false,
"後略": duumy
}
}
},
mounted: function () {
// 生成時に、親要素に追加
this.parent.appendChild(this.$el)
},
methods: {
// ダイアログにデータをセットする為の親から呼ばれる関数
setSlipData: function (slipdata) {
// ・・中略・・
},
// ダイアログを表示する為に親から呼ばれる関数
show: function () {
this.slipDialogVisible = true
},
// 削除ボタン押されたときの内部処理
ondelsubmit: function () {
this.onsubmitsub('del')
},
// 更新ボタン押されたときの内部処理
onupdsubmit: function () {
this.onsubmitsub('upd')
},
onsubmitsub: function (mode) {
// ・・中略・・
var self = this
this.$axios.post(this.apienv.baseendpoint + 'slip', slipdata, config).then(
response => {
self.slipDialogVisible = false
self.$message({message: '登録しました', type: 'success'})
if (self.callbackprm !== undefined) {
// 更新成功したらcallbak関数を呼ぶ
self.callbackprm(mode, slipdata)
}
}
)
}
}
}
</script>
<template>
<!-- 中略 -->
<!-- 編集対象データクリック時にダイアログ表示 -->
<div v-for="(item, index) in sliplist" class="slip-item" :key="index" @click="slipedit">
<!-- 中略 -->
</div>
<!-- 中略 -->
</template>
// ダイアログ側Vueの読み込み
import SlipInputDlg from '@/components/SlipInputDlg'
// ・・略・・
// コンストラクタ宣言部
const SlipInputDlgConstructor = Vue.extend(SlipInputDlg)
// ・・略・・
export default {
// ・・略・・
// 呼び出し側生成時に、ダイアログ側も生成
mounted (e) {
this.inputDlg = new SlipInputDlgConstructor({
propsData: {
parent: this.$el,
// ・・略・・
callbackprm: this.dlgCloseCallback
}
})
this.inputDlg.$mount()
},
methods: {
// 更新操作時、ダイアログに選択データの情報をセットして、表示
slipedit: function (e) {
var elements = e.target.parentNode.childNodes
const idx = [].slice.call(elements).indexOf(e.target) - 1
this.dlgeditidx = idx - 1
this.inputDlg.setSlipData(this.sliplist[this.dlgeditidx])
this.inputDlg.show()
},
// ダイアログ側で更新処理がされたときのcallback
dlgCloseCallback: function (mode, newSlipData) {
if (mode === 'upd') {
// ・・親側データ更新。略・・
// データ再読み込みしたくないので強制表示更新
this.$forceUpdate()
} else {
this.sliplist.splice(this.dlgeditidx, 1)
}
},
// ・・後略・・
}
}
実際の画面
まとめ
- そのままだと、各ページの埋め込み要素となり、一つのVueファイルを使いまわせない。
- 表示するだけなら結構楽だと思うけど、ダイアログの中でデータ編集するとなると、データセット+取り出しが必要になってきてそれなりの処理が必要。