LoginSignup
6
5

More than 3 years have passed since last update.

Vuetifyのv-data-tableで階層構造のデータを検索できるようにする

Last updated at Posted at 2020-03-10

最近はAngularからVue.jsに浮気中の8amjpです。Vue.jsめっちゃ楽しいです。

v-data-tableコンポーネントを使う

さて、下記のようなデータがあるとします。
(ちなみにこのデータ、Zabbix APIで取得できるホスト一覧データから抜粋したものです)

[
    {
        "hostid": "1",
        "host": "Server 1",
        "description": "サーバ1号機",
        "parentTemplates": [
            {
                "name": "Windows Servers",
                "templateid": "1"
            },
            {
                "name": "Web Servers",
                "templateid": "2"
            }
        ],
        "interfaces": [
            {
                "interfaceid": "1",
                "ip": "192.168.1.1",
                "port": "80",
            }
        ]
    },
    {
        ....

よくあるタイプのJSONデータですね。parentTemplatesinterfacesといった項目が配列になっており、その中に複数の子オブジェクトが格納されている……という階層構造になっています。

さて、このデータを、Vuetifyv-data-tableコンポーネントを使って表示すると、こんな感じになると思います。

<template>
  <v-data-table :headers="headers" :items="items" :search="search">
    <template v-slot:top>
      <v-text-field v-model="search" append-icon="mdi-magnify" label="Search" single-line/>
    </template>
    <template v-slot:item.parentTemplates="{ item }">
      <div v-for="i in item.parentTemplates" :key="i.templateid">{{ i.name }}</div>
    </template>
    <template v-slot:item.interfaces="{ item }">
      <div v-for="i in item.interfaces" :key="i.interfaceid">{{ i.ip }}:{{ i.port }}</div>
    </template>
  </v-data-table>
</template>

<script>
export default {
  props: ['items'],
  data () {
    return {
      search: '',
      headers: [
        {
          text: 'ホスト名',
          value: 'host',
        },
        {
          text: '説明',
          value: 'description',
        },
        {
          text: 'テンプレート',
          value: 'parentTemplates',
        },
        {
          text: 'インターフェース',
          value: 'interfaces',
        },
      ]
    }
  },
}
</script>

階層構造のデータは、そのままだと表示できないので、item.<name>スロットを使ってv-forですべてのデータを表示したりします。便利ですねー。

カスタムフィルタを使う

さて、ここから本題。

v-data-tableは、データを簡単にフィルタリングするためにsearchプロパティが用意されています。
バインドされたテキストフィールドに文字を入力すると、その文字列が含まれる行のみをフィルタリングできます。よくある機能ではありますが、最初から用意されていると実装もとても簡単。

ところが、です。前述のデータで言うと、hostdescriptionなどの項目はフィルタリングできるのですが、parentTemplatesinterfacesの項目に含まれるデータはフィルタリングができません。例えば、「Windows」というキーワードを入力してもヒットしません。
理由はもちろん、文字列じゃないから。さすがに配列の中身までは検索してくれません。
こういう時こそカスタムフィルタの出番です。

標準のフィルタを確認する

まずは、標準のフィルタがどういう動きをしているのか確認します。

function defaultFilter(value, search, item) {
  return value != null &&
    search != null &&
    typeof value !== 'boolean' &&
    value.toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1;
}

この中のvalueという変数に、JSONデータの「値」の部分が入るんですけど。
値が配列の場合でも、うまい具合に文字列に変換して渡すことで、なんとかなりそうな感じがします。

では実際にやってみましょう。v-data-tablecustom-filterプロパティに独自のメソッドを追加することで、動作を上書きすることができるんです。
まずはcustom-filterプロパティを追加して、独自のメソッドを指定します。

  <v-data-table :headers="headers" :items="items" :search="search" :custom-filter="customFilter">

で、追加したメソッドで、フィルタリングの動作を上書きします。こんな感じで。

  methods: {
    customFilter (value, search) {
      return value != null &&
        search != null &&
        typeof value !== 'boolean' &&
        // value.toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1;
        (typeof value === 'object' ? value.map(v => v.name).join('\t') : value)
          .toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1;
    },
  },

まず、変数value(=JSONデータの「値」)の型をtypeof演算子で判定します。
で、objectだった場合は配列とみなし、.mapメソッドで配列内のnameプロパティのみを抽出して、タブ文字で連結して単一の文字列にしています。
このように、単一の文字列にしてしまうことで、検索ができるようになります。
(当然ながら、typeofobjectが返ってきても、配列じゃない場合もあるんですけど。その辺は使用するデータに応じて各自いい感じに処理を分岐してくださいね(丸投げ))

ついでに、interfaces配列内のipプロパティも検索対象に含めたい場合は、こんな感じでどうでしょうか。

        (typeof value === 'object' ? value.map(v => v.name || v.ip).join('\t') : value)
          .toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1;

完成

完成品はこちらです。わーい。

<template>
  <v-data-table :headers="headers" :items="items" :search="search" :custom-filter="customFilter">
    <template v-slot:top>
      <v-text-field v-model="search" append-icon="mdi-magnify" label="Search" single-line/>
    </template>
    <template v-slot:item.parentTemplates="{ item }">
      <div v-for="i in item.parentTemplates" :key="i.templateid">{{ i.name }}</div>
    </template>
    <template v-slot:item.interfaces="{ item }">
      <div v-for="i in item.interfaces" :key="i.interfaceid">{{ i.ip }}:{{ i.port }}</div>
    </template>
  </v-data-table>
</template>

<script>
export default {
  props: ['items'],
  data () {
    return {
      search: '',
      headers: [
        {
          text: 'ホスト名',
          value: 'host',
        },
        {
          text: '説明',
          value: 'description',
        },
        {
          text: 'テンプレート',
          value: 'parentTemplates',
        },
        {
          text: 'インターフェース',
          value: 'interfaces',
        },
      ]
    }
  },
  methods: {
    customFilter (value, search) {
      return value != null &&
        search != null &&
        typeof value !== 'boolean' &&
        (typeof value === 'object' ? value.map(v => v.name || v.ip).join('\t') : value)
          .toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1;
    },
  },
}
</script>
6
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5