Vue.jsとjQueryライブラリを共存させなければいけない場合は、コンポーネントに閉じ込める。(select2版)
2018-12-04禁断の融合魔術
もうjQueryなんて使いたくない! Vue.jsや、Reactを使って脱jQueryするんだ!
こんな、言葉が叫ばれて久しい昨今、皆様どうお過ごしでしょうか?
新しいプロジェクトでは、ほとんどが、脱jQeryを果たしていても、各フレームワークのテンプレートエンジン+jQuery+Vueが融合しているプロジェクトも実はあったりするのではないでしょうか?
または、お客様から、
「いやだ!この『ポワン』っていうアニメーションが気に入ってたんだから、このカレンダーはそのままのデザインにしてほしい!」
などの要望があり、泣く泣く、jQueryライブラリを使う事もあるかもしれません。
そんなときは、以下の方法で、なるべく被害が広がらないような、状態にしておきましょう(もちろん、Vue.jsを使用する場合は、jQueryを極力使わない選択をするのがベストです。)
対応前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
mounted: function () { /*----------------------* jQueryライブラリの初期化 *----------------------*/ let self = this; $(function () { // select2は、store.jsで実施 ① // Date picker $('.datepicker').datepicker({ format: 'yyyy-mm-dd', todayHighlight: true, language: 'ja', autoclose: true, }).on({ //ここに日付変更したときの処理を書く }); // モーダルが閉じられた $('.modal').on('hidden.bs.modal', function (e) { self.$store.dispatch('modal/hideUpdateModal'); self.$store.commit('modal/clearErrors'); }); }); }, |
絶望を感じる状況です。
Vue.js内で、jQueryを使う場合は、各コンポーネントの mounted に毎回jQueryライブラリの初期化処理を書くのが、おそらく・・多分・・定石だと思います。
さらに、各jQueryライブラリの初期化処理を methods に分けて書いて、mountedで、一つずつ呼び出す形にするとほんの少しだけスッキリします。
しかし、①のコメントをよく見ると、「select2は、store.jsで実施」と書いてあります。
な、、なんだってー!jQueryをVuex側で使用しているのか・・。
早速、見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
actions: { // jQueryライブラリselect2の初期化 initSelect2({ commit, dispatch }) { $(function() { // select2 Vue.nextTick(function() { if (typeof $("select.select2").select2 == "function") { $("select.select2") .select2() .off("change"); $("select.select2") .select2({ language: { noResults: function() { return "見つかりません。"; } }, escapeMarkup: function(markup) { return markup; }, width: "100%" }) .on("change", e => { var value = e.target.value; if (e.target.multiple) { value = []; var values = $(e.target).select2("data"); values.forEach(function(val) { value.push(val.id); }); } var key_name = $(e.target).data("key_name"); var form_type = $(e.target).data("form_type"); switch (form_type) { case "staff": //①ここにdata属性(form_type)が、staffだったときの処理を書く break; case "user": //②ここにdata属性(form_type)が、userだったときの処理を書く break; case "type": //③略 break; } }); } }); }); } }, |
とても辛い・・・。
特に、同じ画面でselect2などのライブラリを複数使用する時は、何回も同じ初期化処理を書かなくてはいけないので、classは同一にしたりしますよね。
すると、今度は①〜③のような感じで、data属性や、nameなどでイベント処理を分岐させたりしなければいけなくなりカオスです。
一番の問題は、どこにjQueryの記述を書くのか?というルールがないという事です。
jQueryには「CSSセレクタ」という強力な機能がありますので、何処であろうが、DOMを操作することができ、コンポーネントではなく、Vuex側に初期化処理を書いた場合、一体どこでイベントが発火するのかわからなくなってしまいます。
したがって、jQueryライブラリは、コンポーネントに閉じ込め、$emitで親コンポーネントにデータ(日付など)を渡したほうがいいのではないか?と思い試してみました。
その1.templateでそっと優しく包み込む
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<template> <div class="select2" :style="style"> <!-- ① refを使用する--> <select lass="form-control input-md" placeholder="担当者" :multiple="multiple" ref="select2" v-model="selectedList" > <option value="">-未選択-</option> <option v-for="(text, value) in optionList" :value="value" v-text="text" > </option> </select> </div> </template> |
【注意】
同じページで何度も使用する可能性大なのでidは使わず、refを使用しましょう。
idをつけると、イベント発火がめちゃくちゃになります。
担当者を選択したはずなのに、ユーザを選ぶ為のselect2が選択されたりします。
その2.初期化処理は、mountedで
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<script> export default { data: function() { return { selectedList : [], } }, props: { //① データは全て親から受け取る optionList: { type: Object, }, multiple: { type: Boolean, default: false }, selectValue: '' }, mounted () { let self = this; //②self.$refs.select2をjQueryオブジェクト化する $select2 = $(self.$refs.select2); // select2 self.$nextTick(function () { $(function () { $select2.select2({ language: {"noResults": function(){ return "見つかりません。";}}, escapeMarkup: function (markup) { return markup; }, width: '100%' }) .on( 'change', (e) => { let value = e.target.value; let select_ids = $select2.val(); select_ids = e.target.multiple ? select_ids : value; self.selectedList = select_ids; //③$emitで親にデータを渡す self.$emit('change', self.selectedList) } ); //③親からpropsで渡したデータで表示も初期化する $select2.val(self.selectValue).trigger('change'); }); }); }, } </script> |
①動的なデータは全て、propsで親から受け取ります。
select2で必要になりそうなデータは、
optionList:option用のリスト
multiple:複数選択かどうか?
selectValue:デフォルトで選択されている値
の3つです。
②self.$refs.select2をjQueryオブジェクト化する
やはりjQueryライブラリの初期化は、mountedで実施します。
気をつけなければならないのは「絶対にCSSセレクタを使ってはならぬ」ということです。
refを使うことで、このコンポーネント内で、処理が完結します。
③$emitで親にデータを渡す
コレが一番のミソです。
select2でもdatepickerでも、値を選択した時に、Vuexのactionやmutationsを呼び出してしまうと、jQueryライブラリを使用する度に、Vuexに処理を追加しなくてはならず、面倒くさいし、可読性が一気に下がります。
これで、jQueryをコンポーネントの中に閉じ込めることができました。
その3.コンポーネントを呼び出す。
1 2 3 4 5 6 7 8 9 10 11 12 |
<tr> <th>担当者</th> <td> <div class="row"> <select2 :option-list="staffOptionList" :select-value='basicForm.m_staff_id' @change="updateSelectData($event)" ></select2> </div> </td> </tr> |
使用する時は、上記のような感じでpropsでデータを渡します。
今回は、複数選択なしなので、multipleのデータは渡さず、デフォルト値を使います。
$emitで発火させた、changeイベントで処理を実施するように記載してください。
「$event」にselect2コンポーネントから引数で渡されたデータが入っています。
これで(少しだけ)平和な世界が訪れました。めでたい!
—————————-
この記事がお役に立てたら、是非シェアをお願いします^^