本文紧接【5.0苍穹源码浅析】二、接口详解(上篇) (kingdee.com),点击菜单后,会接着发起如下请求:
showForm打开表单,当然得去加载这个表单,其逻辑就在loadData接口,本篇重点解析
url | http://127.0.0.1:8881/ierp/form/batchInvokeAction.do?appId=a2h6_testapp&f=a2h6_test_basedata&ac=loadData |
请求体 | pageId: 1844121403830305792root07d825e480e44b9bbd7345f67f8ebc5c appId: a2h6_testapp params: [{"key":"","methodName":"loadData","args":[],"postData":[]}] |
响应体 |
咱们来瞅瞅这个响应:
1、setVisible
之前举例过了,又是哪个插件里用了this.getView().setVisible()方法,没啥好说的
2、updateControlMetadata
字面意思像是更新控件元数据,其实也就是更新前端控件样式,如是否折叠、列表是否滚动加载、最大页数等
这些标识还挺熟悉的,filtercontainerap是列表过滤控件的标识,gridview是单据列表的表格视图标识,其实都是列表模板bos_list里的控件名
3、InvokeControlMethod
在上篇中也出现过,即触发前端控件的相应方法,第一个InvokeControlMethod根据key和方法名大概推断是给表格视图、轻分析视图添加列,给卡片视图创建视图,点开args里的columns,能看到列名,是否支持过滤、排序等配置信息
第二个InvokeControlMethod能推断是给过滤控件添加过滤字段、设置折叠等,给单据列表设置选中行,焦点等
4、u
这个u得点开来看,会发现里面有真正的业务数据data,通过前面几个接口,前端已经拿到如过滤组件和列表表头等页面信息,能展示这个列表了,就差填充数据了,原来是在这个返回里,这就完整了!
格式有点像我们以前开发的分页接口,如总数datacount,总页数pagecount,页码pageindex,每页页数pagerows,dataindex里是列标识与序号的映射,rows里是序号与列值的映射,要结合着看,比如number对应的值是rows里的第三个
下面我们从源码分析这个loadData请求,其实主要就是这些内容
先看createModelData(model)方法,实际会调用AbstractFormDataModel#createNewData方法,逻辑还是比较清晰的:先触发插件的createNewData方法,如果插件方法给入参里的dataEntity赋了值,就用这个作为model里的,否则new一个 DynamicObject来作为,createDefaultEntity方法会给主业务组织赋默认值和给单据体添加缺省行数。
最后再触发插件的afterCreateNewData方法,这么看其实逻辑并不多,都在插件里。
平台默认自带的插件和列表自带的插件(下图前三个,Hide那个是我二开的),beforeCreateNewData和afterCreateNewData方法没啥逻辑,所以核心逻辑还在后面
再看看this.view.updateView()方法,我们这是打开列表,会进到kd.bos.mvc.list.ListView#updateView方法:这方法很长,先是对过滤控件做处理,再设置排序相关,最后是刷新视图,刷新轻分析视图等,这里主要看核心逻辑super.updateView(),即kd.bos.mvc.list.AbstractListView#updateView
这里主要是对单据列表控件设置一些权限filter,核心逻辑还得往super找,即kd.bos.mvc.form.FormView#updateView()
欸,这里逻辑有点意思了,居然在触发插件的beforeBindData方法和afterBindData方法(标品自带插件里这两方法同样没啥逻辑),看来获取业务数据的逻辑就在这中间的IFormDataBinder.updateView()!!!
其实仔细想下,逻辑在这并不奇怪,因为不管是打开表单(FormView)还是列表(ListView),都需要获取业务数据,当然得放在父类里
kd.bos.mvc.form.FormDataBinder#updateView()方法逻辑如下
会先收集触发时机为【创建】的界面规则和业务规则,然后触发FormRoot的bindData方法
FormRoot类继承自Container类,表示整个表单的根容器(对照下图更好理解),未重写bindData方法。
Container类可以理解为元数据设计时用的flex容器,包含许多控件(即下图items),其bindData方法就是遍历执行下级控件的bindData方法
这里我们打断点逐一查看items,很容易发现有个BillList类型的控件,这不就是单据列表的标识,去看它的bindData方法!
此方法先是触发注册的beforeBindDataListeners,再调用bindData方法真正去获取数据,然后调用super.bindData方法,即继续遍历执行items的bindData方法,最后触发注册的afterBindDataListeners(所以可以利用这两个listener做一些二次开发)
继续看this.bindData方法:还是先调用getListData方法拿到data,再通过clientViewProxy类把data返回给前端(此处setEntryProperty最终会添加到u里返回)
getListData方法会先判断分页用到的limit(即第几条,多少条),终于看到熟悉的操作,看来这个getData方法要查询数据了
这个方法特别长,咱们长话短说,就是拿到ListModel(列表数据模型),设置过滤条件,需要查询的字段,此次查询的最大数据量,然后通过model.getData方法去查询数据,最后按u的格式组装数据(注意这里分页数据会存放在model里)
进入model.getData方法,再次发现是调用provider的getData方法,这个provider就是我们在列表插件beforeCreateListDataProvider方法里设置的那个ListDataProvider,没设置时默认用kd.bos.mvc.list.ListDataProvider
进入kd.bos.mvc.list.ListDataProvider#getData方法,又是交给query对象去处理,这个query取决于列表查询方式配置是IdQuery还是SqlQuery,QueryBuilder对象存放了过滤条件,limit等
后面的代码就不继续往下看了,等后面专门出一篇文章细讲。
现在知道u是从哪来的了,updateControlMetadata,InvokeControlMethod还不知道,我们从ClientViewProxy打条件断点重新调一次接口(怎么打断点上一篇有讲),触发的代码太多,找几个看下就行。
比如updateControlMetadata都是在控件bindData时触发
createClientConfig方法就是取各种属性组装返回
而InvokeControlMethod会比较绕一点,一般都是从这几个calls,states1转换而来的,_premethodCalls和_methodCalls其实用法相同,都是操作控件某些方法,只是_premethodCalls里的会放在前面,优先于_methodCalls里的被前端执行,下面两个states1会在setVisible,给单据体字段设置setEnable时添加值
5、总结
loadData请求主要就是触发view的updateView()方法,打开列表时view是ListView,这里override逻辑里比较重要的是会设置权限qfilter,然后走父类FormView#updateView()方法,逻辑分这几步(下一篇讲加载表单时逻辑相似):
触发插件的beforeBindData方法
拿到表单的根控件(可以直接理解为元数据设计列表时的根节点),调用其bindData方法,继而遍历其下级控件的bindData方法,很多控件会在此时通过updateControlMetadata把自身的配置信息传递给前端,通过InvokeControlMethod让前端执行控件的一些方法,如过滤控件会添加过滤字段等
重点是会触发单据列表控件BillList的bindData方法,会收集过滤条件,需要查询的字段,此次查询的最大数据量,limit等,然后用具体的查询方式,如IdQuery、SqlQuery去查询数据(最终也是通过ORM.queryDataSet查询的数据库,然后转换成DynamicObjectCollection),数据会存放在BillList的ListModel里,最后按分页接口的格式组装数据返回给前端
触发插件的afterBindData方法
推荐阅读