深入学习SAP UI5框架代码系列之六:SAP UI5控件数据绑定的实现原理
这是Jerry 2021年的第 7 篇文章,也是汪子熙公众号总共第 278 篇原创文章。
系列目录
(2) SAP UI5 控件渲染机制
(3) HTML原生事件 VS SAP UI5 Semantic事件
(6) SAP UI5控件数据绑定的实现原理(本文)
(7) SAP UI5控件数据绑定的三种模式:One Way,Two Way和OneTime实现原理比较
(8) SAP UI5控件ID的生成逻辑
(9) SAP UI5控件的多语言(国际化,Internationalization,i18n)支持的实现原理
(10) XML视图里的button控件
(11) button控件和它背后的DOM元素
Jerry使用Angular做开发已经有几个月了,其间也学习了Angular控件的数据绑定,再回过头来重温SAP UI5控件数据绑定,别有一番味道。
之前Jerry时不时会在微信上收到一些朋友的咨询,诸如:“我的SAP UI5控件做了绑定,从Chrome开发者工具也能看到后台返回的OData API确实包含了数据,但是UI5页面渲染出来之后,为什么控件上没有显示绑定的数据呢?”此类问题。
其实,如果了解一些SAP UI5控件的数据绑定实现细节,此类问题完全可以通过自己调试的方式去排错。
我们还是使用之前文章 一个用于SAP UI5学习的脚手架应用,没有任何后台API的依赖 提到的脚手架应用来学习。
添加下列五行高亮代码:
创建一个新的JSON模型,包含一个字段field_for_text, 值为Jerry,再将该模型绑定到button控件,模型字段field_for_text绑定到button的text属性。
这样一来,运行时看到按钮的标签就成了field_for_text的值"Jerry",而不是第18行给text属性赋予的硬编码值"Button":
本文的目的就是,阐述清楚第一张图高亮的五行代码,背后发生了什么事情。
var oModel = new sap.ui.model.json.JSONModel();
当这行代码执行之后,JSONModel以及包含对应的模型绑定实现逻辑的JavaScript源文件会自动被加载,这体现了SAP UI5 Module的懒加载特性,在本系列这篇文章里介绍过:深入学习SAP UI5框架代码系列之一:UI5 Module的懒加载机制。
本系列之前的文章,我们曾经多次提及SAP UI5控件的原型继承链:
Button -> Control -> Element -> ManagedObject -> EventProvider -> BaseObject
本文我们会学习到另一条原型继承链:
JSONModel -> ClientModel -> Model -> MessageProcessor -> EventProvider -> BaseObject
从上图第61行的构造函数的输入参数oData,不难发现,在UI5应用里创建JSONModel实例时,可以将模型存储的数据一并指定。
这样,我们之前脚手架应用里的代码,可以精简成下图右边所示:
单步调试JSONModel的构造函数,会发现它依次会调用原型继承链上游节点的ClientModel,Model和MessageProcessor的构造函数。
下图是Model构造函数的实现,信息量很大:
(1) 第69行,说明SAP UI5模型绑定的默认方式是双向绑定;
(2) 第83行,说明SAP UI5模型绑定支持单向,双向和单次绑定。本系列下一篇文章 SAP UI5控件数据绑定的三种模式:One Way,Two Way和OneTime实现原理比较 会介绍这三种方式的区别。
oModel.setData(myData);
将一个JSON对象通过setData传入JSONModel实例。
setData方法有一个可选参数bMerge,如果显式指定为true,则会调用下图88行的jQuery.extend, 将本地传入的JSON对象同JSONModel原有的数据进行合并(merge).
因为我们本例调用setData并未指定该参数,所以不进行合并,直接执行95行的checkUpdate:
因为此时UI5 button控件实例尚未同JSONModel实例建立绑定关系,所以模型的aBindings数组为空,因此checkUpdate实际并未执行任何和控件绑定相关的操作,直接返回。
oButton1.setModel(oModel);
这行语句的作用,就是将控件实例和传入方法的JSONModel实例建立绑定关系。
4597~4604:该IF分支处理UI5控件之前已经绑定到某个模型实例的情况,此时使用delete操作,删除原有的模型引用,然后更新绑定信息。而本例是button控件第一次调用setModel,故不会进入此IF分支。
4607:UI5控件维护了一个类似字典的键值对数据结构,key为模型名称,value为模型实例。从此处的SAP UI5源代码实现不难得出结论,UI5控件支持同时绑定到多个模型实例上,只要该控件在调用setModel方法时,给传入的模型实例通过方法第二个参数sName,赋予不同的模型名称即可。
至此,JSONModel实例的field_for_text字段值,还不会自动流向button控件的text属性,直到下面这行代码的执行。
oButton1.bindProperty(“text”, “/field_for_text”);
该方法首先构造了一个包含JSONModel绑定字段的对象oBindingInfo:
将该oBindingInfo维护到控件的中央绑定信息存储数据结构mBindingInfos里,该数据结构同样是一个键值对,key为控件属性,本例为text,值为oBindingInfo对象,对象里包含了text属性绑定到JSONModel实例的字段名称:field_for_text.
单步调试进入上图3347行的_bindProperty方法:
3417行,调用JSONModel的bindProperty方法,生成oBinding对象:
这里再次出现了关于SAP UI5数据绑定的三种模式处理的源代码,本系列后续文章会专门介绍。
3433行将生成的oBinding对象实例,添加到aBindings数组里。
那么oBinding实例是如何生成的?需要单步调试上图3417行的oModel.bindProperty代码。
JSONModel.bindProperty的实现,就是一个单纯的new调用。不过更重要的是,此处我们了解到了另一条原型继承链:
JSONPropertyBinding -> ClientPropertyBinding -> PropertyBinding -> Binding -> EventProvider -> BaseObject
我们注意到,在oBinding实例里,有个字段叫做oValue, 其值正好是JSONModel实例维护的JSON对象字段field_for_text的值Jerry. 实际上,最后button控件的text属性,显示的值正是oBinding实例oValue字段的值。
所以需要搞清楚this.oValue的赋值逻辑,就得单步执行上图第35行的this._getValue方法。
this._getValue();
该函数负责将field_for_text字段的值,从this.oModel.oData.field_for_text字段中提取出来,如下图所示:
一旦this._getValue执行完之后,控件text属性同JSONModel实例的field_for_text字段就成功建立起绑定关系,之后我们就能直接从控件实例变量oButton1出发,去找到其text属性应该显示的值:
希望通过本文的介绍,能让大家对SAP UI5控件数据绑定的实现原理有一个最基本的了解。
如果遇到控件绑定不能按照自己的期望工作的时候,不妨试试按照本文提到的这些关键点去调试。
本系列下一篇文章,会介绍SAP UI5控件数据绑定的三种模式:One Way,Two Way和OneTime的实现原理比较。
我的实际工作中发现,了解了SAP UI5这些数据绑定方式的实现,对我近期学习Angular的数据绑定也有一定的借鉴作用。
感谢阅读。