データウィジェット
==================
Yii はデータを表示するために使うことが出来る一連の [ウィジェット](structure-widgets.md) を提供しています。
[DetailView](#detail-view) は、単一のレコードのデータを表示するのに使うことが出来ます。
それに対して、[ListView](#list-view) と [GridView](#grid-view) は、複数のデータレコードをリストまたはテーブルで表示することが出来るもので、ページネーション、並べ替え、フィルタリングなどの機能を提供するものです。
DetailView
----------
DetailView は単一のデータ [[yii\widgets\DetailView::$model|モデル]] の詳細を表示します。
モデルを標準的な書式で表示する場合 (例えば、全てのモデル属性をそれぞれテーブルの一行として表示する場合) に最も適しています。
モデルは [[\yii\base\Model]] またはそのサブクラス、例えば [アクティブレコード](db-active-record.md) のインスタンスか、連想配列かのどちらかにすることが出来ます。
DetailView は [[yii\widgets\DetailView::$attributes]] プロパティを使って、モデルのどの属性が表示されるべきか、また、どういうフォーマットで表示されるべきかを決定します。
利用できるフォーマットのオプションについては、[フォーマッタの節](output-formatting.md) を参照してください。
次に DetailView の典型的な用例を示します。
```php
echo DetailView::widget([
'model' => $model,
'attributes' => [
'title', // title 属性 (平文テキストで)
'description:html', // description 属性は HTML としてフォーマットされる
[ // モデルの所有者の名前
'label' => '所有者',
'value' => $model->owner->name,
'contentOptions' => ['class' => 'bg-red'], // 値のタグをカスタマイズする HTML 属性
'captionOptions' => ['tooltip' => 'Tooltip'], // ラベルのタグをカスタマイズする HTML 属性
],
'created_at:datetime', // 作成日時は datetime としてフォーマットされる
],
]);
```
[[yii\widgets\GridView|GridView]] が一組のモデルを処理するのとは異なって、
[[yii\widgets\DetailView|DetailView]] は一つのモデルしか処理しないということを覚えておいてください。
表示すべきモデルはビューの変数としてアクセスできる `$model` 一つだけですから、たいていの場合、クロージャを使用する必要はありません。
しかし、クロージャが役に立つ場合もあります。例えば、`visible` が指定されており、それが `false` と評価される場合には
`value` の計算を避けたい場合です。
```php
echo DetailView::widget([
'model' => $model,
'attributes' => [
[
'attribute' => 'owner',
'value' => function ($model) {
return $model->owner->name;
},
'visible' => \Yii::$app->user->can('posts.owner.view'),
],
],
]);
```
ListView
--------
[[yii\widgets\ListView|ListView]] ウィジェットは、[データプロバイダ](output-data-providers.md) からのデータを表示するのに使用されます。
各データモデルは指定された [[yii\widgets\ListView::$itemView|ビューファイル]] を使って表示されます。
ListView は、特に何もしなくても、ページネーション、並べ替え、フィルタリングなどの機能を提供してくれますので、エンドユーザに情報を表示するためにも、データ管理 UI を作成するためにも、非常に便利なウィジェットです。
典型的な使用方法は以下の通りです。
```php
use yii\widgets\ListView;
use yii\data\ActiveDataProvider;
$dataProvider = new ActiveDataProvider([
'query' => Post::find(),
'pagination' => [
'pageSize' => 20,
],
]);
echo ListView::widget([
'dataProvider' => $dataProvider,
'itemView' => '_post',
]);
```
`_post` ビューは次のような内容を含むことが出来ます。
```php
['index'],
'method' => 'get',
]); ?>
= $form->field($model, 'title') ?>
= $form->field($model, 'creation_date') ?>
= Html::submitButton('Search', ['class' => 'btn btn-primary']) ?>
= Html::submitButton('Reset', ['class' => 'btn btn-default']) ?>
```
そして、これを以下のように `index.php` ビューにインクルードします。
```php
= $this->render('_search', ['model' => $searchModel]) ?>
```
> Note: Gii を使って CRUD コードを生成する場合、デフォルトで、独立したフィルタ・フォーム (`_search.php`) が生成されます。
ただし、`index.php` ビューの中ではコメントアウトされています。
コメントを外せば、すぐに使うことが出来ます。
独立したフィルタ・フォームは、グリッドビューに表示されないフィールドによってフィルタをかけたり、
または日付の範囲のような特殊なフィルタ条件を使う必要があったりする場合に便利です。
日付の範囲によってフィルタする場合は、DB には存在しない `createdFrom` と `createdTo` という属性を検索用のモデルに追加すること良いでしょう。
```php
class PostSearch extends Post
{
/**
* @var string
*/
public $createdFrom;
/**
* @var string
*/
public $createdTo;
}
```
そして、`search()` メソッドでクエリの条件を次のように拡張します。
```php
$query->andFilterWhere(['>=', 'creation_date', $this->createdFrom])
->andFilterWhere(['<=', 'creation_date', $this->createdTo]);
```
そして、フィルタ・フォームに、日付の範囲を示すフィールドを追加します。
```php
= $form->field($model, 'creationFrom') ?>
= $form->field($model, 'creationTo') ?>
```
### モデルのリレーションを扱う
GridView でアクティブレコードを表示するときに、リレーションのカラムの値、例えば、単に投稿者の `id` というのではなく、投稿者の名前を表示するという場合に遭遇するかも知れません。
`Post` モデルが `author` という名前のリレーションを持っていて、その投稿者のモデルが `name` という属性を持っているなら、[[yii\grid\GridView::$columns]] の属性名を `author.name` と定義します。
そうすれば、GridView が投稿者の名前を表示するようになります。
ただし、並べ替えとフィルタリングは、デフォルトでは有効になりません。
これらの機能を追加するためには、前の項で導入した `PostSearch` モデルを修正しなければなりません。
リレーションのカラムによる並べ替えを有効にするためには、リレーションのテーブルを結合し、データプロバイダの Sort コンポーネントに並べ替えの規則を追加します。
```php
$query = Post::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
// リレーション `author` を結合します。これはテーブル `users` に対するリレーションであり、
// テーブルエイリアスを `author` とします。
$query->joinWith(['author' => function($query) { $query->from(['author' => 'users']); }]);
// バージョン 2.0.7 以降では、上の行は $query->joinWith('author AS author'); として単純化することが出来ます。
// リレーションのカラムによる並べ替えを有効にします。
$dataProvider->sort->attributes['author.name'] = [
'asc' => ['author.name' => SORT_ASC],
'desc' => ['author.name' => SORT_DESC],
];
// ...
```
フィルタリングも上記と同じ joinWith の呼び出しを必要とします。また、次のように、`attributes` と `rules` の中で、検索可能なカラムを追加で定義する必要があります。
```php
public function attributes()
{
// 検索可能な属性にリレーションのフィールドを追加する
return array_merge(parent::attributes(), ['author.name']);
}
public function rules()
{
return [
[['id'], 'integer'],
[['title', 'creation_date', 'author.name'], 'safe'],
];
}
```
`search()` メソッドでは、次のように、もう一つのフィルタ条件を追加するだけです。
```php
$query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name')]);
```
> Info: 上の例では、リレーション名とテーブルエイリアスに同じ文字列を使用しています。
> しかし、エイリアスとリレーション名が異なる場合は、どこでエイリアスを使い、どこでリレーション名を使うかに注意を払わなければなりません。
> これに関する簡単な規則は、データベースクエリを構築するために使われる全ての場所でエイリアスを使い、`attributes()` や `rules()` など、その他の全ての定義においてリレーション名を使う、というものです。
>
> 例えば、投稿者のリレーションテーブルに `au` というエイリアスを使う場合は、joinWith の文は以下のようになります。
>
> ```php
> $query->joinWith(['author au']);
> ```
>
> リレーションの定義においてエイリアスが定義されている場合は、単に `$query->joinWith(['author']);` として呼び出すことも可能です。
>
> フィルタ条件においてはエイリアスが使われなければなりませんが、属性の名前はリレーション名のままで変りません。
>
> ```php
> $query->andFilterWhere(['LIKE', 'au.name', $this->getAttribute('author.name')]);
> ```
>
> 並べ替えの定義についても同じことです。
>
> ```php
> $dataProvider->sort->attributes['author.name'] = [
> 'asc' => ['au.name' => SORT_ASC],
> 'desc' => ['au.name' => SORT_DESC],
> ];
> ```
>
> さらに、並べ替えの [[yii\data\Sort::defaultOrder|defaultOrder]] を指定するときも、エイリアスではなくリレーション名を使う必要があります。
>
> ```php
> $dataProvider->sort->defaultOrder = ['author.name' => SORT_ASC];
> ```
> Info: `joinWith` およびバックグラウンドで実行されるクエリの詳細については、[アクティブレコード - リレーションを使ってテーブルを結合する](db-active-record.md#joining-with-relations) を参照してください。
#### SQL ビューを使って、データのフィルタリング・並べ替え・表示をする
もう一つ別に、もっと高速で便利な手法があります。SQL ビューです。
例えば、ユーザとユーザのプロファイルを一緒にグリッドビューに表示する必要がある場合、次のような SQL ビューを作成することが出来ます。
```sql
CREATE OR REPLACE VIEW vw_user_info AS
SELECT user.*, user_profile.lastname, user_profile.firstname
FROM user, user_profile
WHERE user.id = user_profile.user_id
```
そして、このビューを表す ActiveRecord を作成します。
```php
namespace app\models\views\grid;
use yii\db\ActiveRecord;
class UserView extends ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'vw_user_info';
}
public static function primaryKey()
{
return ['id'];
}
/**
* @inheritdoc
*/
public function rules()
{
return [
// ここで規則を定義
];
}
/**
* @inheritdoc
*/
public static function attributeLabels()
{
return [
// ここで属性のラベルを定義
];
}
}
```
このようにした後は、この UserView アクティブレコードを検索用のモデルとともに使うことが出来ます。
並べ替えやフィルタリングの属性を追加で定義する必要はありません。
全ての属性がそのままで動作します。
この手法にはいくつかの長所と短所があることに注意してください。
- 並べ替えとフィルタリングの条件をいろいろと定義する必要はありません。全てそのままで動きます。
- データサイズが小さく、実行される SQL クエリの数が少ない (通常なら全てのリレーションについて一つずつ必要になる追加のクエリが要らない) ため、非常に高速になり得ます。
- これは SQL ビューにかぶせた単純な UI に過ぎないもので、エンティティに含まれるドメインロジックを欠いています。
従って、`isActive` や `isDeleted` などのような UI に影響するメソッドがある場合は、それらをこのクラスの中に複製する必要があります。
### 一つのページに複数のグリッドビュー
一つのページで二つ以上のグリッドビューを使うことが出来ますが、お互いが干渉しないように、追加の構成がいくつか必要になります。
グリッドビューの複数のインスタンスを使う場合は、並べ替えとページネーションのリンクが違うパラメータ名を持って生成されるように構成して、それぞれのグリッドビューが独立した並べ替えとページネーションを持つことが出来るようにしなければなりません。
そのためには、データプロバイダの [[yii\data\BaseDataProvider::$sort|sort]] と [[yii\data\BaseDataProvider::$pagination|pagination]] インスタンスの [[yii\data\Sort::sortParam|sortParam]] と [[yii\data\Pagination::pageParam|pageParam]] を設定します。
`Post` と `User` のリストを表示するために、二つのプロバイダ、`$userProvider` と `$postProvider` を準備済みであると仮定します。
```php
use yii\grid\GridView;
$userProvider->pagination->pageParam = 'user-page';
$userProvider->sort->sortParam = 'user-sort';
$postProvider->pagination->pageParam = 'post-page';
$postProvider->sort->sortParam = 'post-sort';
echo '