やりたいこと
都道府県のセレクトボックスを Select2 で作る。フォーム入力で選択肢を絞りこめるが、このとき選択肢文字列だけではなくひらがな、アルファベット (半角) でも絞り込めるようにしたい。
Select2 に渡す選択肢の配列は次を想定している。
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" },
];
ちなみに本投稿は Vue.js 3 で Select2 をラップして SFC (シングルファイルコンポーネント) としてコンポーネント化する例 – oki2a24 の続きにあたります。
まとめ
次のことを行えば可能と予想し、実際に実現できました。
- Select2 へ渡す Options に適当なプロパティ A, B を追加する。
- Select2 で絞り込むときにプロパティ A, B も含めて検索する。
完成したコード
laravel/resources/js/components/BaseSelect2.vue
です。 Select2 を Vue.js 3 のシングルファイルコンポーネント (SFC) としてラップしたものとなります。他のコンポーネントから利用されるものです。
- Select2 の検索の方法をカスタマイズするやり方はこの公式ページ Search | Select2 – The jQuery replacement for select boxes を読み込むことで実現できる。
props
にmatch
を追加し、ひらがなやアルファベットなどを値とするプロパティも検索対象とするような関数を受け付けるようにした。props.match
はonMounted
のselect2()
で設定するが、props.match
が引数として渡ってこなかった場合はselect2()
でmatch
は設定しないようにした。公式ページ Options | Select2 – The jQuery replacement for select boxes には何も言及がなかったが、そうしなければprops.match
を未設定時、つまり null の場合に、実行時エラーとなった。
<template>
<select ref="root">
<slot></slot>
</select>
</template>
<script>
import "select2";
import $ from "jquery";
import { ref, onMounted, onUnmounted, watch } from "vue";
export default {
name: "BaseSelect2",
props: {
match: {
type: Function,
default: null,
},
options: {
type: Array,
default: () => {
[];
},
},
modelValue: {
type: String,
default: null,
},
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const root = ref(null);
watch(
() => props.modelValue,
(modelValue) => {
// update modelValue
$(root.value).val(modelValue).trigger("change");
}
);
watch(
() => props.options,
(options) => {
// update options
$(root.value).empty().select2({ data: options });
}
);
onMounted(() => {
const config = { data: props.options };
if (props.match) {
config.matcher = props.match;
}
console.log("config", config);
$(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>
続いて laravel/resources/js/views/SampleSelect2.vue
です。 BaseSelect2.vue
をこのファイルから呼び出して使っています。
- 本投稿の冒頭でも触れた通り、セレクトボックスの選択肢文字列以外に、それぞれのひらがな、アルファベットでも検索できるようにしたい。そのために、選択肢となるデータ配列の要素として、
id
,text
以外にhiragana
,alphabet
を追加した。 - 公式ページの Customizing how results are matched – Search | Select2 – The jQuery replacement for select boxes をベースに検索方法の関数を書いた。
- 検索フォーム入力文字列が
hiragara
,alpabet
の値に含まれているかどうかを判定するようにした。その前にオブジェクトにhiragara
,alpabet
が定義されていない (undefined
) ならばfilter
で取り除くようにしている ([undefined, null, 'a'].filter((i) => i)
) 。今回追加部分とコメントした箇所が該当部分となる。 - 参考元にある、
data.text
のundefined
判定とマッチ判定は、今回追加部分に取り込むようにもできたが、やり方を本投稿では示したかったのであえて残した。 - jQuery を使わないようにした。
- アロー関数へと変更した。
- 検索フォーム入力文字列が
<script>
部分にmatchCustom
を定義した。setup
からのreturn
に含まれるようにすれば良いので、別にsetup
内で定義しても他の方法でも問題ないはず。
<template>
<div class="container">
<h1>Select2 ラッパー SFC の利用</h1>
<p>Selected: {{ selected }}</p>
<base-select-2 v-model="selected" :match="matchCustom" :options="options">
<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" },
];
return {
matchCustom,
options,
selected,
};
},
};
</script>
以上で実現できました!
おわりに
さまざまな種類の入力方法に対応した絞り込みができるようになると、選択肢が数百やそれ以上だとしても、絞り込み前提としてセレクトボックスを使う、という選択も取れそうだと、やってみて感じました♪
以上です。