やったこと
- Select2 の config をなんでも受け取れるようにする。
props
の変更検知は 1 つの watch にまとめられるのではないか?- Bootstrap 4 テーマの時、バリデーションエラーのスタイルを適用できるようにする。
Select2 ラッパー SFC とそれを利用するコンポーネントのコード全体
カスタマイズした Select2 ラッパー SFC の laravel/resources/js/components/BaseSelect2.vue
です。
<template>
<div :class="classValue">
<select ref="root">
<slot />
</select>
</div>
</template>
<script>
import _ from "lodash";
import "select2";
import { ref, onMounted, onUnmounted, watch } from "vue";
import $ from "jquery";
export default {
name: "BaseSelect2",
props: {
classValue: {
type: String,
require: false,
default: null,
},
config: {
type: Object,
require: false,
default: () => {},
},
options: {
type: Array,
require: true,
default: () => {
[];
},
},
modelValue: {
type: String,
require: false,
default: null,
},
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const root = ref(null);
const config = {
data: props.options,
theme: "bootstrap",
...props.config,
};
watch(
() => props.modelValue,
(modelValue) => {
// update modelValue
$(root.value).val(modelValue).trigger("change");
}
);
watch(
() => props.options,
(options) => {
// update options
$(root.value)
.empty()
.select2({ ...config, data: options });
}
);
watch(
() => _.cloneDeep(props),
(props, prevProps) => {
console.log("watch props", props, prevProps);
//$(root.value)
// .empty()
// .select2({ ...config, data: props.options, ...props.config })
// .val(props.modelValue)
// .trigger("change");
},
{ deep: true }
);
onMounted(() => {
$(root.value)
// init select2
.select2(config)
.val(props.modelValue)
.trigger("change")
// emit event on change.
.on("change", (event) => {
emit("update:modelValue", event.target.value);
});
});
onUnmounted(() => {
$(root.value).off().select2("destroy");
});
return { root };
},
};
</script>
Select2 ラッパー SFC を利用するコンポーネントの laravel/resources/js/views/SampleSelect2.vue
です。
<template>
<div class="container">
<h1>Select2 ラッパー SFC の利用</h1>
<p>Selected: {{ selected }}</p>
<base-select-2
v-model="selected"
:config="config"
:options="options"
:class-value="'is-invalid'"
>
<option disabled value="0">Select one</option>
</base-select-2>
</div>
</template>
<script>
import { ref } from "vue";
import BaseSelect2 from "../components/BaseSelect2.vue";
//function matchCustom(params, data) {
const matchCustom = (params, data) => {
// If there are no search terms, return all of the data
//if ($.trim(params.term) === "") {
if (!params.term || params.term.trim() === "") {
return data;
}
// Do not display the item if there is no 'text' property
if (typeof data.text === "undefined") {
return null;
}
// `params.term` should be the term that is used for searching
// `data.text` is the text that is displayed for the data object
if (data.text.indexOf(params.term) > -1) {
//var modifiedData = $.extend({}, data, true);
let modifiedData = { ...data };
modifiedData.text += " (matched)";
// You can return modified objects from here
// This includes matching the `children` how you want in nested data sets
return modifiedData;
}
// 今回追加部分
const hitted = [data.hiragana, data.alphabet]
.filter((element) => element)
.some((element) => {
return element.indexOf(params.term) > -1;
});
if (hitted) {
return data;
}
// Return `null` if the term should not be displayed
return null;
};
export default {
name: "SampleSelect2",
components: { BaseSelect2 },
setup() {
const selected = ref("2");
const options = [
{
id: "1",
text: "北海道",
hiragana: "ほっかいどう",
alphabet: "hokkaido",
},
{
id: "2",
text: "青森県",
hiragana: "あおもりけん",
alphabet: "aomoriken",
},
{ id: "3", text: "岩手県", hiragana: "いわてけん", alphabet: "iwateken" },
{
id: "4",
text: "宮城県",
hiragana: "みやぎけん",
alphabet: "miyagiken",
},
{ id: "5", text: "秋田県", hiragana: "あきたけん", alphabet: "akitaken" },
];
const config = { matcher: matchCustom };
return {
config,
options,
selected,
};
},
};
</script>
Select2 の config をなんでも受け取れるようにする。
setup
内で config の変数を定義し、定義する時に props.config
を展開するようにしてみました。
const config = {
data: props.options,
theme: "bootstrap",
...props.config,
};
onMounted
時はこの setup
内定義の config を使い、 watch
時などで Select2 の config の内容を変えたいときは、この setup
内定義の config を展開したものに新しい porps.config
を展開して上書きする、という流れとなります。
watch(
() => props.options,
(options) => {
// update options
$(root.value)
.empty()
.select2({ ...config, data: options });
}
);
props
の変更検知は 1 つの watch にまとめられるのではないか?
今までは props.modelValue や props.options を個別に監視していました。これを、 props 全体を watch するようにすればコードの変更が容易で見通しもよくなるのではないか ? (ただし 1 つの watch に全ての props の変更に対応する処理を書く必要があるので、コードは複雑になってしまいそう )というアイデアです。
を参考に、次のようにすれば props を監視できることまで、突き止めました。
import _ from "lodash";
... 略 ...
watch(
() => _.cloneDeep(props),
(props, prevProps) => {
console.log("watch props", props, prevProps);
//$(root.value)
// .empty()
// .select2({ ...config, data: props.options, ...props.config })
// .val(props.modelValue)
// .trigger("change");
},
{ deep: true }
);
Bootstrap 4 テーマの時、バリデーションエラーのスタイルを適用できるようにする。
LaravelでSelect2とBootstrap4を使って、バリデーションエラー時に赤枠で囲む – Qiita のページが参考になりました ! ありがとうとございます。
修正内容は簡単で、 Select2 を適用する <select>
を <div>
で囲み、この <div>
に class="is-invali"
が適用できるようにするだけです。
ただ、props で class という名前は使えませんでした。おそらく予約語なのだと思います。次のエラーとなりました。ですので、 classValue という名前にしました。
$ npm run lint
... 略 ...
/var/www/html/laravel/resources/js/components/BaseSelect2.vue
2:21 error Parsing error: Unexpected end of expression vue/no-parsing-error
✖ 1 problem (1 error, 0 warnings)
コードとしては次のようになりました。
Select2 ラッパー SFC の laravel/resources/js/components/BaseSelect2.vue
です。
<div :class="classValue">
<select ref="root">
<slot />
</select>
</div>
Select2 ラッパー SFC を利用するコンポーネントの laravel/resources/js/views/SampleSelect2.vue
です。 :class-value="'is-invalid'"
とベタガキしていますけれども、本来であれば変数を当てての利用が主となる想定です。
<base-select-2
v-model="selected"
:config="config"
:options="options"
:class-value="'is-invalid'"
>
おわりに
今までのシリーズです。
- Vue.js 3 で Select2 をラップして SFC (シングルファイルコンポーネント) としてコンポーネント化する例 – oki2a24
- Vue.js 3 で Select2 をラップした SFC その2 。選択肢文字列以外の文字列 (ひらがなアルファベットなど) で絞り込む方法 (ちなみに Laravel 6 、 Vue.js 3 の環境) – oki2a24
- Vue.js 3 で Select2 をラップした SFC その3 。 Select2 に Bootstrap 4 のテーマを適用する – oki2a24
今後も少しずつ変わるかもしれません。
以上です。