用于单页应用程序中 CRUD 操作的 SLGrid Knockout 组件






4.40/5 (5投票s)
Knockout 组件 SLGrid。
引言
SLGrid 是一个具有标准网格功能的 Knockout 组件:
- 分页
- 排序
- 过滤
- 内联编辑
- 每个单元格都可以包含另一个 Knockout 组件,例如日期选择器、下拉列表、日历或任何其他可重用组件
- ...
我将介绍 SLGrid 组件在单页应用程序环境中用于 CRUD 操作的用法。
这些网格功能对于拥有许多不同表和关系网站的管理部分非常有用。
实际上,这是为了最终确定对某个实体进行 **C**reate(创建)、**R**ead(读取)、**U**pdate(更新)和 **D**elete(删除)所需的操作集。
背景
SLGrid 是客户端组件网格,而不是 ASP.NET 5.1、MVC 4.2、PHP 7.8 等的网格。
我决定遵循 Steve 的 Sanderson 的示例,使用 'yeoman' 来脚手架一个包含 Knockout、CrossroadsJS 和 RequireJS 的 SPA。CrossroadsJS 实现哈希路由,RequireJS 处理模块加载和依赖项。
我正在遵循的演示是
Steve Sanderson - 使用 Knockout 构建大型单页应用程序的架构
启动 Knockout 项目比预期的要容易。
以下是安装 'Yeoman' 并脚手架项目的命令
npm install -g yo
npm install -g generator-ko
yo ko
这给了我们一个简单的项目结构,包含路由和两个初始视图(主页和关于页面)。
可以手动添加其他视图和组件,但 Yeoman 也提供了一个快捷命令
yo ko:component <name>
此演示中使用的关键技术是
- Knockout 组件,一种将 UI 代码组织到自包含、可重用块中的简洁方式
- Reguirejs,用于异步模块定义 (AMD)
- Knockout Validation
- gulpjs,流式构建系统
- babel,JavaScript ES2015 转译器
- JavaScript 继承模式
- jQuery 用于管理对服务的 Ajax 调用
- Bootstrap 用于 HTML 标记的样式化
试试看
使用代码
对于实体的 CRUD 操作,我们需要定义 3 个 JavaScript 类,实际上是 3 个 AMD 模块。以实体 'People
' 为例,我们定义
Person
,继承 `SLEntity`,定义具有默认值和验证规则的属性。- PersonDB,继承 `SLEntityDB`,数据库适配器,针对不同类型的数据库有所不同,执行与服务器的 ajax 调用
PersonGrid
,使用 `SLGrid` 和 `SLPager`,维护实体列表,执行网格操作,如分页、排序、内联编辑... 它定义了将在表格视图中显示的 Person 实体的属性。它根据列的定义生成行和行编辑模板。
实际上,我们可以使用 'yeoman' 生成这些类,从数据库定义读取并尊重数据库的所有关系,例如:一对多,多对多...
// Person
class Person extends SLEntity {
constructor(data) {
super(data);
this.PersonId = ko.observable(data.PersonId || 0)
this.Name = ko.observable(data.Name || "").extend({ required: true, minLength: 2 })
this.IsOnTwitter = ko.observable(data.IsOnTwitter === undefined ? false : data.IsOnTwitter)
this.TwitterName = ko.observable(data.TwitterName || "")
this.City = new CityAsForeign(data.City || {})
this.NumOfPosts = ko.observable(data.NumOfPosts || 0)
this.LastLogin = ko.observable(data.LastLogin ? new Date(data.LastLogin) : new Date())
}
}
// PersonDB
class PersonDB extends SLEntityDB {
constructor(data) {
super(data);
}
}
PersonDB.prototype.Url = {
"get" : "api/people",
"getById": "api/people/getById",
"add": "api/people/post",
"update": "api/people/put"
}
// PersonGrid
class PersonGrid {
constructor(params) {
var self = this;
this.Person = Person;
this.pageSize = ko.observable(params.pageSize);
this.pager = ko.observable();
this.grid = ko.observable();
this.grid.subscribe(function (grid) {
// now we have the grid component instantiated
})
this.columns = [
{ fieldName: 'PersonId', header: 'Id', width: '50px', align: 'right' },
{ fieldName: 'Name', header: 'Name', width: 'auto' },
{ fieldName: 'IsOnTwitter', header: 'Twitter', width: '70px', align: 'center', presentation: 'bindingHandlerCheckbox' },
{ fieldName: 'City', header: 'City', width: '140px',
markup: "<bs-select params='entityAsForeign:City, isViewMode:isViewMode' />" },
{ fieldName: 'NumOfPosts', header: '#Posts', width: '70px', align: 'center' },
{ fieldName: 'LastLogin', header: 'Last Login', width: '180px', align: 'center',
markup: "<sl-date-picker params='date:LastLogin, isViewMode:isViewMode'/>" },
{ fieldName: '', header: 'Inline', width: '80px', presentation: 'bindingHandlerEditInline', sortable: false, align: 'center' },
{ fieldName: '', header: 'Edit', width: '60px', presentation: 'bindingHandlerEdit', sortable: false, align: 'center' }
];
this.filter = new Person({});
this.filter.City.CityId.subscribe(function (newValue) {
self.grid()
.Filter({ CityId: newValue == 'CAPTION' ? 0 : newValue })
.getItems();
});
this.pageSize.subscribe(function (newValue) {
self.grid().getItems(1 /*page*/, newValue /*pageSize*/);
});
// Add
this.addNew = function () {
location.hash = '#person-add';
}
// Edit
this.editEntity = function (vm, e) {
var personDB = Person.prototype.entityDB;
personDB.getById(vm.PersonId(), function (data) {
window.personData = data;
location.hash = '#person-edit/' + vm.PersonId();
})
}
}
}
基础 JavaScript 类可以完成这项工作
SLEntity 源代码
SLEntityDB 源代码
关于 $(document).ready
在此 SPA 环境中,我们有动态加载页面,按需加载,并且到处都有组件:页面是 Kncokout 组件,网格是 Knockout 组件,下拉列表是 Knockout 组件... 我们可以使用 requirejs/domReady 插件,但由于 Knockout,我们有了更好的解决方案。我们希望在绑定过程中针对组件的元素运行代码,因此自定义绑定是可行的方法。
我们将元素放入模板中
<div data-bind="MyComponentHandler:true">
我们像这样设置 ViewModel
// Binding will be applied when the element is introduced to the document.
ko.bindingHandlers.MyComponentHandler = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
viewModel.myElem = $(element);
// bind any plugin here
viewModel.myElem.selectpicker({ size: 10 });
}
}
class MyComponent {
constructor(params) {
this.myElem = null;
// ...
}
}
子 Knockout 组件如何通信
组件是组织 UI 代码到自包含、可重用块中的强大、简洁的方式。
Knockout 组件通过传递给组件的 'params
' 对象进行通信。
通常这是一个包含多个参数的键值对象,并且通常由组件的 ViewModel 构造函数接收。
这张图展示了Filter (下拉列表)和SLGrid 通信的方式
子 Knockout 组件 SLGrid 和 SLPager 如何连接
模拟器
mockjax 插件是一个开发和测试工具,用于拦截和模拟使用 jQuery 发出的 ajax 请求,对生产代码的更改影响最小。对于这个示例应用程序,服务器实际上是一个模拟服务器,它使我们能够进行实时演示,而无需在任何地方托管实际服务器。
打包和缩小
组件的动态加载。我们将 People
所需的组件放入同一个包中
bundles: {
//...
'people': [ 'components/person-grid/person-grid',
'pages/people/people',
'pages/people/add/person-add',
'pages/people/edit/person-edit']
}
这样,只有当用户点击顶部菜单栏中的 'People
' 时,我们才能动态加载 'people.js'。
SLGrid 的 Yeoman 生成器
计划使用以下工具为 SLGrid 创建一个生成器:
https://yeoman.node.org.cn/authoring/
生成器将读取任何数据库定义
- 表
- 列
- 关系
为某个表(实体)的 CRUD 操作生成所需的页面和组件。
这样,我们就可以为包含许多表的某个管理站点生成完整的起点。
通过精心设计的组件和 JavaScript 类,开发人员将更容易自定义模板和实现附加逻辑。
例如,读取 City
表的定义,我们可以生成 City
类
class City extends SLentity
{
constructor(data) {
super(data);
this.CityId = ko.observable(data.CityId)
.extend({ defaultValue: 102, required: true, minLength: 2 })
this.Name = ko.observable(data.Name)
this.CountryId = ko.observable(data.CountryId).extend({ foreignKey: Country })
}
}
此外,'yeoman' 还生成了 City
的 CRUD 操作所需的其他文件。
- 服务器上的 CityController 用于操作
- getItems
- getItemById
- Add
- Update
- Remove - CityDto - 客户端和服务器之间的数据传输对象
components/
city-grid/
city-grid.js
city-grid.html
models/
city/
city.js
city-db.js
pages/
cities/
add/
city-add.js
city-add.html
edit/
city-edit.js
city-edit.html
cities.js
cities.html