搜索分页技术常常与另一个名词LazyLoading(惰性加载)联系在一起。今天Jerry先给大家介绍一下UI搜索分页在S/4HANA、CRMFiori和S4CRM应用中的实现原理。下半场SAP成都研究院菜园小哥王聪将为大家介绍Twitter的懒加载实现。王聪的背景介绍可以参考他之前的文章:SAP成都研究院非典型程序员,菜园子哥:我在使用UI5诊断工具的时候用到了什么。S/4HANAFiori应用搜索分页实现原理以S/4HANAProductMasterFiori应用为例。如果不指定搜索条件,默认返回25条数据,UI上显示系统中的商品总数。在Jerry系统中使用了140个产品。搜索分页的实现是由于OData请求的参数,$skip=0&top=25,意思是从请求命中的第0条记录开始,一共返回25条记录。另一个参数$inlinecount,其工作原理可以类比ABAPOpenSQL的关键字SELECTCOUNT(*),用来统计数据库表的条目数。使用鼠标滚轮将页面向下移动到屏幕底部后,将使用参数$skip=25&top=25自动触发新的OData请求。这样,第26到第50个商品也从数据库表中读取出来,显示在FioriUI上。为什么分页大小为25?在FioriUI列表实现文件sap.m.ListBase.js中,默认的分页大小(GrowingThreshold)是20,也就是说必须要有一些配置,或者ProductMaster应用中某处的JavaScript代码把分页大小从20改成了to变成了25.限于篇幅,Jerry直接揭晓了答案。S/4HANA中的Fiori应用都是使用SmartTemplate技术实现的。列表区的实现位于模板文件SmartTable.fragment.xml中,指定growingThreshold为25。由于SmartTable.fragment.xml在时间序列上是在sap.m.ListBase.js之后加载的,所以定义页面大小具有更高的优先级。如果想通过自己调试找到答案,锻炼分析能力,可以参考我的调试过程:HowdoesUI5AutoGrowinglist(LazyLoadbehavior)work和使用SmartTemplate开发Fiori应用的介绍:Jerry'sthroughCDSview+SmartTemplate为Fiori应用开发博客合集我们来看看搜索分页的后台处理。在Netweaver事务代码ST05的数据库跟踪视图中,可以明显观察到分页效果:每次查询数据库只命中返回25条记录,如下图高亮显示的3条跟踪记录。前台通过UI5库文件发送的带有$skip和$top参数的OData请求,被后台接收并维护在io_query_options参数中:最后通过ABAP的关键字OFFSET实现数据库查询层面的页面处理打开SQL的。上面1674行@lv_offset的值是根据UI传入的$skip和$top计算出来的。CRMFiori应用搜索分页实现原理CRMFiori应用的前台搜索分页实现原理与S/4HANAFiori应用类似,只是页面大小改为20。上面的$skip和$top参数图和S/4HANA应用一样的行为,都传到后台处理:CRMFiori和S/4HANAFiori后台搜索分页处理的区别:没有使用ABAP的OFFSET关键字,由应用程序开发人员自己实施。(1)首先从数据库中取出所有符合查找条件的记录的GUID,由数据库层返回给ABAP层。在我的测试系统中,一共有21条记录,全部返回给ABAP层:(2)应用开发者根据$skip和$top的值,丢弃多余的记录,保证只返回20条记录用户界面到底。至此,S/4HANA和CRMFiori应用的搜索和分页原理介绍完毕。更详细的可以参考我的博客:S/4HANA和CRM中的SearchPaging实现Fiori应用S4CRM应用搜索分页实现原理S4CRM,全称S/4HANAforCustomerManagement,UI开发技术依然采用WebClientUI。不同于S/4HANA基于UI5的前端技术,WebClientUI走的是服务端渲染的BSP路线。严格来说,WebClientUI不存在数据库层面的搜索分页,其分页行为仅体现在服务端渲染。在下图的例子中,我指定了MaxNumberofResults为200,也就是说在期望满足搜索条件的记录中,UI上会显示200条记录。从搜索结果可以看出分页效果。所有200条记录都已从数据库中查询并存储在应用程序的内存中。如下图所示:WebClientUI只在ABAP后台渲染出第一页的HTML源码,然后显示给用户。当用户点击屏幕下方的“2”页码时,不会发生数据库查询,服务器所做的只是渲染第二页对应的HTML源代码。服务器如何知道它应该渲染第二个页面的源代码?这个信息也是在点击“2”页码后从前台发送给ABAP服务器的:ABAP后台拿到visibleFirstRow参数后,知道会从搜索结果记录的第21条开始渲染到第40条.更多渲染细节可以参考我的博客:S/4HANA中客户管理的分页实现了解了我们SAP搜索分页实现的原理之后,我们来看看其他厂商是怎么做的。对于国内的知乎、简书、新浪微博等网站,列表显示都实现了延迟加载。菜园子的弟弟王聪对这些感悟也很好奇。您为什么选择Twitter进行研究?这还得从他和好友老金的故事说起。以下是菜园子小弟王聪的解说。和往常一样,您可以点击文末“阅读原文”,获取王聪的中英德文解说文。*延迟加载,看看推特是怎么做的老金讨厌推特。老金是我在德国留学时的好朋友,在中国时他酷爱文学创作。但他从来没有开过博客什么的,坚持用新浪的“长微博”功能写文章。用他的话说,这代表了新锐文学的态度。到了德国后,老金发现老外用的不是微博,而是推特。作为一个新人,他自然是想入乡随俗,可刚要写,他发现推特里根本没有“长推”这个东西,140个字也做不了什么。于是老金一气之下卸载了推特,觉得西方文学要完蛋了。看老金一天到晚不开心,我就安慰他那条长微博,不就是文字换图吗。推特没有这个东西,就看我的本事了。我会为您编写一个名为“BigTwitter”的应用程序,并且我已经为您设计了所有图标。然后我花了两个晚上做了一个小工具,可以把大块的文字转成图片直接发到推特上。没想到老金刚找了我半天,说他写的东西莫名其妙都是马赛克,还跟“秦老师”发誓,他没写过大尺度的东西。我问他秦老师是谁?他说是印度著名诗人琴格尔老师!我心存善意,没有向他指出老师不姓秦。我就在想,好图怎么编码呢?我一看,原来是老金憋得太久了。这一次,他写了8400多字。生成的图片尺寸太大,又被鸡贼推特压缩了,所以像错别字一样模糊。相同的大小。心灰意冷的老金决定与推特分手,甚至注销了自己的账号。虽然我不怎么用Twitter,但是作为一个程序员我还是对它很感兴趣。作为同类产品中的佼佼者,Twitter自然有其优势。其中比较有特色的一点就是它的懒加载机制。今天我们就通过Debug来一探究竟。你需要了解的一些概念时间线(TimeLine):Twitter最重要的部分。一系列推文组合在一起,形成页面中间的长时间线。位置:推文的标识符。说白了就是推文的ID。新推文的Position比旧推文的Position大,所以我认为Position很可能代表“这是Twitter历史上的第xxxth条推文”。但是我随机找到的一个位置确实大到让我怀疑我的猜测。千里之行始于NetworkFirst,我们在开发者工具的Network工具中拦截用户滚动加载时发送的请求。结果看起来像这样。这里我们可以找到几个有意义的信息:max_position:遍历Header信息和请求参数,这是唯一和请求内容相关的东西。具体含义以后再说。has_more_items:顾名思义,服务端通过这个字段告诉前端是否有更早的内容。items_html:格式化后发现这部分就是我们请求的tweet的内容。很明显,Twitter使用了后端渲染技术,将推文内容渲染出来,直接发送到前端进行展示。min_position:正好对应请求中的max_position。new_latent_count:本次请求的推文数量。钻得更深为了找出消息是什么,我们通过寻找请求的发起者来深入研究代码。结果是Twitter在这里发送了一个XMLHttpRequest。无论请求是什么,总要有处理方法。我们在CallStack中向上追溯,找到了请求的定义位置。这里我们进入请求成功的方法,继续探索。最终到达终点,items_html被添加到时间轴中。min_position和max_position呢?我们回到刚才定义request的位置继续向上追溯,找到了getOldItems这个方法。当用户在时间轴上向下滚动鼠标到最后时,会调用该方法,将上次响应中的min_position赋值给本次请求中的max_position。至此,我们就可以衔接整个推特的懒加载流程了:用户向下滚动时间线,发送请求,通知服务器“我已经看完A篇了,让我看前面的内容”。服务器将A的20条内容返回给前端,并告诉用户“嘿,现在我已经把B之前的所有内容都发给你了,我们来看看吧”。用户再次阅读这些内容,向下滚动时间线,告诉服务器“到B条的内容我已经看完了,你可以在B条之前发给我”。不一定每次20个?在研究的过程中,我发现了一个有趣的现象,就是new_latent_count绝大部分是20,偶尔会略小于20。由于前端请求中不存在要请求的item数量,这个决定是在后端做出的。一开始以为后台会根据回复的内容大小来决定发多少条,但是分析了一些例子后发现,有时候回复明明很小,但还是发了不到20条.所以我的猜测是后端这个神奇的算法可能会判断返回的内容会被用户浏览多长时间,如果比较耗时,就少返回。比如推文中有一段很长的视频,判断阅读时间较长,可以少回几个。不过这只是我的胡乱猜测,知道原理的朋友可以留言告诉我,万分感谢。调试之痛坦白说,整个调试过程花费了我不少时间。一方面不熟悉它的代码结构,另一方面缩小后的js代码着实让人头疼。且不说所有的变量都长成abcd,用逻辑运算符写的条件判断语句比比皆是,让人口吐白沫。但是从学习的角度来说,整个过程下来无论是调试能力还是代码阅读能力都会有所提升。我推荐大家尝试一下。阅读更多Jerry的UI5框架代码自学教程Jerry的Fiori原创文章合集Jerry的WebClientUI42原创文章合集Jerry的破绽:SAPUI5、Angular、React与VueSAPUI和SalesforceUI开发谈SAPS4CRMvsC4C,诸葛亮与周瑜?Jerry给大家讲讲Chrome开发者工具HelloWorld,就是我在S/4HANAforCustomerManagement1.0那几年用的SAPIDE。更多Jerry原创技术文章,请关注公众号“汪子熙”
