在人员对话框列出参演影片

为了显示人员参演的影片,我们将在 PersonDialog 添加一个选项卡。

默认情况下,所有编辑对话框(都继承自 EntityDialog)在 MovieTutorial.Web/Views/Templates/EntityDialog.Template.html 使用 EntityDialog 模板。

  1. <div class="s-DialogContent">
  2. <div id="~_Toolbar" class="s-DialogToolbar">
  3. </div>
  4. <div class="s-Form">
  5. <form id="~_Form" action="">
  6. <div class="fieldset ui-widget ui-widget-content ui-corner-all">
  7. <div id="~_PropertyGrid"></div>
  8. <div class="clear"></div>
  9. </div>
  10. </form>
  11. </div>
  12. </div>

该模板包含占位符,如 工具栏(~_Toolbar)表单(~_Form)网格属性(~_PropertyGrid)*。

~_ 是一个在运行时被替换为唯一对话框 ID 的特殊前缀。这确保了同一个对话框的两个实例不会有同样的 ID 值。

所有模板共享 EntityDialog 模板,因此我们把它原样添加到 PersonDialog 选项卡。

为 PersonDialog 添加选项卡模板定义

Modules/MovieDB/Person/ 文件夹下创建 MovieDB.PersonDialog.Template.html,其内容为:

  1. <div id="~_Tabs" class="s-DialogContent">
  2. <ul>
  3. <li><a href="#~_TabInfo"><span>Person</span></a></li>
  4. <li><a href="#~_TabMovies"><span>Movies</span></a></li>
  5. </ul>
  6. <div id="~_TabInfo" class="tab-pane s-TabInfo">
  7. <div id="~_Toolbar" class="s-DialogToolbar">
  8. </div>
  9. <div class="s-Form">
  10. <form id="~_Form" action="">
  11. <div class="fieldset ui-widget ui-widget-content ui-corner-all">
  12. <div id="~_PropertyGrid"></div>
  13. <div class="clear"></div>
  14. </div>
  15. </form>
  16. </div>
  17. </div>
  18. <div id="~_TabMovies" class="tab-pane s-TabMovies">
  19. <div id="~_MoviesGrid">
  20. </div>
  21. </div>
  22. </div>

我们这里使用的语法是特定于 jQuery UI 选项卡组件,它需要一个包含指向选项卡面板 div(.tab-pane) 的 UL 列表。

EntityDialog 在模板中找到 ID 含有 ~_Tabs 的 div,它将自动在模板中把其初始化为选项卡组件。

模板文件的命名是很重要的,它必须以 .Template.html 扩展名结尾。所有以该扩展名结束的文件都是通过动态脚本提供给客户端。

模板文件的文件夹被忽略,但是模板必须在 Modules 或者 Views/Template 文件夹下面。

默认情况下,所有的模板组件(EntityDialog 也继承自 TemplatedWidget 类)使用类名查找模板。因此,PersonDialog 首先查找名为 MovieDB.PersonDialog.Template.html 的模板,然后是 PersonDialog.Template.html

MovieDB 来自于把 PersonDialog 命名空间删除根命名空间(MovieTutorial)。你也可以认为是模块名与类名的组合。

如果没有找到含该类名的模板,将继续到基类查找并最终找到一个备用模板,使用 EntityDialog.Template.html

现在,我们将在 PersonDialog 中有一个选项卡:

Person With Tabs Initial

与此同时,我注意到 Person 菜单还在 MovieDB 下面,并且我们忘了删除 MovieCast 菜单,我现在就修正……

创建 PersonMovieGrid

Movie 选项卡现在还是空的。我们需要在该选项卡里定义一个有合适列的网格列表。

首先,在 PersonColumns.cs 旁边的 PersonMovieColumns.cs 文件中,定义需要在网格列表使用的的列:

  1. namespace MovieTutorial.MovieDB.Columns
  2. {
  3. using Serenity.ComponentModel;
  4. using System;
  5. [ColumnsScript("MovieDB.PersonMovie")]
  6. [BasedOnRow(typeof(Entities.MovieCastRow))]
  7. public class PersonMovieColumns
  8. {
  9. [Width(220)]
  10. public String MovieTitle { get; set; }
  11. [Width(100)]
  12. public Int32 MovieYear { get; set; }
  13. [Width(200)]
  14. public String Character { get; set; }
  15. }
  16. }

然后,在 PersonGrid.ts 旁边的 PersonMovieGrid.ts 文件中定义一个 PersonMovieGrid 类:

  1. namespace MovieTutorial.MovieDB {
  2. @Serenity.Decorators.registerClass()
  3. export class PersonMovieGrid extends Serenity.EntityGrid<MovieCastRow, any>
  4. {
  5. protected getColumnsKey() { return "MovieDB.PersonMovie"; }
  6. protected getIdProperty() { return MovieCastRow.idProperty; }
  7. protected getLocalTextPrefix() { return MovieCastRow.localTextPrefix; }
  8. protected getService() { return MovieCastService.baseUrl; }
  9. constructor(container: JQuery) {
  10. super(container);
  11. }
  12. }
  13. }

实际上,我们使用 MovieCast 服务查找人员所参演的影片列表。

最后的步骤是在 PersonDialog.ts 实例化该网格列表:

  1. @Serenity.Decorators.registerClass()
  2. @Serenity.Decorators.responsive()
  3. export class PersonDialog extends Serenity.EntityDialog<PersonRow, any> {
  4. protected getFormKey() { return PersonForm.formKey; }
  5. protected getIdProperty() { return PersonRow.idProperty; }
  6. protected getLocalTextPrefix() { return PersonRow.localTextPrefix; }
  7. protected getNameProperty() { return PersonRow.nameProperty; }
  8. protected getService() { return PersonService.baseUrl; }
  9. protected form = new PersonForm(this.idPrefix);
  10. private moviesGrid: PersonMovieGrid;
  11. constructor() {
  12. super();
  13. this.moviesGrid = new PersonMovieGrid(this.byId("MoviesGrid"));
  14. this.tabs.on('tabsactivate', (e, i) => {
  15. this.arrange();
  16. });
  17. }
  18. }

记得我们的模板在 movies 选项卡下有一个 id 为 ~_MoviesGrid 的 div,我们在该 div 中创建了 PersonMovie 网格列表。

this.ById(“MoviesGrid”) 是一个特殊的模板组件方法。$(‘#MoviesGrid’) 在这里不能工作,因为实际上 div 有一些像 PersonDialog17_MoviesGrid 的 ID。~_ 在模板中被替换为容器组件的唯一 ID。

我们还为 jQuery UI 选项卡附加 OnActivate 事件,并调用对话框的 Arrange 方法。这是解决 SlickGrid 初始创建在不可见的选项卡的问题,通过 Arrange 触发 SlickGrid 的布局来解决该问题。

OK, 现在我们可以在 Movies 选项卡中看到影片列表,但是有些奇怪:

Person With Movies Unfiltered

筛选出人员所参演的影片

不,Carrie-Anne Moss 并没有扮演以上三个角色。由于我们还没有告诉该应用什么过滤器,现在这个网格列表显示所有的影片演员记录。

PersonMovieGrid 应该知道它该显示哪个人员的电影记录。因此我们添加一个 PersonID 属性到该网格列表。该 PersonID 应该通过某种方式传递给列表服务进行过滤。

  1. namespace MovieTutorial.MovieDB
  2. {
  3. @Serenity.Decorators.registerClass()
  4. export class PersonMovieGrid extends Serenity.EntityGrid<MovieCastRow, any>
  5. {
  6. protected getColumnsKey() { return "MovieDB.PersonMovie"; }
  7. protected getIdProperty() { return MovieCastRow.idProperty; }
  8. protected getLocalTextPrefix() { return MovieCastRow.localTextPrefix; }
  9. protected getService() { return MovieCastService.baseUrl; }
  10. constructor(container: JQuery) {
  11. super(container);
  12. }
  13. protected getButtons() {
  14. return null;
  15. }
  16. protected getInitialTitle() {
  17. return null;
  18. }
  19. protected usePager() {
  20. return false;
  21. }
  22. protected getGridCanLoad() {
  23. return this.personID != null;
  24. }
  25. private _personID: number;
  26. get personID() {
  27. return this._personID;
  28. }
  29. set personID(value: number) {
  30. if (this._personID != value) {
  31. this._personID = value;
  32. this.setEquality(MovieCastRow.Fields.PersonId, value);
  33. this.refresh();
  34. }
  35. }
  36. }
  37. }

我们使用 ES5 (EcmaScript 5) 的属性(get/set) 特性。它与 C# 的属性非常相似。

我们把人员 ID 存储在一个私有变量中。当它改变时,我们还使用 SetEquality 方法(将被发送到列表服务)为 PersonId 字段设置一个相等过滤器,并刷新获取更新。

相等过滤器是使用快速过滤器项目的列表请求参数。

当网格列表调用列表服务时,重写 GetGridCanLoad 方法允许我们做一些控制。如果我们没有重写它,当创建一个新的人员时,由于还没有 PersonID(它为 null),网格列表将加载所有的演员记录。

如果相等过滤器的参数为 null,List handler 将忽略相等过滤器的参数,就像快速过滤器下拉列表为空时,所有的记录都被显示出来。

通过重写三个方法,我们还做了三个改变:一、从工具栏(getButtons)中删除所有按钮;二、从网格列表(getInitialTitle)删除作为选项卡的标题,三、删除分页功能(usePager),一个人不可能参演一百万部电影吧?。

在 PersonDialog 设置 PersonMovieGrid 的 PersonID

如果没有设置网格列表的 PersonID 属性,它将总是为 null,并且不会加载记录。我们应该在 Person 对话框的 afterLoadEntity 方法中设置它。

  1. namespace MovieTutorial.MovieDB
  2. {
  3. // ...
  4. export class PersonDialog extends Serenity.EntityDialog<PersonRow>
  5. {
  6. // ...
  7. protected afterLoadEntity()
  8. {
  9. super.afterLoadEntity();
  10. moviesGrid.personID = this.entityId;
  11. }
  12. }
  13. }

在实体或新实体被加载到对话框之后调用 afterLoadEntity

请注意,实体是在后一阶段被加载的,因此把它放在对话框的构造函数中是没有用的。

this.EntityId 引用当前加载实体的标识值。在新记录的情况下,它的值为 null。

在对话框生命周期内,AfterLoadEntity 和 LoadEntity 可能被调用多次,因此避免在这些事件中创建一些子对象,否则你会有多个创建对象的实例。这就是为什么我们在对话框构造函数中创建网格列表的原因。

Person With Movies Filtered

调整 Movies 选项卡大小

你可能已经注意到,当你切换到 Movies 选项卡时,对话框高度有点不够。这是由于对话框设置为自动高度,并且网格列表默认高度是 200px。当你切换到 Movies 选项卡,表单被隐藏,因此对话框调整为 Movies 网格列表高度。

在 site.less 编辑 s-MovieDB-PersonDialog css:

  1. .s-MovieDB-PersonDialog {
  2. > .size { width: 650px; }
  3. .caption { width: 150px; }
  4. .s-PersonMovieGrid > .grid-container { height: 287px; }
  5. }