Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Vuetify.js 2.3のData iteratorsコンポーネントについて

概要

Data iteratorsコンポーネントの初歩的な使い方のまとめです。

環境

  • Windows 10 Professional 20H2
  • Node.js 12.18.0
  • Vue.js 2.6.11
  • Vuetify.js 2.3.1

参考

Data iterators

The v-data-iterator component is used for displaying data, and shares a majority of its functionality with the v-data-table component. Features include sorting, searching, pagination, and selection.
--------------------------------------------------------------------------------------------
v-data-iteratorコンポーネントはデータの表示に使用され、その機能の大部分をv-data-tableコンポーネントと共有します。 機能には、並べ替え、検索、ページ付け、および選択が含まれます。

上記で引用した通り、v-data-iteratorコンポーネントはデータの操作(ソート、検索、ページネーション、グルーピング)を担当していて、表示したいデータ(アイテムの配列)を与えるだけで、ほぼコーディングレスでこれらの操作を実現できます。
ただし、データ表示についてはこのコンポーネントの担当外なので、他のコンポーネントなどで別途実装する必要があります。

データを表形式で表示したいといった場合は、v-data-tableコンポーネントが便利です。このコンポーネントは内部的にv-data-iteratorコンポーネントを利用しているので、このコンポーネントの機能が利用できるうえに、デフォルトのヘッダーやフッターも利用できます。
(※この記事ではv-data-tableコンポーネントには触れません)

Props

VDataIterator

VDataIterator ---- VData
              |
              `--- Themeable
name type default relation src
VDataIterator item-key string 'id' 'id'
value array [] []
single-select boolean false
expanded array [] []
mobile-breakpoint number, string 600 600
single-expand boolean false
loading boolean, string undefined
no-results-text string '$vuetify.dataIterator.noResultsText' search '$vuetify.dataIterator.noResultsText'
no-data-text string '$vuetify.noDataText' '$vuetify.noDataText'
loading-text string '$vuetify.dataIterator.loadingText' loading '$vuetify.dataIterator.loadingText'
hide-default-footer boolean false
footer-props object undefined v-data-footer
selectable-key string 'isSelectable' 'isSelectable'
VData items array [] []
options DataOptions *1)参照 {}
sort-by string, array [] []
sort-desc boolean, array [] []
custom-sort function *2)参照 sortItems
must-sort boolean false
multi-sort boolean false
page number 1 1
items-per-page number 10 10
group-by string, array [] []
group-desc boolean, array [] []
custom-group function *3)参照 groupItems
locale string 'en-US' 'en-US'
disable-sort boolean false
disable-pagination boolean false
disable-filtering boolean false
search string undefined
custom-filter function *4)参照 searchItems
server-items-length number -1 -1
Themeable dark boolean false
light boolean false

1) options

{
  page: number
  itemsPerPage: number
  sortBy: string[]
  sortDesc: boolean[]
  groupBy: string[]
  groupDesc: boolean[]
  multiSort: boolean
  mustSort: boolean
}

2) custom-sort

(items: any[], sortBy: string[], sortDesc: boolean[], locale: string, customSorters?: Record<string, compareFn>) => any[]

3) custom-group

(items: any[], groupBy: string[], groupDesc: boolean[]) => Record<string, any[]>

4) custom-filter

(items: any[], search: string) => any[]

I18N

プロパティのdefaultが$vuetify.で始まるものはVuetify.jsに組み込みのメッセージリソースキーです。実際のメッセージは各国語別に管理されています。

この記事ではデフォルトロケールが英語でしたが、ロケールをjaに変えることでメッセージを日本語にすることが可能です。下記が組み込みのメッセージです。

prop key en message text ja message text
no-results-text '$vuetify.dataIterator.noResultsText' 'No matching records found' '検索結果が見つかりません。'
no-data-text '$vuetify.noDataText' 'No data available' 'データはありません。'
loading-text '$vuetify.dataIterator.loadingText' 'Loading items...' '項目をロード中です...'

これらのプロパティにはメッセージリソースキーだけでなく、任意の文字列リテラルをメッセージとして指定することも可能です。

VDataFooter

name type default relation src
VDataFooter options object undefined
pagination object undefined
items-per-page-options array [5,10,15,-1] [5, 10, 15, -1]
prev-icon string '$prev' '$prev'
next-icon string '$next' '$next'
first-icon string '$first' '$first'
last-icon string '$last' '$last'
items-per-page-text string '$vuetify.dataFooter.itemsPerPageText' '$vuetify.dataFooter.itemsPerPageText'
items-per-page-all-text string '$vuetify.dataFooter.itemsPerPageAll' '$vuetify.dataFooter.itemsPerPageAll'
show-first-last-page boolean false
show-current-page boolean false
disable-pagination boolean false
disable-items-per-page boolean false
page-text string '$vuetify.dataFooter.pageText' '$vuetify.dataFooter.pageText'

I18N

prop en ja
page-text '$vuetify.dataFooter.pageText' '{0}-{1} of {2}' '{0}-{1} 件目 / {2}件'
items-per-page-text '$vuetify.dataFooter.itemsPerPageText' 'Items per page:' '1ページあたりの件数:'
items-per-page-all-text '$vuetify.dataFooter.itemsPerPageAll' 'All' 'すべて'

実装例

最小の実装

最小の実装はitemsプロパティに表示するデータ(アイテムの配列)を指定するだけです。フッターのページネーションはデフォルトのものが表示されます。

下記はitemsプロパティだけを指定したv-data-iteratorの実装例です。

<template>
  <v-container fluid>
    <v-data-iterator :items="items">
      <template v-slot:default="props">
        <v-row dense>
          <v-col v-for="item in props.items" :key="item.title" cols="12" sm="6" md="4" lg="3">
            <v-card tile color="brown lighten-2">
              <div class="d-flex flex-row align-stretch">
                <div class="pl-2 pt-5">
                  <v-chip outlined label color="brown darken-4">{{ item.no_in_series }}</v-chip>
                </div>
                <div>
                  <v-card-title>{{ item.title }}</v-card-title>
                  <v-card-subtitle>{{ item.japanese_title }}</v-card-subtitle>
                </div>
              </div>
              <v-divider></v-divider>
              <v-list dense>
                <v-list-item v-for="label in labels" :key="label.name">
                  <v-list-item-content>{{ label.name }}</v-list-item-content>
                  <v-list-item-content class="align-end">{{ transform(item, label) }}</v-list-item-content>
                </v-list-item>
              </v-list>
            </v-card>
          </v-col>
        </v-row>
      </template>
    </v-data-iterator>
  </v-container>
</template>

<script>
import items from '@/components/items'

const labels = [
  {
    name: '初回放送日(米)',
    prop: 'original_air_date'
  },
  {
    name: '初回放送日(日)',
    prop: 'japanese_air_date'
  },
  {
    name: '放映時間',
    prop: 'runtime'
  },
  {
    name: 'ゲスト',
    prop: 'guest_staring'
  }
]

export default {
  name: 'Iterator',
  data() {
    return {
      items: [],
      labels: labels
    }
  },
  mounted() {
    setTimeout(() => {
      this.items = items.map(item => {
        item.original_air_date = this.toDate(item.original_air_date)
        item.japanese_air_date = this.toDate(item.japanese_air_date)
        return item
      })
    }, 2000)
  },
  methods: {
    transform(item, label) {
      if (label.prop === 'japanese_air_date' || label.prop === 'original_air_date') {
        return this.formatDate(item[label.prop])
      } else if (label.prop === 'runtime') {
        return item[label.prop] + ''
      }
      return item[label.prop]
    },
    toDate(value) {
      if (!value) {
        return value
      }
      return new Date(Date.parse(value.toString()))
    },
    formatDate(value) {
      if (!value) {
        return 'unknown'
      }
      return `${value.getFullYear()}/${value.getMonth() + 1}/${value.getDate()}`
    }
  }
}
</script>

Screenshot_1.png

itemsプロパティとinternalCurrentItems

itemsプロパティのデータが直接画面に表示されているのではなく、v-data-iteratorコンポーネントが内部的に持つinternalCurrentItemsというデータが表示に利用されます。
v-data-iteratorはitemsプロパティのデータを操作(ソート、フィルター、グルーピング、ページネーション)し、その結果をinternalCurrentItemsに反映、その結果が画面に表示されます。

例えば1ページ当たりの表示件数を5件とした場合、internalCurrentItemsの要素数は5になります。
Screenshot_1.png

デフォルトスロット

データ表示の実装はデフォルトスロットで行います。スロットプロパティをpropsという名前で受け取ると、スロット内では表示するデータをprops.items、ページネーションをprops.pagination、ソートやグルーピングのオプションをprops.optionsといった名前でアクセスできます。
データ表示の仕方(デザイン)は実装者に任されていて、この例ではグリッド(v-container, v-row, v-col)とv-cardコンポーネントを使ってデータを表示しています。

<v-data-iterator :items="items">

  <template v-slot:default="props">

    // データ表示するコード
    <v-row>
      <v-col v-for="item in props.items">

         // データ1件分の表示
         <v-card>
           // 省略
         </v-card>

      </v-col>
    </v-row>

  </template>

</v-data-iterator>

デフォルトのフッター

デフォルトのフッターにデフォルトのページネーションが表示されています。
ページネーションに関するプロパティの初期値は、表示ページを指すpageは1、1ページ当たりの表示アイテム数を指すitems-per-pageは10となっています。
この部分はコーディング不要でプロパティを指定することである程度カスタマイズすることができます。(後述します)
Screenshot_2.png

v-data-footer

Vue Devtoolsで確認すると、v-data-footerコンポーネントが自動的に組み込まれていることがわかります。このv-data-footerコンポーネントのプロパティは、v-data-iteratorコンポーネントのfooter-propsプロパティを介して指定できます。
Screenshot_3.png

例えば、1ページ当たりの表示件数を変更する選択リストのアイテムは、v-data-footerコンポーネントのitems-per-page-optionsプロパティで変更できますが、直接指定できないのでfooter-propsプロパティを介して下記のように指定します。

<v-data-iterator :items="items" :footer-props="{ 'items-per-page-options': [3, 6, 9, -1] }">

なお、v-data-footerの複数のプロパティを変更したい場合は、下記のようにdataに定義しておくといいと思います。

<template>
  <v-container fluid>
    <v-data-iterator :items="items" :footer-props="footerProps">
      // 省略
    <v-data-iterator>
  </v-container>
</template>

<script>
export default {
  data() {
    return {
      items: [],
      footerProps = {
        'items-per-page-options': [3, 6, 9, -1],
        'show-first-last-page': true,
        'show-current-page': true
      }
    }
  },

}
</script>

デフォルトフッターの無効化

hide-default-footerプロパティでデフォルトのフッターを非表示にすることができます。

代替テキスト

itemsプロパティに指定したデータ(アイテムの配列)が空だった場合、代わりに表示するメッセージテキストをno-data-textプロパティで指定します。
デフォルト値は'$vuetify.noDataText'というメッセージリソースキーです。

ローディングテキスト

loadingプロパティをtrueにセットすると、itemsプロパティに要素数が0以上の配列がセットされるまで(つまりローディング中に)loading-textプロパティで指定したローディングテキストを表示するようになります。
(※端的に言うと、itemsプロパティが空の配列で且つloadingプロパティがtrueの場合はローディングテキストが表示されます)

loadingプロパティをtrueにしたままにすると、例えばサーバーから空のデータを取得した場合、データ取得処理が終わった後もローディング中のメッセージテキストが表示され続けます。
なので、サーバーからデータを取得する場合は、取得開始直前にtrueをセットしデータ取得終了直後にfalseに戻すといった実装が必要です。

代替テキスト

ローディング中に、代わりに表示するメッセージテキストをloading-textプロパティで指定します。
デフォルト値は'$vuetify.dataIterator.noResultsText'というメッセージリソースキーです。

ページネーション

ページネーションの設定は上述した通り、表示ページ位置をpageプロパティで、1ページ当たりの表示アイテム数をitems-per-pageプロパティで指定します。それぞれのプロパティは修飾子.syncを付けることができます。

<v-data-iterator :items="items" :page="1" :items-per-page="5">

ページネーションの無効化

ページネーションの機能を無効にしたい場合はdisable-paginationを指定しますが、このプロパティはv-data-iteratorとv-data-footerの両方にあり、指定するとそれぞれ違う結果になります。

v-data-iterator

無効にするとitems-per-pageプロパティの値は無視されデータは全件表示されます。また1ページ当たりの表示件数を選択するリストや改ページボタンは操作できますが機能しません。
Screenshot_4.png
(※見た目上はわかりませんが、操作しても表示されるデータには反映されません)

v-data-iterator

無効にするとitems-per-pageプロパティの値のデータ件数が表示されます。また1ページ当たりの表示件数を選択するリストは有効で表示するデータ件数を切り替えることができます。
ただし改ページボタンは非活性として表示されていてクリックできません。
Screenshot_5.png

プロパティのコメント

prop v-data-iterator v-data-footer
disable-pagination Disables pagination completely Disables pagination buttons

1ページ当たりの表示件数の選択の無効化

1ページ当たりの表示件数を選択するリストを無効化するにはv-data-footerコンポーネントのdisable-items-per-pageプロパティを指定します。
Screenshot_6.png

ソート

表示するデータのソートの仕方はv-data-iteratorコンポーネントのsort-bysort-descプロパティで指定します。それぞれのプロパティは修飾子.syncを付けることができます。

表示するアイテムが下記のようなオブジェクトの場合

{
  id: 1,
  modelNo: 'AC-01',
  name: 'BALL',
  price: 5800,
  releaseDate: '1980/04/28',
  premium: 'A',
  million: false
}

このオブジェクトのmodelNoの降順でソートするには、sort-byプロパティにオブジェクトのプロパティ名modelNoを、sort-descプロパティにtrueを指定します。

<v-data-iterator :items="items" sort-by="modelNo" :sort-desc="true">

複数のプロパティでソートする場合は、sort-bysort-descに配列でプロパティ名と並び順を指定します。
たとえばpriceの昇順、modelNoの降順でソートするには下記のように記述します。

<v-data-iterator :items="items" :sort-by="['price', 'modelNo']" :sort-desc="[true, false]">

デフォルトのソート

デフォルトのソート実装はvuetify/src/util/helpers.ts#L285sortItems関数です。(関数のシグネチャだけ記載します。)

export function sortItems<T extends any = any> (
  items: T[],
  sortBy: string[],
  sortDesc: boolean[],
  locale: string,
  customSorters?: Record<string, DataTableCompareFunction<T>>
): T[] {

  // 省略

}

カスタムソート

custom-sortプロパティに独自のソートを行うファンクションを指定すると、このファンクションによるソートを行うことができます。

customSort(items, sortBy, sortDesc, locale, customSorters) {
  const sortedItems = []

  // 独自のソート処理

  return sortedItems
}

ソートの無効化

disable-sortプロパティを指定すると、sort-bysort-descで指定したソートを無効にします。

フィルター

searchプロパティに検索文字列を指定すると、検索文字列によりフィルタリングされた結果が表示されます。デフォルトのフィルタリングの動作は検索文字列の部分一致になります。

代替テキスト

searchプロパティで指定した検索文字列の検索結果が空だった場合、代わりに表示するメッセージテキストをno-results-textプロパティで指定します。
デフォルト値は'$vuetify.dataIterator.noResultsText'というメッセージリソースキーです。

デフォルトのフィルター

defaultのフィルタリングの実装はvuetify/src/util/helpers.ts#L338searchItems関数と、

export function searchItems<T extends any = any> (items: T[], search: string): T[] {

  // 省略

}

vuetify/src/util/helpers.ts#L331defaultFilter関数です。

export function defaultFilter (value: any, search: string | null, item: any) {

  // 省略

}

カスタムフィルター

custom-filterプロパティに独自のフィルタリングを行うファンクションを指定すると、このファンクションによるフィルタリング結果が画面に表示されます。

customFilter(items, search) {
  const filteredItems = []

  // 独自のフィルタリング処理

  return filteredItems
}

フィルターの無効化

disable-filteringプロパティを指定すると、searchプロパティで指定した検索文字列を無効にします。

グルーピング

表示するデータのグルーピングはv-data-iteratorコンポーネントのgroup-bygroup-descプロパティで指定します。それぞれのプロパティは修飾子.syncを付けることができます。
これらのプロパティには配列を指定することができますが、その配列に指定できるプロパティは1つだけです。つまり単一項目でのグルーピングしかできません(2020年6月現在)。

グルーピングするアイテムが下記のようなオブジェクトの場合

{
  title: 'Prescription: Murder',
  original_air_date: 'February 20, 1968',
  runtime: 98,
  guest_staring: 'Gene Barry',
  guest_staring_role: 'Dr. Ray Fleming (Gene Barry), a psychiatrist',
  directed_by: 'Richard Irving',
  written_by: ['Richard Levinson & William Link'],
  teleplay: [''],
  season: 0,
  no_in_season: 1,
  no_in_series: 1,
  japanese_title: '殺人処方箋',
  japanese_air_date: '1972-08-27T00:00:00+09:00'
}

このオブジェクトのseasonでグルーピングするには、group-byプロパティにオブジェクトのプロパティ名seasonを、グルーピングしたプロパティで降順にソートするにはgroup-descプロパティにtrueを指定します。

<v-data-iterator :items="items" group-by="season" :group-desc="true">

グルーピングされたデータはスロットプロパティのgroupedItemsでアクセスできます。groupedItemsには下記の構造の配列がセットされます。
nameがグルーピングしたプロパティの値、itemsが各グループのアイテムの配列です。

[
  {
    name: 0, 
    items: [ {...省略...}, {...省略...}, ...省略... ]
  },
  {
    name: 1, 
    items: [ {...省略...}, {...省略...}, ...省略... ]
  },
  {
    name: 2, 
    items: [ {...省略...}, {...省略...}, ...省略... ]
  }
]

下記はv-listコンポーネントを使ってグルーピングされたデータを表示するサンプルです。

<v-data-iterator :items="items" group-by="season" :group-desc="true">
  <template v-slot:default="props">

    <v-list three-line>
      <v-list-group v-for="group in props.groupedItems" :key="group.name">
        <template v-slot:activator>
          <v-list-item-content>
            <v-list-item-title>シーズン: {{ group.name }}</v-list-item-title>
          </v-list-item-content>
        </template>
        <v-list-item v-for="item in group.items" :key="item.title">
          <v-list-item-content>
            <v-list-item-title>
              <div class="d-flex flex-row align-stretch">
                <div class="pl-2 pt-5">
                  <v-chip outlined label color="brown darken-4">{{ item.no_in_series }}</v-chip>
                </div>
                <div>
                  <v-card-title>{{ item.title }}</v-card-title>
                  <v-card-subtitle>{{ item.japanese_title }}</v-card-subtitle>
                </div>
              </div>
            </v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list-group>
    </v-list>

  </template>
</v-data-iterator>

Screenshot_7.png

クリックすると展開しますが、これはv-listコンポーネントの機能です。
Screenshot_8.png

デフォルトのグルーピング

デフォルトのグルーピングの実装はvuetify/src/util/helpers.ts#L260groupItems関数です。

export function groupItems<T extends any = any> (
  items: T[],
  groupBy: string[],
  groupDesc: boolean[]
): ItemGroup<T>[] {

  // 省略

}

カスタムグルーピング

custom-groupプロパティに独自のグルーピングを行うファンクションを指定すると、このファンクションによるグルーピング結果が画面に表示されます。

customGroup(items, groupBy, groupDesc) {
  const groupedItems = []

  // 独自のグルーピング処理

  return groupedItems
}

複数のプロパティをまとめて指定する

optionsプロパティで表示するページ、ソート、グルーピングなどのプロパティをまとめて指定することができます。
optionsプロパティも修飾子.syncを付けることができます。

たとえば下記のようなコードを

<template>
  <v-container>

    <v-data-iterator
     :items="items"
     :page="page"
     :items-per-page="itemsPerPage"
     :sort-by="sortBy"
     :sort-desc="sortDesc"
     :group-by="groupBy"
     :group-desc="groupDesc"
     :multi-sort="multiSort"
     :must-sort="mustSort"
    >
      // 省略
    <v-data-iterator>

  </v-container>
</template>

<script>
const items = [
  ...省略...
]

export default {
  data() {
    return {
      items: items,
      page: 1,
      itemsPerPage: 5,
      sortBy: 'modelNo',
      sortDesc: true,
      groupBy: null,
      groupDesc: false,
      multiSort: false,
      mustSort: false
    }
  }
}
</script>

optionsを使うと下記のようになります。

<template>
  <v-container>

    <v-data-iterator
     :options="options"
    >
      // 省略
    <v-data-iterator>

  </v-container>
</template>

<script>
const items = [
  ...省略...
]

export default {
  data() {
    return {
      items: items,
      options: {
        page: 1,
        itemsPerPage: 5,
        sortBy: ['modelNo'],
        sortDesc: [true],
        groupBy: [],
        groupDesc: [],
        multiSort: false,
        mustSort: false
      }
    }
  }
}
</script>

ヘッダー/フッターのカスタマイズ

v-data-iteratorコンポーネントのheaderfooterスロットでカスタマイズしたヘッダーとフッターを表示することができます。
デフォルトのフッター(ページネーション)を表示したくない場合はhide-default-footerプロパティを指定します。

<v-data-iterator :items="items">

  <template v-slot:default="props">
    // データ表示
  </template>

  <template v-slot:header>
    // ヘッダー表示
  </template>

  <template v-slot:footer>
    // フッター表示
  </template>

<v-data-iterator>

headerfooterスロットではデフォルトスロットとほぼ同じオブジェクトをスロットプロパティで受け取ります。

<template v-slot:footer="props">
  <div>
    <p>アイテム:{{ props.items.length }}</p>
    <p>現在のページ:{{ props.pagination.page }}</p>
    <p>最大ページ:{{ props.pagination.pageCount }}</p>
  </div>
</template>

下記はスロットプロパティを分割代入する書き方です。

<template v-slot:footer="{ items, pagination }">
  <div>
    <p>アイテム:{{ items.length }}</p>
    <p>現在のページ:{{ pagination.page }}</p>
    <p>最大ページ:{{ pagination.pageCount }}</p>
  </div>
</template>

代替テキストのカスタマイズ

表示するデータが無い、検索結果が無い、ローディング中など、表示するデータが無いときに表示される代替テキストを、下記のloadingno-datano-resultsスロットでカスタマイズすることができます。

<template v-slot:loading>
  // ローディング中の代替コンテンツ
</template>

<template v-slot:no-data>
  // 表示するデータが無かったときの代替コンテンツ
</template>

<template v-slot:no-results>
  // 検索結果が無かったときの代替コンテンツ
</template>

下記は代替コンテンツにv-alertやv-progress-linearコンポーネントを使う例です。
loadingスロットの代替コンテンツはloadingプロパティがtrueのときに表示されます。

<template v-slot:no-data>
  <v-alert class="ma-1" icon="mdi-vuetify" tile outlined>
    No data available
  </v-alert>
</template>

<template v-slot:loading>
  <v-alert class="ma-1" icon="mdi-vuetify" tile outlined>
    Loading items...
    <v-progress-linear color="blue accent-4" indeterminate height="5"></v-progress-linear>
  </v-alert>
</template>

Sscreenshot_9.gif

ページネーションのカスタマイズ

デフォルトフッターに表示されるページネーションもプロパティでカスタマイズできます。
図はデフォルトの状態です。
Screenshot_2.png

<template>
  <v-container>
    <v-data-iterator :items="items" :footer-props="footerProps">

    // 省略

    </v-data-iterator>
  </v-container>
</template>

<script>
const items = [ ...省略... ]

const footerProps = {
  'items-per-page-options': [3, 5, 7, 9, -1],
  'show-current-page': true,
  'show-first-last-page': true,
  'prev-icon': 'mdi-chevron-left',
  'next-icon': 'mdi-chevron-right',
  'first-icon': 'mdi-chevron-double-left',
  'last-icon': 'mdi-chevron-double-right',
  'items-per-page-all-text': 'すべて'
}

export default {
  name: 'IteratorSearch',
  data() {
    return {
      items: items,
      footerProps: footerProps
    }
  }
}
</script>

図は上記のカスタマイズをしたページネーションです。
Screenshot_10.png

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away