カテゴリー
コンピューター

Vue.js 3 で Select2 をラップした SFC その2 。選択肢文字列以外の文字列 (ひらがなアルファベットなど) で絞り込む方法 (ちなみに Laravel 6 、 Vue.js 3 の環境)

やりたいこと

都道府県のセレクトボックスを 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 の続きにあたります。

まとめ

次のことを行えば可能と予想し、実際に実現できました。

  1. Select2 へ渡す Options に適当なプロパティ A, B を追加する。
  2. Select2 で絞り込むときにプロパティ A, B も含めて検索する。

完成したコード

laravel/resources/js/components/BaseSelect2.vue です。 Select2 を Vue.js 3 のシングルファイルコンポーネント (SFC) としてラップしたものとなります。他のコンポーネントから利用されるものです。

  • Select2 の検索の方法をカスタマイズするやり方はこの公式ページ Search | Select2 – The jQuery replacement for select boxes を読み込むことで実現できる。
  • propsmatch を追加し、ひらがなやアルファベットなどを値とするプロパティも検索対象とするような関数を受け付けるようにした。
  • props.matchonMountedselect2() で設定するが、 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.textundefined 判定とマッチ判定は、今回追加部分に取り込むようにもできたが、やり方を本投稿では示したかったのであえて残した。
    • 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>

以上で実現できました!

おわりに

さまざまな種類の入力方法に対応した絞り込みができるようになると、選択肢が数百やそれ以上だとしても、絞り込み前提としてセレクトボックスを使う、という選択も取れそうだと、やってみて感じました♪

以上です。

コメントを残す