Vue3でjQueryのdatepickerを使いたい!実装手順と注意点を解説します。
皆様、ご無沙汰しております。
今回は、先日Vue3とjQueryのDatepickerを組み合わせて実装する機会があったので、その方法と、つまずいたポイントおよび解決策についてご紹介したいと思います。
要件
- 日付の入力欄が複数個欲しい
- 入力欄の個数は任意で増減させることができる
- それぞれの入力欄をカレンダーピックを使って選択したい
前提条件
- Vue 3.5.9
- jQuery 3.6.1
完成形
以下画像のような挙動を実現できるようにしたいです。
- 初期状態は入力欄の枠なしで、枠を追加できるように「+」ボタンを置いておく。
- 「+」ボタンで入力欄の枠を増やすことができ、「-」ボタンで入力欄の枠を減らすことができる。
- 各入力欄にdatepickerを使って、カレンダーを表示して、日付を入力できる。
準備
以下のhtmlファイルとjsファイルと画像達を用意して、必要なライブラリたちをCDN経由で読み込みます。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>use datepicker in vue and jquery</title>
</head>
<body>
<h1>Use datepicker in vue and jquery</h1>
<div id="app">
<div>
<h2>日程</h2>
</div>
<div>
<!-- ここに追加していく -->
</div>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.9/dist/vue.global.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
index.js
const { createApp } = Vue
createApp({
delimiters: ["${", "}"],
data() {
return {
// ここに追加していく
};
},
methods: {
// ここに追加していく
isEmpty: function (obj) {
return Object.keys(obj).length === 0;
},
},
}).mount("#app");
実装
追加した全体は以下の通りです。
追加した全体
index.html
<div>
<template v-if="isEmpty(dates)">
<a href="javascript:void(0);" @click="addDateBox(0)">
<img src="./addbutton.png" alt="枠追加ボタン"></a>
</template>
<template v-else>
<div v-for="date in dates" :key="date.id" class="sp-pt020">
<input class="sys-input-txt sys-txt20" type="text" v-model="date.value"
:ref="`date${date.id}`">
<a href="javascript:void(0);" @click="addDateBox(date.id+1)">
<img src="./addbutton.png" alt="枠追加ボタン"></a>
<a href="javascript:void(0);" @click="removeDateBox">
<img src="./removebutton.png" alt="枠削除ボタン"></a>
</div>
</template>
</div>
index.js
const { createApp } = Vue
createApp({
delimiters: ["${", "}"],
data() {
return {
dates: [],
};
},
watch: {
"dates.length"(newVal, oldVal) {
if (newVal !== oldVal) {
let id = newVal - 1;
this.$nextTick(() => {
jQuery(this.$refs['date'+id]).datepicker({
dateFormat: "yy-mm-dd",
showOn: "button",
buttonImage: "./calendar.png",
buttonImageOnly: true,
onSelect: (dateText) => {
this.dates[id].value = dateText;
},
});
});
}
},
},
methods: {
addDateBox(id) {
this.dates.push({ id: id, value: "" });
},
removeDateBox() {
this.dates.pop();
},
isEmpty: function (obj) {
return Object.keys(obj).length === 0;
},
},
}).mount("#app");
これ以降、部分的に見ていきます。
プロパティの作成
data() {
return {
dates: [],
};
},
{ id: id, value: ''}
というようなオブジェクトの配列となるdates
プロパティを作ります。
枠追加ボタンの表示
<template v-if="isEmpty(dates)">
<a href="javascript:void(0);" @click="addDateBox(0)"><img src="./addbutton.png" alt="枠追加ボタン"></a>
</template>
クリックするとdates
プロパティにオブジェクトを追加するaddDateBox()
関数を@click
で発火するよう設定してます。
また、dates
プロパティが空の場合は入力欄は表示せず、枠追加ボタンだけ表示するようにisEmpty()
関数を使って制御しています。
addDateBox()の作成
addDateBox(id) {
this.dates.push({ id: id, value: "" });
},
入力欄の作成
<div v-for="date in dates" :key="date.id" class="sp-pt020">
<input class="sys-input-txt sys-txt20" type="text" v-model="date.value" :ref="`date${date.id}`">
</div>
v-model
にdate
プロパティのvalue
を設定し、:ref
属性にはdateという文字列に各date
プロパティのid
の値をくっつけて一意の文字列にしています。これにより入力欄(inputタグ)が複数個になっても、各入力欄(inputタグ)の任意のものを参照できるようになります。
躓いた箇所①
<div v-for="date in dates" :key="date.id" class="sp-pt020">
<input class="sys-input-txt sys-txt20" type="text" v-model="date.value" :ref="date">
</div>
:ref
属性の値をオブジェクトで設定したところ、jqueryのdatepickerで:ref
属性の値を参照できなくなった。一意の値になるようにすると上手くいく。
ただ、そもそものvueプロジェクトの作り方によっては、これで上手くいく場合もあった。難しい、、、
removeDateBox()の作成
removeDateBox() {
this.dates.pop();
},
ループ内での枠追加ボタンと枠削除ボタンの作成
<a href="javascript:void(0);" @click="addDateBox(date.id+1)">
<img src="./addbutton.png" alt="枠追加ボタン"></a>
<a href="javascript:void(0);" @click="removeDateBox">
<img src="./removebutton.png" alt="枠削除ボタン"></a>
datepickerの設定
watch: {
"dates.length"(newVal, oldVal) {
if (newVal !== oldVal) {
let id = newVal - 1;
this.$nextTick(() => {
jQuery(this.$refs['date'+id]).datepicker({
dateFormat: "yy-mm-dd",
showOn: "button",
buttonImage: "./calendar.png",
buttonImageOnly: true,
onSelect: (dateText) => {
this.dates[id].value = dateText;
},
});
});
}
},
},
何をしたいかと言うと、各入力欄毎にクリック用のカレンダー画像とdatepickerのカレンダーを表示させて、選択できるようにしたい。これを達成するために、dates
配列の長さが変更されたときに、新しいdatepickerを初期化し、ユーザーが選択した日付を対応するdates
配列の要素に設定できるようにします。
watch
プロパティを使用して、dates.length
の変更を監視し、dates.length
が変更されると、指定された関数が呼び出されます。
newVal
はdates.length
の新しい値、oldVal
は以前の値で、newVal
とoldVal
が異なる場合に処理が走るようにします。
this.$nextTick
:$nextTick
メソッドは、DOMの更新が完了した後に指定されたコールバックを実行するので、これを利用してDOMが更新された後にjQueryのdatepickerを初期化しています。
jQuery(this.$refs['date'+id]).datepicker
:this.$refs['date'+id]
で、Vue.jsのref
属性を使用して各入力欄(inputタグ)のDOM要素を参照しています。idはnewVal - 1
で計算し、これはdates
配列の最後の要素のidとなるようにします。
onSelect
コールバック:ユーザーが日付を選択したときに呼び出されるコールバック関数で、選択された日付(dateText
)をthis.dates[id].value
に設定します。
躓いた箇所②
addDateBox(id) {
this.dates.push({ id: id, value: "" });
jQuery(this.$refs["date" + id]).datepicker({
dateFormat: "yy-mm-dd",
showOn: "button",
buttonImage: "./calendar.png",
buttonImageOnly: true,
onSelect: (dateText) => {
this.dates[id].value = dateText;
},
});
},
datepickerの初期化のタイミングを、dates
配列の値が更新された後にしないといけない。例えば、addDateBox()
関数で実装すると、カレンダーピックの読み込みがvueの再レンダリングの前に行われて、以下の画像の通り上手くいかない。
おわりに
以上、いかがでしたでしょうか。
あまりないユースケースだと思いますが、と同時にあまり参考になる記事もなかったので、いつかどこかで誰かの一助となれば幸いです。
この記事を書いた人
-
大学4年時春に文系就職を辞め、エンジニアになることを決意し、独学でRuby、Ruby on Railsを学習。
約1年間の独学期間を経てアーティスへWebエンジニアとして入社。現在はWebエンジニアとして、主にシステムの開発・運用に従事している。
抽象的なもの、複雑なものを言語化して文章にするのが好きで得意。
この執筆者の最新記事
- 2024年10月8日WEBVue3でjQueryのdatepickerを使いたい!実装手順と注意点を解説します。
- 2024年8月21日WEBVue3の非同期コンポーネントを使ってみる
- 2024年5月28日WEBLaravel×Inertia×Vue3でファイルアップロード機能を作ってみた
- 2024年4月15日WEBLaravel×Inertia×Vue3でCRUD機能を持つSPAを作ってみた
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー