数据小部件
============
Yii提供了一套数据小部件 [widgets](structure-widgets.md) ,这些小部件可以用于显示数据。
[DetailView](#detail-view) 小部件能够用于显示一条记录数据,
[ListView](#list-view) 和 [GridView](#grid-view) 小部件能够用于显示一个拥有分页、
排序和过滤功能的一个列表或者表格。
DetailView
----------
[[yii\widgets\DetailView|DetailView]] 小部件显示的是单一 [[yii\widgets\DetailView::$model|model]] 数据的详情。
它非常适合用常规格式显示一个模型(例如在一个表格的一行中显示模型的每个属性)。
这里说的模型可以是 [[\yii\base\Model]] 或者其子类的一个实例,例如子类 [active record](db-active-record.md),也可以是一个关联数组。
DetailView使用 [[yii\widgets\DetailView::$attributes|$attributes]] 属性来决定显示模型哪些属性以及如何格式化。
可用的格式化选项,见 [formatter section](output-formatting.md) 章节。
一个典型的DetailView的使用方法如下:
```php
echo DetailView::widget([
'model' => $model,
'attributes' => [
'title', // title attribute (in plain text)
'description:html', // description attribute formatted as HTML
[ // the owner name of the model
'label' => 'Owner',
'value' => $model->owner->name,
'contentOptions' => ['class' => 'bg-red'], // HTML attributes to customize value tag
'captionOptions' => ['tooltip' => 'Tooltip'], // HTML attributes to customize label tag
],
'created_at:datetime', // creation date formatted as datetime
],
]);
```
请记住,与处理一组模型的 [[yii\widgets\GridView|GridView]] 不同,
[[yii\widgets\DetailView|DetailView]] 只处理一个。
因为 `$model` 是唯一一个用于显示的模型,并且可以作为变量在视图中使用。
但是有些情况下可以使闭包有用。
例如指定了 `visible`,并且你不想让`value` 的结果为 `false`:
```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]] 小部件用于显示数据提供者 [data provider](output-data-providers.md) 提供的数据。
每个数据模型用指定的视图文件 [[yii\widgets\ListView::$itemView|view file]] 来渲染。
因为它提供开箱即用式的(译者注:封装好的)分页、排序以及过滤这样一些特性,
所以它可以很方便地为最终用户显示信息并同时创建数据管理界面。
一个典型的用法如下例所示:
```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` 视图中已经被注释了。取消注释就可以用了!
当您需要按字段过滤时,单独的过滤器表单很有用,这些字段不会在 GridView 中显示,也不适用于特殊筛选条件(如日期范围)。
对于按日期范围过滤,
我们可以将非 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') ?>
```
### 处理关系型模型
当我们在一个网格视图中显示活动数据的时候,你可能会遇到这种情况,就是显示关联表的列的值,
例如:发帖者的名字,而不是显示他的 `id`。当 `Post` 模型有一个关联的属性名(译者注: `Post` 模型中用 `hasOne` 定义 `getAuthor()` 函数)
叫 `author` 并且作者模型(译者注:本例的作者模型是 `users` )有一个属性叫 `name`,
那么你可以通过在 [[yii\grid\GridView::$columns]] 中定义属性名为 `author.name` 来处理。
这时的网格视图能显示作者名了,但是默认是不支持按作者名排序和过滤的。
你需要调整上个章节介绍的 `PostSearch` 模型,以添加此功能。
为了使关联列能够排序,你需要连接关系表,
以及添加排序规则到数据提供者的排序组件中:
```php
$query = Post::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
// 连接与 `users` 表相关联的 `author` 表
// 并将 `users` 表的别名设为 `author`
$query->joinWith(['author' => function($query) { $query->from(['author' => 'users']); }]);
// since version 2.0.7, the above line can be simplified to $query->joinWith('author AS author');
// 使得关联字段可以排序
$dataProvider->sort->attributes['author.name'] = [
'asc' => ['author.name' => SORT_ASC],
'desc' => ['author.name' => SORT_DESC],
];
// ...
```
过滤也需要像上面一样调用joinWith方法。你也需要在属性和规则中定义该列,就像下面这样:
```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` 作为作者关系表的别名,那么联查语句就要写成像下面这样:
>
> ```php
> $query->joinWith(['author' => function($query) { $query->from(['au' => 'users']); }]);
> ```
>
> 当别名已经在关联函数中定义了时,也可以只调用 `$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` 和在后台执行查询的相关信息,
> 可以查看 [active record docs on joining with relations](db-active-record.md#joining-with-relations)。
#### SQL 视图用于过滤、排序和显示数据
还有另外一种方法可以更快、更有用的 SQL 视图。例如,我们要在 `GridView`
中显示用户和他们的简介,可以这样创建 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
```
然后你需要创建活动记录模型来代表这个视图:
```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\Sort::sortParam|sortParam]] 和
[[yii\data\Pagination::pageParam|pageParam]],对应于数据提供者的
[[yii\data\BaseDataProvider::$sort|sort]] 和
[[yii\data\BaseDataProvider::$pagination|pagination]] 实例。
假如我们想要同时显示 `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 '