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

**参考**

* [Quick start -Vuetify.js] (https://vuetifyjs.com/en/getting-started/quick-start/)
* [Components] (https://vuetifyjs.com/en/components/api-explorer/)

# [Data iterators] (https://vuetifyjs.com/en/components/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] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/components/VDataIterator/VDataIterator.ts)
* [VData] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/components/VData/VData.ts)
* [Themeable] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/mixins/themeable/index.ts)

```
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**

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

**3) custom-group**

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

**4) custom-filter**

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

**I18N**

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

* [vuetify/src/locale/en.ts] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/locale/en.ts)
* [vuetify/src/locale/ja.ts] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/locale/ja.ts)

この記事ではデフォルトロケールが英語でしたが、ロケールを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

* [VDataFooter] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/components/VDataIterator/VDataFooter.ts)

|               |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`の実装例です。

```Vue
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/8e3cfb3f-43bf-9018-67f3-e06ce2cadc4e.png)

#### `items`プロパティと`internalCurrentItems`

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

例えば1ページ当たりの表示件数を5件とした場合、`internalCurrentItems`の要素数は5になります。
![Screenshot_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/0410b61d-b5aa-293a-2355-6c0e4d206660.png)

#### デフォルトスロット

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

```vue
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/764fe186-dff4-52ec-263e-cd0b20a6fdcd.png)


#### v-data-footer

Vue Devtoolsで確認すると、v-data-footerコンポーネントが自動的に組み込まれていることがわかります。このv-data-footerコンポーネントのプロパティは、v-data-iteratorコンポーネントの`footer-props`プロパティを介して指定できます。
![Screenshot_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/e750908f-1905-5052-a6e5-c850059e481c.png)

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

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

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

```vue
<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`を付けることができます。

```vue
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/c30079bb-aed8-4447-6d68-db94559d0acd.png)
（※見た目上はわかりませんが、操作しても表示されるデータには反映されません）

**v-data-iterator**

無効にすると`items-per-page`プロパティの値のデータ件数が表示されます。また1ページ当たりの表示件数を選択するリストは有効で表示するデータ件数を切り替えることができます。
ただし改ページボタンは非活性として表示されていてクリックできません。
![Screenshot_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/43446d4b-250c-aec3-9171-1e05fffb5020.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/1c649fe6-8f8c-79bd-aba6-a89edc238673.png)

### ソート

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

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

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

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

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

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

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

#### デフォルトのソート

デフォルトのソート実装は[vuetify/src/util/helpers.ts#L285] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/util/helpers.ts#L285)の`sortItems`関数です。（関数のシグネチャだけ記載します。）

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

  // 省略

}
```

#### カスタムソート

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

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

  // 独自のソート処理

  return sortedItems
}
```

#### ソートの無効化

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

### フィルター

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

#### 代替テキスト

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

#### デフォルトのフィルター

defaultのフィルタリングの実装は[vuetify/src/util/helpers.ts#L338] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/util/helpers.ts#L338)の`searchItems`関数と、

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

  // 省略

}
```

[vuetify/src/util/helpers.ts#L331] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/util/helpers.ts#L331)の`defaultFilter`関数です。

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

  // 省略

}
```

#### カスタムフィルター

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

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

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

  return filteredItems
}
```

#### フィルターの無効化

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

### グルーピング

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

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

```javascript
{
  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を指定します。

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

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

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

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

```vue
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/ac3dadac-b299-2459-2680-1cca1c322aea.png)

クリックすると展開しますが、これはv-listコンポーネントの機能です。
![Screenshot_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/566c544f-e84e-c2ba-4415-4a2fd565eaa1.png)

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

デフォルトのグルーピングの実装は[vuetify/src/util/helpers.ts#L260] (https://github.com/vuetifyjs/vuetify/blob/v2.3.1/packages/vuetify/src/util/helpers.ts#L260)の`groupItems`関数です。

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

  // 省略

}
```

#### カスタムグルーピング

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

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

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

  return groupedItems
}
```

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

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

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

```vue
<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`を使うと下記のようになります。

```vue
<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コンポーネントの`header`、`footer`スロットでカスタマイズしたヘッダーとフッターを表示することができます。
デフォルトのフッター（ページネーション）を表示したくない場合は`hide-default-footer`プロパティを指定します。

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

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

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

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

<v-data-iterator>
```

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

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

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

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

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

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

```vue
<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のときに表示されます。

```vue
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/0d4f05ba-6cc4-155a-5acc-fc87b715f977.gif)

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

デフォルトフッターに表示されるページネーションもプロパティでカスタマイズできます。
図はデフォルトの状態です。
![Screenshot_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/644a1edf-cf8a-2bf5-1aed-1248ba91321d.png)

```vue
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/a571f47f-a7e4-29fd-8b4e-954361f7809f.png)