ページのイメージ
WEB開発 2017/03/27

[Vue.js]Vue.jsとbootstrap-datetimepickerを合わせて利用する

榊原継太
榊原継太
  •  
  • このエントリーをはてなブックマークに追加


この頃、サーバーサイドプログラムを書くより、設計書とフロントサイドプログラムを書くことが多くなりました。
システムエンジニアの榊原です。

今年一発目の記事でフロントに力を入れると言いましたが、一応は有言実行できているんですかね?
ということ、今回はこの頃よく使っているVue.jsとこれまたよく使うbootstrap-datetimepickerを合わせて使った際のことを書きます

Vue.jsとは

一言で言うなら、「JavaScriptアプリケーションフレームワーク」
もうちょっとわかりやすく言うと「Angular JS」、「Backbone.js」と同じようなものと思っていただければいいかと思います
同じようなものがある中で、なんでVue.jsなんだというと…

Vue.js 公式ドキュメント
https://jp.vuejs.org/v2/guide/

上記URLの公式ドキュメントを見てもらえればわかるのですが、日本語化がかなりされている点がかなり大きい理由です
また、勉強会でもよく名前を聞き、知り合いのフロントエンドエンジニアも利用していたため、ソースを見せていただいたときに個人的には分かりやすかったという理由もあります

bootstrap-datetimepickerとは

こちらはかなり有名だと思うので、あまり説明はしませんが…
カレンダー入力を簡単に実装するためのライブラリです
サンプルがかなりしっかり作られているので、公式サンプルページを下記に記載しますので、そちらをご確認ください

bootstrap-datetimepicker Github

https://github.com/Eonasdan/bootstrap-datetimepicker

bootstrap-datetimepicker 公式サンプル
https://eonasdan.github.io/bootstrap-datetimepicker/

Vue.jsとbootstrap-datetimepicker合わせて利用する場合の問題点

とある一覧で、ページネイトありで、並び順や有効期限などをその場で更新できる実装にしたいため、Vue.jsを利用する形で作成しました
また、有効期限をカレンダー形式で選択させたかったため、bootstrap-datetimepickerを合わせて利用してみたのですが、ここで問題が発生しました

Vue.jsのdataで一覧の各行の値を管理しているのですが、
bootstrap-datetimepickerでカレンダーから日付を変更した際、画面上は値が変わるのですが、Vue.jsで管理している値が変わらない現象が起きました

当たり前といえば当たり前ですよねー
別のライブラリで値を変更しているので、Vue.jsが値変更を検知できませんよね…
ということで、色々調べた結果、解決しました

解決方法については、実際のソースの一部を貼りますが
今回はカスタムディレクティブ機能を利用することで、解決しました
その他にも調べてみるとコンポーネント機能を利用することでも解決することがわかりましたが、おいおいやっていこうかと思います

テンプレート

該当箇所は23行目で、v-datepickerを記述することで
自分で作成したdatepickerディレクティブが使用されます

<div id="vue-list" class="form-inline dt-bootstrap no-footer">
					<div class="row">
						<div v-if="list" class="col-sm-12">
							<table class="table table-striped table-bordered" style="min-width: 1090px;">
								<thead>
									<tr>
										<th class="width-80">id</th>
										<th class="width-100">表示順</th>
										<th class="width-50">掲載期間</th>
									</tr>
								</thead>
								<tbody>
									<tr v-for="(value, index) in displayData">
										<td></td>
										<td>
											<div class="input-group">
												<input v-model="value.order_no" class="form-control" type="text">
												<span class="input-group-btn">
													<button @click="updateOrderNo(value.id)" class="btn btn-primary" type="button" :disabled="disableUpdateDate(value.id)">更新</button>
												</span>
											</div>
										</td>
										<td><input class="form-control" type="text" :value="value.expiration_at" v-datepicker="value" readonly="readonly"></td>
									</tr>
								</tbody>
							</table>

							<div v-show="pageCount > 1" id="pagination">
								<ul class="pagination">
									<li @click="showFirst" :class="isStartPage ? 'disabled' : ''"><a>«</a></li>
									<li @click="showPrev" :class="isStartPage ? 'disabled' : ''"><a><</a></li>
									<li v-for="index in pageCount" @click="showPage(index - 1)" :class="page == (index - 1) ? 'active': ''"><a></a></li>
									<li @click="showNext" :class="isEndPage ? 'disabled' : ''"><a>></a></li>
									<li @click="showLast" :class="isEndPage ? 'disabled' : ''"><a>»</a></li>
								</ul>
							</div>
						</div>
						<div v-else class="col-sm-12">
							<p class="lead text-center">
								施工例が作成されていません
							</p>
						</div>
					</div>
				</div><!-- /dataTables_wrapper -->


JS

該当箇所は12〜28行目で、特に重要なのが21〜24行目になります
bootstrap-datetimepickerの変更時に設定された値をVueオブジェクトのdataに手動で反映するということをやっています

$(function() {
	var vm = new Vue({
		el: '#vue-list',
		data: {
			page: 0,
			per_page: 2,
			date_format: 'YYYY-MM-DD',
			list: null,
			loading: false,
			work_url: ':base_url'
		},
		directives: {
			datepicker: {
				bind: function (el, binding) {
					$(el).datetimepicker({
						ignoreReadonly: true,
						useCurrent: false,
						defaultDate: binding.value.expiration_at,
						format: vm.date_format
					}).on('dp.change', function(e) {
						var obj = _.findWhere(vm.list, { id: binding.value.id });

						Vue.set(obj, 'expiration_at', e.date.format(vm.date_format) );
						vm.updateDate(obj.id);
					});
				}
			}
		},
		created: function () {
			this.updateList();
		},
		computed:{
			displayData: function() {
				var page = this.page * this.per_page;
				return this.list.slice(page, page + this.per_page);
			},
			isStartPage: function(){
				return (this.page == 0);
			},
			isEndPage: function(){
				return ((this.page + 1) * this.per_page >= this.list.length);
			},
			pageCount: function() {
				return Math.ceil(this.list.length / this.per_page);
			}
		},
		methods: {
			// ページネイト
			showFirst: function () {
				this.page = 0;
			},
			showPrev: function () {
				if (!this.isStartPage) {
					this.page--;
				}
			},
			showNext: function () {
				if (!this.isEndPage) {
					this.page++;
				}
			},
			showLast: function () {
				this.page = Math.floor((this.list.length - 1) / this.per_page);
			},
			showPage: function (page) {
				this.page = page;
			}
		}
	});
});

まとめ

駆け足となってしまったため、不明点や指摘をいただける方はFB(keita.sakakibara.9216)でご連絡ください
Vue.jsをまじめに使い始めて2ヶ月ほどですが、かなり使いやすいなというイメージです
簡易的なFinderをVue.js + Railsで実装してみましたが、1週間もかからずに作成することができました
フロントサイドはやることが多すぎてとっつきにくいですが、一度、慣れてくるとかなり楽しいです

フロントエンドサイドをまじめに触り始めて、ますます画面遷移等のView側の処理はフロントエンドで処理し
データ操作、提供部分だけサーバーサイドでやらせるのがいいのではないかと実感しています
Railsでwebpack用のGemがベータですが提供されているので、webpackの導入を試してみたりして、将来的にはフロントエンド、サーバーサイドできれいに処理が切り分けられるようにしたいです

次は、Vue.jsのコンポーネント周りの話ができたらなーっと思っています
っということで今回はこのあたりで終わらせていただきます

Category

Ranking