本文解答上周一位网友向我咨询的一个问题。
问题可以抽象为,在 SAP CRM UI 上,当用户修改一个扩展字段的值时,是否可以编写逻辑,让这个 UI 上其他标准字段的值自动发生变化。
这个扩展字段的类型为「下拉列表」,当用户从下拉列表里任意选择一个值时,希望其他某些标准字段值发生联动。
以下图 Service Contract 抬头区域的扩展字段 AI Type 为例,其类型为下拉列表。
话说笔者离开 SAP CRM 开发团队已经有五六年了,但是再次看到上图打开的 WebClient UI ,仍然觉得异常亲切:我是 2012 年 3 月开始接触 SAP CRM 开发和 WebClient UI 这个框架。那个年代没有 ChatGPT,我的开发技术还显得很稚嫩,当时甚至还没有开始技术写作。开发过程中但凡遇到无法解决的技术问题,要么一行行阅读框架源代码和它们死磕,要么求助于资深的德国同事。
我其实也挺怀念 2012 年的时光,那时候工作中需要深入学习的技术其实不多,就 ABAP + HANA 两样,仅此而已。不像现在,要学的东西十根指头都数不过来......
2012 年我还没听说过 SAP UI5,更别提 Docker,Kubernetes,SAP BTP,这些都还没有诞生。那个年代可能所有开发者都不会想到,十多年以后 AI 居然可以在浏览器里,仅仅通过简单的交谈就能快速生成高质量的代码。
好像有点扯远了,下面我们将这个需求进行拆解,翻译成 ABAP 领域的技术语言。
1. 创建类型为下拉列表的扩展字段。
2. 用户从下拉列表里选择一条记录时,触发一个 ABAP 事件。
3. Service Contract 的 UI 捕捉到这个事件,在事件处理方法里编写 ABAP 代码,修改 Service Contract BO 的值。
上面步骤 1,笔者写过详细的教程,中英文版本的都有。
英文版:https://community.sap.com/t5/crm-and-cx-blog-posts-by-sap/create-extension-field-with-type-code-list-via-aet/ba-p/13334561
中文版:
这个需求的实现关键在于第二步:当用户在浏览器从下拉列表选择一个值时,如何触发 ABAP 端的事件?
WebClient UI 本质上还是 ABAP BSP(Business Server Page), MVC 三层的实现和运行都发生在 ABAP 服务器端。
用户在浏览器里面操作,首先触发的是浏览器端的 JavaScript 代码。这些客户端执行的 JavaScript 代码,如何触发 ABAP 服务器端的事件?
笔者 2014 年曾经在 SAP 社区上发布过一篇博客,介绍了两种通过 JavaScript 代码触发 ABAP Event 的技术方法:
Two approaches to trigger ABAP backend event via Javascript
https://community.sap.com/t5/technology-blog-posts-by-sap/two-approaches-to-trigger-abap-backend-event-via-javascript/ba-p/13264085
本文分享另一种纯 ABAP 编程实现的方法,并且开发量非常少。
以 Service Contract UI 为例。因为我的扩展字段是创建在抬头区域的,所以这些字段会出现在 Model 的 BTADMINH 这个 Context Node 下面。
所以对这个 Context Node 的实现类(上图 CN00 结尾的类)中名叫 GET_P_S_EXT 的方法进行重新实现(redefine),即覆写其父类实现。
这里 P 代表 Property,S 代表 Structure,因为这个 Context Node 下的所有扩展字段,都被一个虚拟的名叫 EXT 的结构包裹起来。
EXT 代表 Extension Field,GET 说明这是一个回调方法。开发人员负责实现方法,但不用操心方法什么时候会被执行到。WebClient UI 的框架会在需要的时候自动去调用这些 GET 方法。
GET_P_S_EXT 的实现代码:
CALL METHOD super->get_p_s_ext EXPORTING component = component io_current = io_current iv_index = iv_index iv_property = iv_property iv_display_mode = iv_display_mode iv_is_table = iv_is_table iv_is_search = iv_is_search RECEIVING rv_value = rv_value. CASE iv_property. WHEN if_bsp_wd_model_setter_getter=>fp_server_event. rv_value = 'EXT_SELECTED'. ENDCASE.
但凡子类 redefine 继承自父类的方法,第一件事就是使用 super 调用父类的实现,然后添加子类自己的逻辑。
这里我编写的逻辑,翻译成自然语言就是:当 WebClient UI 初始化扩展字段时,询问 Service Contract UI component,"你这个扩展字段有 server event 需要维护吗?"
我通过给 GET_P_S_EXT 里的返回参数 rv_value 赋值成 `EXT_SELECTED`,回答 WebClient UI:"有啊,server event 的名称叫做 EXT_SELECTED".
当然,本例我的抬头区域只创建了一个扩展字段。如果存在多个扩展字段,需要通过输入参数 component 即扩展字段的名称来进行区分。
Server Event 就是 ABAP 服务器端的事件。如此一来,当这个扩展字段的值在浏览器里发生变化时,WebClient UI 框架就会自动触发在 UI Component 里编写的针对事件 `EXT_SELECTED` 的事件处理方法。
所以剩下的事情就简单了,在 UI Component 里创建一个名叫 `EXT_SELECTED` 的事件即可。
Workbench 会自动为其创建对应的事件处理方法,即下图的 EH_ONEXT_SELECT.
这个方法里就可以编写根据用户选择的内容,对标准字段进行修改的逻辑了。
最后对这个实现方案进行复盘。把需求扔给 ChatGPT,它给的实现已经非常接近最终方案了,只是 ChatGPT 不清楚扩展字段没办法单独实现 GET_P 方法,而需要在 EXT 这个虚拟的 Wrapper 上实现。这是大语言模型幻觉的一个例子,不过它的回答指向了正确的方向。
更多阅读