数据小部件
============
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,
],
'created_at:datetime', // creation date formatted as datetime
],
]);
```
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']) ?>
```
and include it in `index.php` view like so:
```php
= $this->render('_search', ['model' => $searchModel]) ?>
```
> 注意:if you use Gii to generate CRUD code, the separate filter form (`_search.php`) is generated by default,
but is commented in `index.php` view. Uncomment it and it's ready to use!
Separate filter form is useful when you need to filter by fields, that are not displayed in GridView
or for special filtering conditions, like date range. For filtering by date range we can add non DB attributes
`createdFrom` and `createdTo` to the search model:
```php
class PostSearch extends Post
{
/**
* @var string
*/
public $createdFrom;
/**
* @var string
*/
public $createdTo;
}
```
Extend query conditions in the `search()` method like so:
```php
$query->andFilterWhere(['>=', 'creation_date', $this->createdFrom])
->andFilterWhere(['<=', 'creation_date', $this->createdTo]);
```
And add the representative fields to the filter form:
```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')]);
```
> 信息:在上面的代码中,我们使用相同的字符串作为关联名称和表别名;
> 然而,当你的表别名和关联名称不相同的时候,你得注意在哪使用你的别名,在哪使用你的关联名称。
> 一个简单的规则是在每个构建数据库查询的地方使用别名,而在所有其他和定义相关的诸如:
>`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];
> ```
> 信息:更多关于 `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 '