脏数据处理

Grid 的多行数据,修改后要提交到后台。如果大批量的数据一次性提交到后台恐怕不大合理。如果只是修改过那行的数据才提交过去,是比较合理的方式。这些修改的数据,我们称为“脏数据(Dirty data)”。脏数据本质也是一个 map、一个 json。

/*** 某个记录的修改过的部分,其中 id 用于标识,* 其余字段就是修改过的 key & value*/
interface DirtyData extends JsonParam {/*** 修改过记录的 id 标识*/id: string;
}

在 Grid 里实现脏数据机制的要点是:如何知晓哪些是修改过数据,其余的问题就不复杂了。

Vue 提供 watch 监视数据的变化,——这特性很符合我们解决这个问题的要求。我们把有变化的值保存到一个 map 之中,这个 map 又带有 id 所以知道是修改哪一行的记录。如果是 Vue 传统的配置项写法,很难符合我们 Grid 大批量数据的方式,总不能手工逐个登记吧?不过幸好 Vue 提供 $watch() 手动方法登记监视数据。

mounted(): void {for (var i in this.rowData) // 监视每个字段this.$watch('rowData.' + i, makeWatch.call(this, i));
}

makeWatch 是生成监视函数的高阶函数。

/*** 生成该字段的 watch 函数* * @param this * @param field */
function makeWatch(this: GridEditRow, field: string): (_new: any) => void {return function (this: GridEditRow, _new: any) {let arr: GridRecord[] = this.$parent.list,data!: GridRecord;for (let i = 0, j = arr.length; i < j; i++) {// 已知 id 找到原始数据if (this.id && (String(arr[i].id) == this.id)) {data = arr[i];break;}}if (!data)throw '找不到匹配的实体!目标 id: ' + this.id;if (!data.dirty)data.dirty = { id: this.id };data.dirty[field] = _new; // 保存新的值,key 是字段名}
}

当前设计是在原 Grid 记录那一行添加 dirty 的 map 记录脏数据。

/*** 行记录数据*/
interface GridRecord extends BaseObject {/*** 修改过记录*/dirty?: DirtyData;
}

或者是否没必要找到原始数据呢?有待优化~~

剩下的 CRUD 代码就简单了,举个例子。

/*** 保存脏数据*/
onDirtySaveClk(): void {let dirties: GridRecord[] = getDirty.call(this);if (!dirties.length) {msg.show('没有修改过的记录');return;}dirties.forEach((item: GridRecord) => {xhr.put(`${this.apiUrl}/${item.id}/`, (j: RepsonseResult) => {if (j.isOk) {this.list.forEach((item: GridRecord) => { // clearif (item.dirty)delete item.dirty;});msg.show('修改记录成功');}}, item.dirty);});
}

动态组件渲染

笔者学艺未精,在未理解 Vue 动态组件渲染之前,走了一段弯路:避开了原生的方法,去用另外一个动态组件的方法来做,也算是一种别致的方法。这种方法使用过程如下,算是在这里记录一下。

首先定义一个组件:

/*** 动态组件*/
Vue.component('aj-cell-renderer', {props: {html: { type: String, default: '' },form: Object},render(h: any) {if (this.html.indexOf('<aj-') != -1) {let com = Vue.extend({template: this.html,props: {// form: Object}});return h(com, {props: {// form: this.form}});} else {return this._v(this.html); // html}}
});

使用组件:

<aj-cell-renderer v-if="!isEditMode || !cellRenderer.editMode" :html="cellRenderer.renderer(rowData)"></aj-cell-renderer>

实话说笔者也搞不懂这个组件的原理(网上摘抄回来的代码),总之可以输入字符串的组件如 cellRenderer.renderer(rowData) 返回的结果:<aj-avatar /> 便可动态渲染组件了。

这是老的方法,在查阅 Vue 文档的时候记得 Vue 本身就支持动态组件的,不知道当时编码遇到什么状况,居然调不出原生方法,现在重构之后,代码更加清晰,于是试着重新使用原生方法,谁知道一调就通,——笔者也百思不得其解。新方法如下。

<span v-if="cellRenderer && cellRenderer.isComponent"><component v-if="!isEditMode || !cellRenderer.editMode" v-bind:is="cellRenderer.renderer(rowData)"></component><component v-if="isEditMode && cellRenderer.editMode"   v-bind:is="cellRenderer.editRenderer(rowData)"></component>
</span>

关键在于 component 的 v-bind:isv-bind,分别对应组件的名字及其 props,事件也是同样道理,使用 v-on。调用者的配置和例子如下。


/*** 复杂的单元格渲染器*/
interface CellRendererConfig {/*** 传入一个 Vue 的组件去参与渲染*/cmpName: string;/*** 编辑状态下使用何种组件?*/editCmpName?: string;/*** 组件所依赖的 props */cmpProps: (j: JsonParam) => JsonParam;/*** 可否被编辑编辑*/editMode: boolean;key?: CellRendererKey;/*** 数据类型*/type?: String | Number | Boolean;
}let avatar: CellRendererConfig = {cmpName: "aj-avatar",editMode: false,cmpProps(data: JsonParam): JsonParam {let avatar = <string>data.avatar,prefix = '';//'${aj_allConfig.uploadFile.imgPerfix}'avatar = "https://static001.geekbang.org/account/avatar/00/10/10/51/9fedfd70.jpg?x-oss-process=image/resize,w_200,h_200";if (!avatar)return { avatar: "" };if (avatar.indexOf('http') === -1)avatar = prefix + avatar;return { avatar };}
};

回归简单,还是原生 Vue 的 API 方法好。

新建行记录

在 Table 中插入一个新的 <tr> 元素专用于新建记录,这个行也是一个标准 Vue 组件(位于 inline-edit-row-creat.ts),用法如下。

<tr v-show="grid.showAddNew" is="aj-grid-inline-edit-row-create" :columns="['name', 'rate', null]" create-api=".">
</tr>

为了更好地参与到 <table> 元素中,还是使用 <tr> 元素比较妥当,然后通过 Vue 提供的 is 指令即可等价于声明组件。使用该组件要什么哪些字段是可以创建的,在属性 columns 中指定。

这里稍微说说题外话,Vue 一大特色是与标签强烈耦合,话说前端 Web 开发,很多时候都是在和 HTML 打交道,最终产物还是在浏览器渲染出来的 DOM,而不管你使用何种手段,是后台语言 Java 还是 JavaScript、还是 TypeScript……终究还是化身成为 DOM。一个稍有规模的工程,一般都不会只用一种语言来从头写到尾,我们开发者要处理多种语言是家常便饭的事情,最难的就是如何协调好它们。也就是说,用一种语言去控制另外一种语言,是非常常见的,有时它可能还不是一种语言,只算是小型的 DSL(Domain-specific language 领域特定语言)。

类似的场景,好比 SQL,我们一般都不会直接去写,而通过 ORM 等方法去控制 SQL——最终数据库只认 SQL。如今换作 Vue,其目的同样也是,怎么更好地控制 HTML 标签。Vue 给出的各种手段,例如标签化的组件、各种方式的数据绑定、事件绑定、props 属性输入……可以看出 Vue 的立场还是非常尊重标签的,从而鼓励大家使用标签这种声明式的编码方法去构建 UI,某种角度讲,它就是一种“用标签生成标签”的理论。

“用一种语言去控制另外一种语言”,——我们无法回避,也司空见惯——焦点是如何更优雅地去处理、控制、协调,那才是最难的。幸运地是,Vue 向我们给出的答案尚算不赖,甚至让笔者觉得,后端的 SQL/ORM 则没有那么优雅的方案了……

工具条

工具条 Toolbar 如下图所示。它本身提供一些默认的按钮(CRUD)。

另外你也可以通过 Vue 的 slot 机制添加自定义的按钮。

<div class="box"><!-- 菜单工具栏--><aj-entity-toolbar :create="false" :save="false"> <li onclick="window.open('${ctx}/user/register/');"> 用户注册</li><li class="fa fa-user-o" onclick="USER_GROUP.$refs.layer.show();"> 用户组</li><li class="fa fa-user-circle-o" onclick="ASSIGN_RIGHT.$refs.assignRight.show();"> 角色分配</li></aj-entity-toolbar>
</div>

效果如下图所示。

原理

该组件不复杂,源码如下所示(位于 toolbar.ts)。

namespace aj.list.grid {/*** 工具条*/export interface GridToolbar {$el: HTMLElement;$parent: Grid;}/*** 工具条*/Vue.component('aj-entity-toolbar', {template: html`<div class="toolbar"><form v-if="search" class="right"><input type="text" name="keyword" placeholder="请输入关键字" size="12" /><button @click="doSearch"><i class="fa fa-search" style="color:#417BB5;"></i>搜索</button></form><aj-form-between-date v-if="betweenDate" class="right"></aj-form-between-date><ul><li v-if="create" @click="$emit('on-create-btn-clk')"><i class="fa fa-plus" style="color:#0a90f0;"></i> 新建</li><li v-if="save" @click="$emit('on-save-btn-clk')"><i class="fa fa-floppy-o"style="color:rgb(205, 162, 4);"></i>保存</li><li v-if="deleBtn" @click="$emit('on-delete-btn-clk')"><i class="fa fa-trash-o" style="color:red;"></i> 删除</li><li v-if="excel"><i class="fa fa-file-excel-o" style="color:green;"></i> 导出</li><slot></slot></ul></div>`,props: {betweenDate: { type: Boolean, default: true },create: { type: Boolean, default: true },save: { type: Boolean, default: true },excel: { type: Boolean, default: false },deleBtn: { type: Boolean, default: true },search: { type: Boolean, default: true }},methods: {/*** 获取关键字进行搜索* * @param this * @param ev */doSearch(this: GridToolbar, ev: Event): void {ev.preventDefault();aj.apply(this.$parent.$store.extraParam, { keyword: form.utils.getFormFieldValue(this.$el, 'input[name=keyword]') });this.$parent.$store.reload();}}});
}

主要是子组件与父组件之间的事件通讯,采用 $emit() 触发事件,然后在 Grid 组件实例中捕获事件,自定义处理。为什么要自定义处理?因为按钮事件会按照实际情形有不同逻辑,硬是封装一起没必要,不如开放给用户去自定义。当然也可以作一些适当的封装,例如下例的“新建”按钮,点击后触发的事件是 Grid 默认的 onCreateClk() 方法。

<!-- 菜单工具栏-->
<aj-entity-toolbar @on-create-btn-clk="grid.onCreateClk" @on-save-btn-clk="grid.onDirtySaveClk"></aj-entity-toolbar>

这里的 grid 指向 aj-grid 组件实例,而不是最外层的 Vue 实例。值得注意的是它通过 Vue “插槽 slot”暴露出来的:<template v-slot="{grid}">,在 aj-grid 组件创建 slot 时候就如此声明了,注意 v-bind:grid="this" 指向当前 Grid 实例。

template = '<div class="aj-grid"><slot v-bind:grid="this"></slot></div>';

用 TypeScript 写一个轻量级的 UI 框架之十三:Grid 表格组件(下)相关推荐

  1. 如何搭建python框架_从零开始:写一个简单的Python框架

    原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...

  2. 如何写一个Android inline hook框架

    Android_Inline_Hook https://github.com/GToad/Android_Inline_Hook_ARM64 有32和64的实现,但是是分离的,要用的话还要自己把两份代 ...

  3. 为了支持AOP的编程模式,我为.NET Core写了一个轻量级的Interception框架[开源]

    ASP.NET Core具有一个以ServiceCollection和ServiceProvider为核心的依赖注入框架,虽然这只是一个很轻量级的框架,但是在大部分情况下能够满足我们的需要.不过我觉得 ...

  4. 一个轻量级分布式RPC框架--NettyRpc

    1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个轻量级的分布式RPC ...

  5. 设计一个简单的UI框架,实现不同模块之间相互转换,使用单例实现。

    实现不同模块或窗口的互相切换,其实用一些代码都可以实现,但是使用UI框架不仅方便后续修改添加删除,同时在做出扩展效果时,也可以快速实现.由于我还是学生,这些搭建UI的材料都是以前玩的一些小游戏里面,比 ...

  6. 自己动手写一个简单的MVC框架(第一版)

    一.MVC概念回顾 路由(Route).控制器(Controller).行为(Action).模型(Model).视图(View) 用一句简单地话来描述以上关键点: 路由(Route)就相当于一个公司 ...

  7. hadoop 依赖式job_每天一学:一个轻量级分布式任务调度框架 XXL-JOB

    概述 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源代码并接入多家公司线上产品线,开箱即用. 官方地址中文版:http://www.xux ...

  8. 仿照源码,手写一个自定义 Spring MVC 框架

    毫无疑问,Spring 框架目前已经成为 Java 开发的行业标准,Spring MVC 作为其 Web 解决方案,是所有 Java 开发者都必须掌握的基本技能,理解其底层原理,才能更好地应用它进行实 ...

  9. OCRunner 第零篇:从零教你写一个 iOS 热修复框架

    为什么要热修复 在软件开发过程中,很难避免 BUG 的存在,尤其是对于一些达到一定规模的 App 因为协作模式错综复杂,就很容易带着问题上线. 一旦问题上线之后,问题就麻烦了,不仅需要重新打包.测试, ...

最新文章

  1. html 打印时隐藏,html 打印相关操作与实现详解
  2. Mac 设置文件默认打开方式
  3. 【Android 插件化】Hook 插件化框架 ( 创建插件应用 | 拷贝插件 APK | 初始化插件包 | 测试插件 DEX 字节码 )
  4. Spring autowire 自动装配简介
  5. 转python version 2.7 required,which was not found in the registry
  6. how is Customizing activity launched by ABAP Framework
  7. ssh连接阿里云服务器遇到的坑
  8. 7种常见的数据分析基本思路,满足你职场95%的需求
  9. dll 文件创建与使用
  10. 利用 sql server agent(sql server代理)实现定时备份
  11. 拓端tecdat|Python用稀疏、高斯随机投影和主成分分析PCA对MNIST手写数字数据进行降维可视化
  12. fedora15下GNOME3使用笔记
  13. OpenSIPS Crash 宕机的初步解决办法(二)
  14. Win11动态磁贴没了?Win11中恢复动态磁贴的方法
  15. WhatsApp被禁用该如何操作呢?实操WhatsApp解封全过程| 2022五月
  16. dubbo学习:2小时入手RPC框架Dubbo分布式服务调度(一)
  17. [从头读历史] 第303节 星球战争 起始:冷兵器战争的宏观架构
  18. www.us258.com 这家主机商相当无耻,用着不到一个月给我停了,连用户名都删了。...
  19. office 365安装包下载
  20. 项目经验之:针对昨日FDO连接SDF文件报错,国个GIS论坛给出了一个思路.帮助我过关.

热门文章

  1. 阴天,心情很down!
  2. HQChart使用教程67 - 鼠标点击K线柱子监听事件
  3. RISC-V学习笔记【中断和异常】
  4. Jsoup+JavaMail实现自动投票,验证邮箱功能
  5. Maxwell安装与入门
  6. 2008-2020年数据上市公司高管团队异质性数据包含Stata代码
  7. 【钉钉-场景化能力包】公司/园区数字食堂
  8. 深入理解halcon相机标定
  9. python代码少儿编程转换_数据类型转换_清华尹成python入门教程_少儿编程视频-51CTO学院...
  10. 物联网(Iot)台灯设计完整教程(长图文)