https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/topics/SAPUI5-blog-posts.xml SAP Community - SAPUI5 2026-07-24T20:01:41.356595+00:00 python-feedgen SAPUI5 blog posts in SAP Community https://community.sap.com/t5/technology-blog-posts-by-members/stop-users-from-typing-invalid-values-in-sapui5-combobox-a-practical/ba-p/14429701 Stop Users from Typing Invalid Values in SAPUI5 ComboBox – A Practical Validation Approach 2026-06-29T21:36:52.647000+02:00 Myvizhipriya_Thangaraj_2810 https://community.sap.com/t5/user/viewprofilepage/user-id/1477011 <H3 id="toc-hId-1947447359"><STRONG>Introduction</STRONG></H3><P><SPAN>In one of my recent SAPUI5 projects, I needed users to select a value from a predefined list. Since the list was quite large, I wanted to make the selection process as smooth as possible. Instead of using </SPAN><STRONG>sap.m.Select</STRONG><SPAN>, I went with </SPAN><STRONG>sap.m.ComboBox</STRONG><SPAN> because it lets users <STRONG>search for values by typing, making it much easier</STRONG> to find the right option.</SPAN></P><P><SPAN>Everything worked as expected until I started writing unit test cases. While testing different scenarios, I noticed that users weren't limited to selecting values from the dropdown they could also type any value they wanted. Even if the entered text didn't exist in the list, the </SPAN><SPAN>ComboBox</SPAN><SPAN> would still accept it.</SPAN></P><P><SPAN>It might seem like obvious behavior once you know how </SPAN><SPAN>ComboBox</SPAN><SPAN> works, but it was something I hadn't considered during development. That's when I realized that while the control offers a great user experience, it doesn't automatically guarantee valid input.</SPAN></P><P><SPAN>We could have used a value help dialog for selection, but the requirement specifically called for a <STRONG>dropdown-based experience</STRONG>. That’s why the focus stayed on <STRONG>ComboBox&nbsp;</STRONG> and a deeper validation approach around it.</SPAN></P><P><SPAN>In this article, I'll walk through the approach I used to keep the search functionality intact while ensuring users can only submit values that actually exist in the dropdown.</SPAN></P><H2 id="toc-hId-1621851135">&nbsp;</H2><H3 id="toc-hId-1554420349"><STRONG>The Real Issue in Applications</STRONG></H3><P><SPAN>In actual business scenarios, this becomes a real problem when data moves to the backend. Users may enter free text in the </SPAN><SPAN>ComboBox</SPAN><SPAN>&nbsp;and it still gets treated as valid input even if it doesn’t exist in the master data.</SPAN></P><P><SPAN>This can lead to invalid payloads, failed backend validations, and inconsistent business data in reports. What starts as a small UI behavior can quickly turn into a data integrity issue.</SPAN></P><P><SPAN>That’s why, when using </SPAN><SPAN>sap.m.ComboBox</SPAN><SPAN> for strictly controlled fields like Country, Plant, or Company Code, frontend validation becomes essential before processing the input.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Combobox Behaviour.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/427308i9B14D60484B24609/image-size/large?v=v2&amp;px=999" role="button" title="Combobox Behaviour.png" alt="Combobox Behaviour.png" /></span></P><P class="lia-align-center" style="text-align: center;">&nbsp;<EM><STRONG>Picture 1: Understanding the ComboBox and It's Behaviour</STRONG></EM></P><P>&nbsp;</P><H3 id="toc-hId-1357906844"><STRONG>The Approach I Used</STRONG></H3><P><SPAN>To handle this, I added a simple validation on the </SPAN><SPAN>ComboBox</SPAN><SPAN> events. The idea was straightforward—accept the input only if it matches a valid item from the list.</SPAN></P><P><SPAN>I mainly relied on the </SPAN><SPAN>change</SPAN><SPAN> event along with </SPAN><SPAN>getSelectedKey()</SPAN><SPAN>. If the user entered something that didn’t match any item, I cleared the value, showed an error state using </SPAN><SPAN>setValueState("Error")</SPAN><SPAN>, and displayed a meaningful message.</SPAN></P><P><SPAN>I also used </SPAN><SPAN>selectionChange</SPAN><SPAN> to reset the error state when a valid option was selected from the dropdown, ensuring a smoother user experience.</SPAN></P><P><SPAN>&nbsp;</SPAN></P><H3 id="toc-hId-1161393339"><STRONG>Implementation</STRONG></H3><P><SPAN>The validation was handled mainly using the </SPAN><SPAN>change</SPAN><SPAN> and </SPAN><SPAN>selectionChange</SPAN><SPAN> events. The goal was to ensure that only valid values from the dropdown are accepted, while giving clear feedback when something incorrect is entered.</SPAN></P><H5 id="toc-hId-1223045272"><SPAN>XML View Code: (.xml file)</SPAN></H5><pre class="lia-code-sample language-markup"><code>&lt;!-- XML View --&gt; &lt;ComboBox id="countryCombo" items="{path: '/Countries'}" selectionChange=".onSelectionChange" change=".onChange"&gt; &lt;core:Item key="{CountryCode}" text="{CountryName}" /&gt; &lt;/ComboBox&gt;</code></pre><H5 id="toc-hId-1026531767"><SPAN>Controller Code: (.js file)</SPAN></H5><pre class="lia-code-sample language-javascript"><code>// Controller Logic onChange: function (oEvent) { const oCombo = oEvent.getSource(); const sValue = oCombo.getValue().trim(); const sKey = oCombo.getSelectedKey(); if (!sValue || !sKey) { oCombo.setValue(""); oCombo.setValueState("Error"); oCombo.setValueStateText("Please select a valid value from the list."); return; } oCombo.setValueState("None"); }, onSelectionChange: function (oEvent) { oEvent.getSource().setValueState("None"); }</code></pre><H3 id="toc-hId-571852824">&nbsp;</H3><H3 id="toc-hId-375339319"><STRONG>Best Practices</STRONG></H3><P><SPAN>When working with </SPAN><SPAN>sap.m.ComboBox</SPAN><SPAN>, validation should always be treated as part of the design, not an afterthought.</SPAN></P><P><SPAN>Use </SPAN><SPAN>getSelectedKey()</SPAN><SPAN> instead of relying only on </SPAN><SPAN>getValue()</SPAN><SPAN>, since free text input is always possible. Trim user input before validation to avoid whitespace-related issues.</SPAN></P><P><SPAN>Always provide clear </SPAN><SPAN>ValueState</SPAN><SPAN> feedback so users immediately understand what went wrong. Also, reset the error state on valid selection using </SPAN><SPAN>selectionChange</SPAN><SPAN> to improve usability.</SPAN></P><P><SPAN>Finally, even if frontend validation is in place, always enforce the same checks at the backend to ensure data integrity.</SPAN></P><P class="lia-align-center" style="text-align: center;"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Data Integrity.png" style="width: 971px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/427315i1F9026392A3E4F4B/image-size/large?v=v2&amp;px=999" role="button" title="Data Integrity.png" alt="Data Integrity.png" /></span></P><P class="lia-align-center" style="text-align: center;"><EM><STRONG>&nbsp;Picture 2: Data Integrity</STRONG></EM></P><P class="lia-align-center" style="text-align: center;">&nbsp;</P><H3 id="toc-hId-178825814"><STRONG>Alternative Approaches</STRONG></H3><P><SPAN>Depending on the requirement, there are a few other ways to handle this.</SPAN></P><P><SPAN>If search is not critical and the list is small, </SPAN><SPAN>sap.m.Select</SPAN><SPAN> is a simpler option since it only allows predefined selections.</SPAN></P><P><SPAN>In some cases, developers try disabling free typing in </SPAN><SPAN>ComboBox</SPAN><SPAN>, but that removes the search flexibility, which is often the main reason for choosing it in the first place.</SPAN></P><P><SPAN>Another option is model or backend-level validation, but that should always complement frontend checks rather than replace them.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Right Approach.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/427316i05C7F397641C44B7/image-size/large?v=v2&amp;px=999" role="button" title="Right Approach.png" alt="Right Approach.png" /></span></P><P class="lia-align-center" style="text-align: center;">&nbsp;<EM><STRONG>&nbsp;Picture 3: Right Approaches for Handling Selections</STRONG></EM></P><P>&nbsp;</P><H3 id="toc-hId--92919060"><STRONG>Key Takeaway &amp; Conclusion:</STRONG></H3><P><SPAN>A </SPAN><SPAN>ComboBox</SPAN><SPAN> is not automatically restricted to its items. If your requirement is to allow only predefined values, you must explicitly validate the user input before processing it.</SPAN></P><P><SPAN>The goal is not to remove the flexibility of </SPAN><SPAN>sap.m.ComboBox</SPAN><SPAN>, but to balance it—keep the search experience intact while ensuring only valid, business-approved values make it to the backend.</SPAN></P><P><SPAN><STRONG>Kindly Note:</STRONG>&nbsp;</SPAN><SPAN>We could have used a value help dialog for selection, but the requirement specifically called for a dropdown-based experience. That’s why the focus stayed on </SPAN><CODE><SPAN>sap.m.ComboBox</SPAN></CODE><SPAN> and a deeper validation approach around it.</SPAN></P><P><SPAN>If you have any alternative effective solutions, please feel free to share your thoughts and so everyone can learn from each other.</SPAN></P><P>Thank you for taking the time to read this blog! If you found this helpful, i would love to hear your thoughts, feedback or questions in the comments. Let's keep learning and growing together!</P><P>I’m still new to blogging, so if you notice anything that could be improved or corrected, please don’t hesitate to let me know!!</P> 2026-06-29T21:36:52.647000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/deploy-a-sap-fiori-elements-app-from-bas-to-s-4hana-cloud-and-register-it/ba-p/14430175 Deploy a SAP Fiori Elements App from BAS to S/4HANA Cloud and Register It as a Launchpad Tile 2026-06-30T11:31:48.957000+02:00 abhishekpandey https://community.sap.com/t5/user/viewprofilepage/user-id/1452165 <P><SPAN>Most tutorials about deploying Fiori apps from Business Application Studio (BAS) stop at 'Deployment Successful.' But deployment is only the midpoint. For a user to actually find and launch your app in the SAP Fiori Launchpad, you need to wire up four more things: a crossNavigation inbound in the manifest, an IAM App, a Business Catalog, and a Launchpad Space. Get any one of these wrong and you hit silent failures — the app exists in the system but never shows as a tile.</SPAN></P><H2 id="toc-hId-1819014497">Prerequisites</H2><UL><LI><SPAN>SAP Business Technology Platform (BTP) subaccount with SAP Business Application Studio subscription</SPAN></LI><LI><SPAN>S/4HANA Cloud system with an active OData V4 service</SPAN></LI><LI><SPAN>A BTP Destination configured for your S/4HANA system</SPAN></LI><LI><SPAN>Eclipse ADT connected to your S/4HANA system</SPAN></LI></UL><H2 id="toc-hId-1622500992">Architecture Overview</H2><P>OData V4 Service (S/4HANA ABAP)<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ▼<BR />Fiori Elements List Report App&nbsp; ◄── Generated in SAP BAS<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp; npm run deploy&nbsp; (deploy-to-abap, ui5-deploy.yaml, gCTS transport)<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ▼<BR />BSP Application + LADI /NAMESPACE/MYAPP_UI5R&nbsp; (auto-created on deploy)<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ▼<BR />IAM App (External App type)&nbsp; ◄── Created in ADT, Published Locally<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ▼<BR />Business Catalog&nbsp; ◄── Created in ADT, Published Locally<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ▼<BR />Business Role&nbsp; ◄── Assigned in Maintain Business Roles<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; │<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ▼<BR />Launchpad Space / Page&nbsp; ◄── Tile visible to user</P><P>&nbsp;</P><P><SPAN>&nbsp;</SPAN></P><H1 id="toc-hId-1296904768">Step 1 — Open Business Application Studio via BTP Cockpit</H1><P><SPAN>BAS is accessed through the SAP BTP Cockpit — not through the S/4HANA Fiori Launchpad. Open BTP Cockpit → Subaccount → Instances and Subscriptions.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_0-1783311861190.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429558i374C74DD30F015DE/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_0-1783311861190.png" alt="abhishekpandey_0-1783311861190.png" /></span></P><P><SPAN>Find SAP Business Application Studio → click Go to Application. You land in the SAP Build Lobby.</SPAN></P><H1 id="toc-hId-1100391263">Step 2 — Create the Project in SAP Build</H1><P><SPAN>In the SAP Build Lobby, click Create (top right) to launch the Create Project wizard.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_1-1783311861195.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429559iE8569516E6EB653D/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_1-1783311861195.png" alt="abhishekpandey_1-1783311861195.png" /></span></P><H3 id="toc-hId-1162043196">Wizard Step 1 — Objective: Application</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_2-1783311861200.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429560i49C9B17A3E98D303/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_2-1783311861200.png" alt="abhishekpandey_2-1783311861200.png" /></span></P><H3 id="toc-hId-965529691">Wizard Step 2 — Category: Full-Stack</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_3-1783311861206.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429561i0B439FB1B6A6A71F/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_3-1783311861206.png" alt="abhishekpandey_3-1783311861206.png" /></span></P><H3 id="toc-hId-769016186">Wizard Step 3 — Type: Full-Stack Node.JS</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_4-1783311861212.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429562i0468717A9C7FD254/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_4-1783311861212.png" alt="abhishekpandey_4-1783311861212.png" /></span></P><H3 id="toc-hId-572502681">Wizard Step 4 — Name and Dev Space</H3><P><SPAN>Provide a project name (e.g. myapp-audit-log) and create or select a Full-Stack dev space.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_5-1783311861219.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429563iB6255AD648D4FE64/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_5-1783311861219.png" alt="abhishekpandey_5-1783311861219.png" /></span></P><P><SPAN>Click Review then Create. SAP Build provisions the dev space and scaffolds the project.</SPAN></P><H1 id="toc-hId-117823738">Step 3 — Generate the Fiori App</H1><P><SPAN>With the project open in BAS, press Ctrl+Shift+P and run: Fiori: Open Application Generator</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_6-1783311861222.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429564iFE8FA046A6D8DCCE/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_6-1783311861222.png" alt="abhishekpandey_6-1783311861222.png" /></span></P><H3 id="toc-hId-179475671">Template Selection — List Report Page</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_7-1783311861226.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429566i71136FDB7EC8D975/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_7-1783311861226.png" alt="abhishekpandey_7-1783311861226.png" /></span></P><H3 id="toc-hId--92269203">Data Source — Connect to a System</H3><UL><LI><SPAN>Data Source: Connect to a System</SPAN></LI><LI><SPAN>System: Your S/4HANA destination (e.g. YOUR_SYSTEM (S4HC))</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_8-1783311861229.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429565i05656A71CFF1769B/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_8-1783311861229.png" alt="abhishekpandey_8-1783311861229.png" /></span></P><P><SPAN>Once connected, choose your OData V4 service.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_9-1783311861231.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429568i36DA61730B708200/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_9-1783311861231.png" alt="abhishekpandey_9-1783311861231.png" /></span></P><H3 id="toc-hId--288782708">Entity Selection</H3><UL><LI><SPAN>Main Entity: Root CDS entity exposed by the service</SPAN></LI><LI><SPAN>Automatically add table columns: Yes</SPAN></LI><LI><SPAN>Table Type: Responsive</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_10-1783311861234.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429567iDA9F72DA4609CC8B/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_10-1783311861234.png" alt="abhishekpandey_10-1783311861234.png" /></span></P><P><SPAN>Click Finish. BAS generates and opens the project.</SPAN></P><H1 id="toc-hId-101509801">Step 4 — Add the crossNavigation Inbound to manifest.json</H1><P><EM><span class="lia-unicode-emoji" title=":warning:">⚠️</span>&nbsp; This is where most guides go wrong by omission.</EM></P><P><SPAN>By default the Fiori generator creates an application-type app with no crossNavigation block. If you deploy as-is, the BAS terminal shows:</SPAN></P><P>warn&nbsp; No LADI was created as no inbound is defined in manifest.json<BR />warn&nbsp; App descriptor of type 'application' does not define a launchable inbound</P><P><SPAN>Without a Launchpad App Descriptor Item (LADI), you cannot create an IAM App that references your app, and therefore cannot add a tile to a Business Catalog.</SPAN></P><P><SPAN>Fix: Open webapp/manifest.json and add a crossNavigation block inside sap.app:</SPAN></P><P>"sap.app": {<BR />&nbsp; "id": "com.mycompany.myauditlog",<BR />&nbsp; "type": "application",<BR />&nbsp; "crossNavigation": {<BR />&nbsp;&nbsp;&nbsp; "inbounds": {<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "MyAuditLog-display": {<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "semanticObject": "MyAuditLog",<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "action": "display",<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "title": "{{flpTitle}}",<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "subTitle": "{{flpSubtitle}}",<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "signature": {<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "parameters": {},<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "additionalParameters": "allowed"<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR />&nbsp;&nbsp;&nbsp; }<BR />&nbsp; }<BR />}</P><P><SPAN>Also add the keys to webapp/i18n/i18n.properties:</SPAN></P><P>flpTitle=My Audit Log App<BR />flpSubtitle=View sales order change history</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_11-1783311861240.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429569i90E7AA92BC764338/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_11-1783311861240.png" alt="abhishekpandey_11-1783311861240.png" /></span></P><P>&nbsp;</P><P><EM><span class="lia-unicode-emoji" title=":memo:">📝</span>&nbsp; Tip on additionalParameters: Setting it to "" produces a harmless warning. Use the explicit string "allowed" or "ignored" to avoid it.</EM></P><P>&nbsp;</P><P><SPAN>&nbsp;</SPAN></P><H1 id="toc-hId--95003704">Step 5 — Create a gCTS Transport Request in ADT</H1><P><SPAN>The deploy-to-abap task requires a gCTS transport — a Workbench Request with target 1GT. A normal transport fails with:</SPAN></P><P>Request does not have transport target 1GT</P><P><SPAN>In ADT: Transport Organizer → right-click your user node → Create → Transport Request:</SPAN></P><UL><LI><SPAN>Type: Workbench Request</SPAN></LI><LI><SPAN>Target: 1GT</SPAN></LI></UL><P><SPAN>Note the transport number (e.g. ABC9xxxxx) — you will plug it into ui5-deploy.yaml.</SPAN></P><H1 id="toc-hId--291517209">Step 6 — Configure ui5-deploy.yaml</H1><P><SPAN>Create ui5-deploy.yaml in the project root:</SPAN></P><P>specVersion: "4.0"<BR />metadata:<BR />&nbsp; name: com.mycompany.myauditlog<BR />type: application<BR />builder:<BR />&nbsp; resources:<BR />&nbsp;&nbsp;&nbsp; excludes:<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - /test/**<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - /localService/**<BR />&nbsp; customTasks:<BR />&nbsp;&nbsp;&nbsp; - name: deploy-to-abap<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; afterTask: generateCachebusterInfo<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; configuration:<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; target:<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; destination: YOUR_SYSTEM<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; authenticationType: reentranceTicket<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app:<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name: /MYNAMESPACE/MYAUDITLOG<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; description: My Audit Log App<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; package: /MYNAMESPACE/MY_PACKAGE<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; transport: ABCK9xxxxx<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exclude:<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - /test/<BR />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - /localService/</P><P><EM><span class="lia-unicode-emoji" title=":warning:">⚠️</span>&nbsp; app.name must use your registered ABAP namespace prefix (e.g. /MYNAMESPACE/). Using a Z* name in a namespaced package causes: Customer object WAPA cannot be assigned to package</EM></P><H1 id="toc-hId--488030714">Step 7 — Build and Deploy</H1><P><SPAN>Open a terminal in the project root and run:</SPAN></P><P>npm install<BR />npm run build<BR />npm run deploy</P><P><SPAN>A successful deployment shows:</SPAN></P><P>Inbound MyAuditLog-display was converted to LADI /MYNAMESPACE/MYAUDITLOG_UI5R<BR />Launchpad App Descriptor Item /MYNAMESPACE/MYAUDITLOG_UI5R was created<BR />SAPUI5 Application has been uploaded and registered successfully<BR />Deployment Successful.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_12-1783311861245.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429572i2EC22859EC7ECAA5/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_12-1783311861245.png" alt="abhishekpandey_12-1783311861245.png" /></span></P><P><EM><span class="lia-unicode-emoji" title=":memo:">📝</span>&nbsp; Note the LADI ID (/MYNAMESPACE/MYAUDITLOG_UI5R) — you need it for the IAM App in the next step.</EM></P><H1 id="toc-hId--684544219">Step 8 — Create the IAM App in ADT</H1><P><SPAN>In ADT: right-click your package → New → Other ABAP Repository Object → search for IAM App.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_13-1783311861250.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429571iB294861DB847630E/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_13-1783311861250.png" alt="abhishekpandey_13-1783311861250.png" /></span></P><P><SPAN>Fill in:</SPAN></P><UL><LI><SPAN>Name: /MYNAMESPACE/MYAUDITLOG_APP</SPAN></LI><LI><SPAN>Description: My Audit Log Fiori App</SPAN></LI><LI><SPAN>App Type: External App&nbsp; ← critical (see gotcha below)</SPAN></LI><LI><SPAN>Fiori Launchpad App Descr Item ID: /MYNAMESPACE/MYAUDITLOG_UI5R</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_14-1783311861262.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429570iB921F095E7406FEA/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_14-1783311861262.png" alt="abhishekpandey_14-1783311861262.png" /></span></P><P><SPAN>Select your transport request and click Finish.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_15-1783311861276.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429576iACA840995127909D/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_15-1783311861276.png" alt="abhishekpandey_15-1783311861276.png" /></span></P><P><SPAN>Click Publish Locally.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_16-1783311861284.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429574i23F6AE7701556B3E/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_16-1783311861284.png" alt="abhishekpandey_16-1783311861284.png" /></span></P><P><EM><span class="lia-unicode-emoji" title=":warning:">⚠️</span>&nbsp; Do NOT select 'Fiori App' or 'UI Adaptation App' as the type. The UI5R descriptor created by deploy-to-abap is only compatible with External App. Wrong type gives:<BR />&nbsp;&nbsp;&nbsp; UI5R can only be used in Adaptation UI Apps</EM></P><P><EM><span class="lia-unicode-emoji" title=":memo:">📝</span>&nbsp; Publishing auto-generates _UI5A and _UI5_EXT runtime artifacts. Always reference the IAM App by its logical name /MYNAMESPACE/MYAUDITLOG_APP, not the runtime objects.</EM><SPAN>&nbsp;</SPAN></P><H1 id="toc-hId--881057724">Step 9 — Create and Publish the Business Catalog</H1><P><SPAN>In ADT: right-click your package → New → Other ABAP Repository Object → Business Catalog.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_17-1783311861290.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429573iA398438636CAE860/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_17-1783311861290.png" alt="abhishekpandey_17-1783311861290.png" /></span></P><P><SPAN>Name it (e.g. /MYNAMESPACE/MYAUDITLOG_BC), select your transport, click Finish.</SPAN></P><P><SPAN>In the Apps tab, click Add and search for your logical IAM App name.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_18-1783311861297.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429579i117EAC123AF875E0/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_18-1783311861297.png" alt="abhishekpandey_18-1783311861297.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_19-1783311861304.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429577i2316F072A13EE670/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_19-1783311861304.png" alt="abhishekpandey_19-1783311861304.png" /></span></P><P><SPAN>Save, then click Publish Locally.</SPAN></P><P><EM><span class="lia-unicode-emoji" title=":warning:">⚠️</span>&nbsp; The catalog MUST be Published before it appears in:<BR />&nbsp; • The Maintain Business Roles app's Business Catalog picker<BR />&nbsp; • The Select Catalogs dialog in the Launchpad page editor<BR />If you can see a colleague's catalog but not yours, come back and verify the Publication State.</EM></P><H1 id="toc-hId--1077571229">Step 10 — Assign the Catalog to a Business Role</H1><P><SPAN>In the Fiori Launchpad, open the Maintain Business Roles app:</SPAN></P><UL><LI><SPAN>Open your role → Edit</SPAN></LI><LI><SPAN>Business Catalogs tab → Add → search for /MYNAMESPACE/MYAUDITLOG_BC → OK</SPAN></LI><LI><SPAN>Set Access Category to Unrestricted</SPAN></LI><LI><SPAN>Save</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_20-1783311861309.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429578i27B4DB782F871DF5/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_20-1783311861309.png" alt="abhishekpandey_20-1783311861309.png" /></span></P><P><EM><span class="lia-unicode-emoji" title=":warning:">⚠️</span>&nbsp; The Select Catalogs dialog in the page editor (next step) only lists catalogs already assigned to the role that owns the space. Skip this step and your catalog won't appear in the dialog even if it's published.</EM><SPAN>&nbsp;</SPAN></P><H1 id="toc-hId--1274084734">Step 11 — Add the Tile to a Launchpad Space</H1><P><SPAN>In Maintain Business Roles → your role → Launchpad Spaces tab:</SPAN></P><OL><LI><SPAN>Click Add → Create New Space</SPAN></LI></OL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_21-1783311861316.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429582i60CE0B8D9CE50709/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_21-1783311861316.png" alt="abhishekpandey_21-1783311861316.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_22-1783311861320.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429581i826FF4EB80505786/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_22-1783311861320.png" alt="abhishekpandey_22-1783311861320.png" /></span></P><OL><LI><SPAN>Open the space/page → Edit → click Select Catalogs</SPAN></LI></OL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_23-1783311861322.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429580iD3F2A9F9DB5D5071/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_23-1783311861322.png" alt="abhishekpandey_23-1783311861322.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_24-1783311861326.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429584iA9E8D35ADCB9BA6B/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_24-1783311861326.png" alt="abhishekpandey_24-1783311861326.png" /></span></P><OL><LI><SPAN>Tick your catalog → Select</SPAN></LI><LI><SPAN>On the Manually Selected tab, drag the tile into the page</SPAN></LI><LI><SPAN>Add a Section Title, then Save</SPAN></LI></OL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_25-1783311861331.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429585iE84C93F5AC1665CC/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_25-1783311861331.png" alt="abhishekpandey_25-1783311861331.png" /></span></P><H1 id="toc-hId--1302414548">Step 12 — Access the App</H1><P><SPAN>Hard-refresh the Fiori Launchpad (Ctrl+Shift+R). Navigate to your space.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_26-1783311861334.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429583iEBF4049DEB0C65BB/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_26-1783311861334.png" alt="abhishekpandey_26-1783311861334.png" /></span></P><P><SPAN>Click the tile. The List Report loads. Press Go.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_27-1783311861338.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429586i86698F74469BBA68/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_27-1783311861338.png" alt="abhishekpandey_27-1783311861338.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abhishekpandey_28-1783311861342.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429587i06974FE3A46EA0D5/image-size/medium?v=v2&amp;px=400" role="button" title="abhishekpandey_28-1783311861342.png" alt="abhishekpandey_28-1783311861342.png" /></span></P><H1 id="toc-hId--1498928053">Troubleshooting Cheat Sheet</H1><TABLE><TBODY><TR><TD width="195"><P><STRONG>Symptom</STRONG></P></TD><TD width="195"><P><STRONG>Root Cause</STRONG></P></TD><TD width="195"><P><STRONG>Fix</STRONG></P></TD></TR><TR><TD width="195"><P><STRONG>warn No LADI was created</STRONG></P></TD><TD width="195"><P>No crossNavigation.inbounds in manifest.json</P></TD><TD width="195"><P>Add the crossNavigation block before deploying</P></TD></TR><TR><TD width="195"><P><STRONG>Request does not have transport target 1GT</STRONG></P></TD><TD width="195"><P>Wrong transport type</P></TD><TD width="195"><P>Create a gCTS transport (target 1GT) in ADT</P></TD></TR><TR><TD width="195"><P><STRONG>Customer object WAPA cannot be assigned to package</STRONG></P></TD><TD width="195"><P>Z* app name in a namespaced package</P></TD><TD width="195"><P>Use namespaced app name: /MYNAMESPACE/MYAPP</P></TD></TR><TR><TD width="195"><P><STRONG>UI5R can only be used in Adaptation UI Apps</STRONG></P></TD><TD width="195"><P>Wrong IAM App type</P></TD><TD width="195"><P>Create IAM App with type External App</P></TD></TR><TR><TD width="195"><P><STRONG>My catalog missing from Select Catalogs</STRONG></P></TD><TD width="195"><P>Not published or not assigned to the role</P></TD><TD width="195"><P>Publish in ADT + assign to Business Role + reload</P></TD></TR><TR><TD width="195"><P><STRONG>403 Forbidden on BSP URL</STRONG></P></TD><TD width="195"><P>UCON blocks direct access</P></TD><TD width="195"><P>Access via the Launchpad tile, not the direct URL</P></TD></TR></TBODY></TABLE><P><SPAN>&nbsp;</SPAN></P><H1 id="toc-hId--1695441558">Summary</H1><P><SPAN>The key insight this guide captures is that deployment and launchpad registration are separate concerns that must be completed correctly and in order:</SPAN></P><OL><LI><SPAN>crossNavigation inbound → enables LADI creation on deploy</SPAN></LI><LI><SPAN>gCTS transport → enables deploy-to-abap</SPAN></LI><LI><SPAN>IAM App (External App) → links the LADI to the authorization layer</SPAN></LI><LI><SPAN>Business Catalog (Published) → groups the app for role assignment</SPAN></LI><LI><SPAN>Business Role (catalog assigned) → makes the catalog visible in the page editor</SPAN></LI><LI><SPAN>Launchpad page (tile dragged in) → makes the tile visible to the user</SPAN></LI></OL><P><SPAN>&nbsp;</SPAN><EM>Tested on SAP S/4HANA Cloud Public Edition, SAP Build / BAS (2024/2025 release), <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/2302137">@SAP</a>-ux/ui5-deploy task via specVersion: '4.0' ui5-deploy.yaml.</EM></P> 2026-06-30T11:31:48.957000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/your-voice-on-the-future-of-sap-ui-technology/ba-p/14426910 Your Voice on the Future of SAP UI Technology 2026-07-01T09:00:00.021000+02:00 OliverGraeff https://community.sap.com/t5/user/viewprofilepage/user-id/4124 <P>AI is reshaping how enterprise applications are built, used, and experienced. This raises fundamental questions for SAP's UI technology strategy - and we want to hear from you.</P><BLOCKQUOTE><P><STRONG>TL;DR:</STRONG> We are running a short, anonymous survey on the future of enterprise UI development with SAP. It takes 5 minutes and is open from July 1 to July 19, 2026. <A title="SAPUI5 Strategy Survey" href="https://sapinsights.eu.qualtrics.com/jfe/form/SV_eo2dMNvfXvdoGzk" target="_blank" rel="noopener nofollow noreferrer">Take the survey →</A></P></BLOCKQUOTE><H1 id="toc-hId-1689194500">What are we asking?</H1><P>We have put together a short survey on the future of enterprise UI development with SAP. It covers topics such as:</P><UL><LI>How well SAPUI5 meets your needs today - and in the age of AI</LI><LI>Which UI experiences you expect to dominate enterprise apps in the coming years</LI><LI>Which capabilities matter most for your future development work</LI><LI>Where you see SAP's UI technology strategy heading</LI></UL><H1 id="toc-hId-1492680995"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="308044_AdobeStock-543813474_large_jpg.jpeg" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/425962i9F05616B52CC0306/image-size/medium?v=v2&amp;px=400" role="button" title="308044_AdobeStock-543813474_large_jpg.jpeg" alt="308044_AdobeStock-543813474_large_jpg.jpeg" /></span></H1><H1 id="toc-hId-1296167490">Why your input matters</H1><P>SAP's UI frameworks power thousands of enterprise applications worldwide. Decisions about their future direction affect you — whether you are building new apps, maintaining existing ones, or advising customers on their architecture. Your perspective as a developer, architect, or decision maker in the SAP ecosystem is what we need to make the right calls.<BR /><BR /></P><H1 id="toc-hId-1099653985">Who should take this survey?</H1><P>This survey is for anyone working with SAP UI technologies: SAPUI5 / Fiori developers, UI and UX architects, technical leads, enterprise architects, and IT decision makers at SAP customers and partners.<BR /><BR /></P><H1 id="toc-hId-903140480">Call to Action</H1><P>Make your voice heard by participating in the survey here: <A title="SAP UI Technology Strategy Survey" href="https://sapinsights.eu.qualtrics.com/jfe/form/SV_eo2dMNvfXvdoGzk" target="_self" rel="nofollow noopener noreferrer">SAP UI Technology Strategy Survey</A>. It only takes 5 minutes to complete, and is open from July 1 to July 19, 2026.</P> 2026-07-01T09:00:00.021000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/project-homecontrol-2-part-1-10-years-later-my-whole-house-talks-and-an-ai/ba-p/14433670 Project HomeControl 2 · Part 1 — 10 years later, my whole house talks (and an AI drew the dashboard) 2026-07-05T16:20:13.554000+02:00 Noel_Hendrikx https://community.sap.com/t5/user/viewprofilepage/user-id/185962 <BLOCKQUOTE><P>Sequel to <A href="https://community.sap.com/t5/-/-/m-p/13176811" target="_blank">Project HomeControl (2016)</A>. A short series about wiring up a modern smart home — and, running underneath it, <STRONG>what you can actually do with AI today</STRONG>. Every part ends with one concrete lesson on building <EM>with</EM> an agent. All passwords, tokens and keys are deliberately left out.</P></BLOCKQUOTE><H2 id="toc-hId-1819108670">Ten years ago I soldered my meter cupboard to UI5</H2><P>In March 2016 I wrote my first <EM>Project HomeControl</EM> post. The trigger: the EU was rolling out smart meters, the Netherlands would replace every old meter before 2020, and I had an electric car and wanted to know what it did to my consumption.</P><P>The solution back then was gloriously hacky: an <STRONG>Arduino on a breadboard</STRONG>, a wire in the P1 port of the meter, reading serial number, tariff, current and total usage — shown in a <STRONG>UI5 app</STRONG> I built myself.</P><P>Ten years on, almost everything about that story has changed, and one thing hasn't.</P><P>The thing I soldered? It's now a <STRONG>HomeWizard P1</STRONG> — a €30 plug that does exactly the same, reliably, out of the box. The electric car is a Porsche Taycan. And it's no longer one meter: it's a whole house — solar, a heat pump, chargers, Hue, Sonos, thirteen cameras, blinds.</P><P>What didn't change: the curiosity, and — spoiler for Part 2 — that it ends up being about <STRONG>UI5</STRONG> again.</P><P>This is <EM>Project HomeControl 2</EM>. This part is the boring-but-important half: the setup, and a first dashboard. The fun half (an AI agent that did most of the work) is Part 3.</P><H2 id="toc-hId-1622595165">What's in the house now</H2><P>Everything reports into one hub — <STRONG>Home Assistant</STRONG>, running in Docker on a QNAP NAS. The integrations fall into three neat buckets, and that split matters later:</P><UL><LI><STRONG>Local, real-time</STRONG> — SolarEdge inverter over <STRONG>Modbus TCP</STRONG>, the HomeWizard P1 meter, the Tesla Wall Connector's local API, the heat pump via a CMI gateway.</LI><LI><STRONG>Local, discovered</STRONG> — Philips Hue (79 lamps, 83 scenes), Sonos, the Brel blinds hub.</LI><LI><STRONG>Cloud</STRONG> — Porsche Connect for the Taycan, Ring for the cameras.</LI></UL><P>By the end of the setup, Home Assistant held <STRONG>424 entities</STRONG> across eight integrations. The 2016 version tracked about six values from one meter. Progress.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="architecture-dark.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429406i00558633A3BCEFD6/image-size/large?v=v2&amp;px=999" role="button" title="architecture-dark.png" alt="architecture-dark.png" /></span></P><P><EM>Three kinds of integration feed one hub: local and real-time where it matters (solar, meter, charger), local discovery for lighting and media, cloud where there's no other way in. Home Assistant is the single source of truth the dashboard reads from.</EM></P><H2 id="toc-hId-1426081660">Design before framework: a static dashboard first</H2><P>Here's a habit worth stealing: <STRONG>before</STRONG> reaching for a framework, I build the dashboard as a single static HTML file. No build step, no components, no bindings — just a design I can look at and react to. It's the cheapest possible way to decide <EM>what the thing should feel like</EM> before committing to <EM>how it's built</EM>.</P><P>The result is a small, deliberate design system:</P><UL><LI><STRONG>Design tokens</STRONG> for colour, surfaces and text, with a genuine dark <STRONG>and</STRONG> light theme (not a naive invert — each theme tuned so the accent still reads).</LI><LI><STRONG>Tabular numerals</STRONG> everywhere digits line up, so values don't jitter as they update.</LI><LI><STRONG>Status encoded in form, not just text</STRONG> — a green "Live" chip, an amber "Almost done", a red battery figure — so what needs attention reads at a glance.</LI><LI>A live <STRONG>energy-flow</STRONG>: where the power is going right now, and how much of it the sun covers.</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="dashboard-dark.png" style="width: 827px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429407i3D544C7D8C9C5F22/image-size/large?v=v2&amp;px=999" role="button" title="dashboard-dark.png" alt="dashboard-dark.png" /></span></P><P><EM>The static dashboard (dark theme). Summary tiles up top, then energy, car &amp; charging, and the house — every number is real data from Home Assistant. There's a light theme too, driven by the same design tokens, so one set of components serves both.</EM></P><P>That energy-flow card tells a true story from one afternoon: the Taycan was charging at 11 kW while the sun only produced 3.1 kW, so 8.9 kW came from the grid. Seeing that immediately raised the question that becomes Part 4 — <EM>can I make the car charge on sunshine only?</EM></P><H2 id="toc-hId-1229568155">The part that surprised me</H2><P>I didn't hand-code that dashboard pixel by pixel. I <STRONG>described</STRONG> it — "a clean, dark energy dashboard, tokenised, dark and light, tabular numbers, status chips" — and an AI agent produced the design system, wired it to the live Home Assistant data, and rendered it in both themes. My job shifted from <EM>typing CSS</EM> to <EM>directing taste</EM>: react, refine, decide.</P><P>That's the thread running through this whole series. Not "AI writes code" — we knew that — but that the unit of work moves up a level. You bring the intent and the judgement; the agent brings the hours.</P><BLOCKQUOTE><P><STRONG>What this shows about building with AI</STRONG></P><P>A plain-language description — <EM>"make it feel like a clean energy dashboard"</EM> — became a real, themeable design system wired to live data, in minutes. The lesson isn't speed for its own sake; it's that <STRONG>the cheap-to-throw-away exploration phase gets almost free.</STRONG> You can see three directions before lunch and keep the one with taste. Start vaguer than you think, then direct.</P></BLOCKQUOTE><H2 id="toc-hId-1033054650">Next</H2><P>The dashboard is static HTML today. In <STRONG>Part 2</STRONG>, I turn it into a proper <STRONG>UI5 frontend</STRONG> — and the interesting bit is that I do it with <EM>non-UI5</EM> Web Components, hosted inside a UI5 app. It's the same question I asked in 2016, only reversed, with a much better answer.</P><P><EM>Part 2 — A UI5 frontend with (non-UI5) Web Components — is next.<BR /></EM><EM><A href="https://community.sap.com/t5/technology-blog-posts-by-members/project-homecontrol-2-part-2-a-ui5-frontend-built-from-non-ui5-web/ba-p/14433981" target="_blank">https://community.sap.com/t5/technology-blog-posts-by-members/project-homecontrol-2-part-2-a-ui5-frontend-built-from-non-ui5-web/ba-p/14433981</A></EM></P><P>&nbsp;</P> 2026-07-05T16:20:13.554000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/project-homecontrol-2-part-2-a-ui5-frontend-built-from-non-ui5-web/ba-p/14433981 Project HomeControl 2 · Part 2 — A UI5 frontend, built from non-UI5 Web Components 2026-07-06T08:26:02.639000+02:00 Noel_Hendrikx https://community.sap.com/t5/user/viewprofilepage/user-id/185962 <BLOCKQUOTE><P>Part 2 of <A href="https://community.sap.com/t5/technology-blog-posts-by-members/project-homecontrol-2-part-1-10-years-later-my-whole-house-talks-and-an-ai/ba-p/14433670" target="_blank">Project HomeControl 2</A>. In <A href="https://community.sap.com/t5/technology-blog-posts-by-members/project-homecontrol-2-part-1-10-years-later-my-whole-house-talks-and-an-ai/ba-p/14433670" target="_blank">Part 1</A> I connected the whole house to Home Assistant and drew a dashboard as static HTML. Now I make it a real UI5 frontend — using Web Components that aren't UI5. As with every part, there's one lesson about building <EM>with</EM> an AI agent at the end.</P></BLOCKQUOTE><H2 id="toc-hId-1819111585">The question I couldn't let go</H2><P>In 2016 I built a UI5 app to show my smart meter. In Part 1 of this sequel I had a dashboard again — but as a static HTML file with a bespoke design system: dark tokens, tabular numbers, status chips, a live energy-flow. Lovely to look at, but not a UI5 app.</P><P>Rebuilding that look in stock Fiori controls would mean fighting the theme every step of the way. So I asked the question a lot of UI5 developers quietly wonder about:</P><BLOCKQUOTE><P>Can I keep this exact design — as <STRONG>Web Components</STRONG> — and still host it in a UI5 app, with proper model binding, in an XML view, driven by a JSONModel?</P></BLOCKQUOTE><P>Yes. Here is the whole thing, end to end, including the parts that bit me.</P><H2 id="toc-hId-1622598080">Why UI5, and not React or Astro?</H2><P>Fair question — and an honest answer: if I were building this dashboard greenfield, I probably wouldn't reach for UI5. For a bespoke, consumer-style dashboard, Astro, React or Lit are the natural picks. UI5 is built for enterprise Fiori apps with OData, not dark energy dashboards.</P><P>But that's not the interesting scenario. The interesting one is the reverse:</P><BLOCKQUOTE><P>Nobody <EM>starts</EM> a bespoke dashboard in UI5. Plenty of teams <EM>have</EM> a large UI5/Fiori landscape and want to bring modern, shared Web Components into it — without rewriting.</P></BLOCKQUOTE><P>That's a real, non-niche need: reuse a design-system component across apps, drop in a third-party Web Component (a charting library, a Shoelace control), or share slices of UI between UI5 and non-UI5 apps as micro-frontends. In that world, the dashboard here is just the vehicle — the point is that your UI5 investment can host framework-agnostic components, <EM>with binding</EM>.</P><P>And it's oddly under-documented. SAP even ships "UI5 Web Components" — but that's the other direction (UI5 controls exposed <EM>as</EM> custom elements for React/Angular). Hosting <EM>arbitrary</EM> Web Components <EM>inside</EM> a classic UI5 app, with model binding and the sharp edges, is territory most UI5 content skips. That's exactly why it's worth writing down.</P><H2 id="toc-hId-1426084575">A 30-second recap: what a Web Component is</H2><P>A Web Component is three browser standards working together:</P><PRE>class PeppieTile extends HTMLElement { // 1. a custom element constructor() { super(); this.attachShadow({ mode: "open" }); } // 2. Shadow DOM (scoped CSS) set data(d) { // 3. a PROPERTY that takes an object this.shadowRoot.innerHTML = `&lt;style&gt;…&lt;/style&gt;&lt;div class="tile"&gt;${d.value} ${d.unit}&lt;/div&gt;`; } } customElements.define("peppie-tile", PeppieTile); // tag MUST contain a hyphen</PRE><P>Use it with plain DOM:</P><PRE>const t = document.createElement("peppie-tile"); t.data = { label: "Solar now", value: "3.1", unit: "kW", accent: "amber" }; document.body.append(t);</PRE><P>No framework. It runs standalone, in React, and — the point of this post — in UI5.</P><H2 id="toc-hId-1229571070">The bridge: one UI5 control</H2><BLOCKQUOTE><P><STRONG>Am I not reinventing something SAP already ships?</STRONG> Partly — yes, and it's worth saying up front. UI5 has an official base class, <CODE>sap.ui.core.webc.WebComponent</CODE>, that does this generically: it renders the tag, forwards object values onto the element as <EM>properties</EM> (not attributes), updates them without a full re-render, and maps slots and events. For a fixed set of components, extending it per element is the idiomatic route — and a lot less code than the wrapper below. Nico Schönteich walks through exactly that in <A href="https://community.sap.com/t5/frontend-ui5-sap-fiori-blog-posts/consuming-your-own-or-external-web-components-in-ui5-applications/ba-p/14281839" target="_blank">Consuming your own (or external) Web Components in UI5 Applications</A>. I'm rolling the glue by hand here on purpose: it's the clearest way to <EM>show</EM> why <CODE>.data</CODE> has to be a property, and where the upgrade-order and re-render traps live. Do it once yourself, then reach for the base class. (I also wanted one <EM>generic</EM> control that wraps any tag via a single <CODE>data</CODE> object — a different trade-off, handy for a fast-moving bespoke design system.)</P></BLOCKQUOTE><P>UI5 already renders DOM. A custom element <EM>is</EM> DOM. So the bridge is a single control that renders the tag and hands the element its data:</P><PRE>// peppie/control/WebComponent.js sap.ui.define(["sap/ui/core/Control"], function (Control) { "use strict"; return Control.extend("peppie.control.WebComponent", { metadata: { properties: { tag: { type: "string", defaultValue: "div" }, // which custom element data: { type: "object" } // bind your model object here } }, renderer: { apiVersion: 2, // semantic rendering / DOM patching render: function (rm, oControl) { rm.openStart(oControl.getTag(), oControl); // &lt;peppie-tile id="__control0"&gt; rm.style("display", "block"); rm.openEnd(); rm.close(oControl.getTag()); } }, onAfterRendering: function () { var oDom = this.getDomRef(); if (oDom) { oDom.data = this.getData(); } // property, not attribute (pitfall #1) }, setData: function (vData) { this.setProperty("data", vData, true /* suppress re-render */); var oDom = this.getDomRef(); if (oDom) { oDom.data = vData; } // live update in place (pitfall #3) return this; } }); });</PRE><P><CODE>rm.openStart(tag, oControl)</CODE> is the trick in the renderer: passing the control writes UI5's <CODE>id</CODE> and bookkeeping onto the custom element, so <CODE>apiVersion: 2</CODE> can patch it in place instead of throwing it away on every change.</P><H2 id="toc-hId-1033057565">Wiring it into a real UI5 app</H2><P>Four small pieces, all of them standard UI5.</P><P><STRONG>1. Bootstrap</STRONG> — load the custom elements <EM>before</EM> UI5, register a resource root for your control namespace, and pick a theme:</P><PRE>&lt;!-- index.html --&gt; &lt;script src="webcomponents.js"&gt;&lt;/script&gt; &lt;!-- define elements first (pitfall #4) --&gt; &lt;script id="sap-ui-bootstrap" src="https://sdk.openui5.org/resources/sap-ui-core.js" data-sap-ui-theme="sap_horizon_dark" data-sap-ui-libs="sap.m,sap.ui.layout" data-sap-ui-resourceroots='{"peppie": "./peppie/"}' data-sap-ui-oninit="module:sap/ui/core/ComponentSupport" data-sap-ui-compatVersion="edge" data-sap-ui-async="true"&gt;&lt;/script&gt;</PRE><P><STRONG>2. The model</STRONG> — declared in the manifest, so it's there before the view:</P><PRE>"sap.ui5": { "models": { "": { "type": "sap.ui.model.json.JSONModel", "uri": "model/data.json" } } }</PRE><P><STRONG>3. The view</STRONG> — the namespace <CODE>peppie.control</CODE> resolves to <CODE>peppie/control/WebComponent.js</CODE> via the resource root. Now binding is exactly what you'd hope for:</P><PRE>&lt;mvc:View controllerName="peppie.controller.Main" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:grid="sap.ui.layout.cssgrid" xmlns:wc="peppie.control"&gt; &lt;VBox class="peppieWrap"&gt; &lt;!-- a native UI5 control, bound to the same model --&gt; &lt;MessageStrip text="{/suggest}" type="{/suggestType}" showIcon="true"/&gt; &lt;!-- tiles: one control, four bindings, in a real CSS grid --&gt; &lt;grid:CSSGrid gridTemplateColumns="repeat(auto-fit, minmax(220px, 1fr))" gridGap="0.6rem"&gt; &lt;wc:WebComponent tag="peppie-tile" data="{/tiles/0}"/&gt; &lt;wc:WebComponent tag="peppie-tile" data="{/tiles/1}"/&gt; &lt;wc:WebComponent tag="peppie-tile" data="{/tiles/2}"/&gt; &lt;wc:WebComponent tag="peppie-tile" data="{/tiles/3}"/&gt; &lt;/grid:CSSGrid&gt; &lt;!-- cards: two columns that each stack tightly --&gt; &lt;HBox wrap="Wrap" class="peppieCols"&gt; &lt;VBox class="peppieCol"&gt; &lt;wc:WebComponent tag="peppie-flow" data="{/flow}"/&gt; &lt;wc:WebComponent tag="peppie-metric-card" data="{/p1}"/&gt; &lt;!-- … taycan, ring --&gt; &lt;/VBox&gt; &lt;VBox class="peppieCol"&gt; &lt;wc:WebComponent tag="peppie-metric-card" data="{/solar}"/&gt; &lt;wc:WebComponent tag="peppie-metric-card" data="{/wp}"/&gt; &lt;!-- … tesla, hue --&gt; &lt;/VBox&gt; &lt;/HBox&gt; &lt;/VBox&gt; &lt;/mvc:View&gt;</PRE><P>One generic control wraps <EM>every</EM> element in the library. Ordinary UI5 layout does the arranging — a <CODE>sap.ui.layout.cssgrid.CSSGrid</CODE> for the tile row and two <CODE>sap.m.VBox</CODE> columns in a wrapping <CODE>HBox</CODE> for the cards (a float-based <CODE>sap.ui.layout.Grid</CODE> left ragged gaps between cards of different heights, so I switched). And a native <CODE>sap.m.MessageStrip</CODE> sits at the top, bound to the same model — proof that stock UI5 controls and Web Components mix freely in one view.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Noel_Hendrikx_0-1783674029061.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/431437i63F1F07E36C2327F/image-size/medium?v=v2&amp;px=400" role="button" title="Noel_Hendrikx_0-1783674029061.png" alt="Noel_Hendrikx_0-1783674029061.png" /></span></P><P>&nbsp;</P><P><EM>Running OpenUI5 app (<CODE>sap_horizon_dark</CODE>). The toolbar and its buttons are stock <CODE>sap.m</CODE> controls; everything below is Web Components bound to a <CODE>JSONModel</CODE>. Two worlds, one app.</EM></P><H2 id="toc-hId-836544060">The pitfalls (this is where the time goes)</H2><P><STRONG>1. Property, not attribute.</STRONG> The big one. UI5 binding, and most snippets you'll copy, write to <EM>attributes</EM>. Web Components take rich values — objects, arrays — as <STRONG>properties</STRONG>. <CODE>setAttribute("data", {...})</CODE> stringifies to <CODE>[object Object]</CODE>. So in <CODE>onAfterRendering</CODE> you do <CODE>element.data = this.getData()</CODE>. That one line is why this works at all.</P><P><STRONG>2. Bind objects, update them immutably.</STRONG> The wrapper binds a whole object (<CODE>data="{/tiles/0}"</CODE>), and <CODE>JSONModel</CODE> compares by reference. Mutate that object in place and the binding never fires. The tempting fix — <CODE>model.refresh(true)</CODE> — works, but it's a smell: it re-evaluates <EM>every</EM> binding in the app to paper over mutated state. The clean fix is to treat model data as immutable and replace the node with a <STRONG>new</STRONG> object, so the reference changes and exactly that binding updates:</P><PRE>onSimulate: function () { var m = this._oModel, oTile = m.getProperty("/tiles/0"); // new object → new reference → the {/tiles/0} binding fires on its own m.setProperty("/tiles/0", Object.assign({}, oTile, { value: solar.toFixed(1) })); }</PRE><P>No <CODE>refresh(true)</CODE>, no shared state mutated in place — each changed node fires its own binding.</P><P><STRONG>3. Re-render vs. live update.</STRONG> UI5 controls invalidate and re-render. You do <EM>not</EM> want to tear down and rebuild a custom element on every model tick. That's why <CODE>setData</CODE> uses <CODE>suppressInvalidate = true</CODE> and updates the live element directly. In the demo, "Simulate reading" changes the model and every tile, bar and card updates without a single re-render.</P><P><STRONG>4. Load order and the upgrade trap.</STRONG> Define your custom elements <EM>before</EM> the control first renders. If UI5 renders <CODE>&lt;peppie-tile&gt;</CODE> before the browser knows the class, it's an inert unknown element; worse, setting <CODE>.data</CODE> on a not-yet-upgraded element creates an own-property that <EM>shadows</EM> the class setter after upgrade. Loading <CODE>webcomponents.js</CODE> in <CODE>&lt;head&gt;</CODE> before the bootstrap avoids the whole class of "it works on refresh but not first load" bugs.</P><P><STRONG>5. Layout and height.</STRONG> Web Components are <CODE>display: inline</CODE> by default — render them <CODE>display: block</CODE> (the control does this). And don't drop them into a <CODE>sap.m.Page</CODE> that has no height: the Page positions its content absolutely, so a zero-height container renders a blank screen. I lost a minute to exactly that; a <CODE>VBox</CODE>/<CODE>Grid</CODE> that flows with content, or a container with a real height, fixes it.</P><P><STRONG>6. Getting events back into UI5.</STRONG> Binding flows data <EM>in</EM>; for interaction <EM>out</EM> (a light toggled, a card clicked) let the element dispatch a <CODE>CustomEvent</CODE> and re-fire it as a UI5 event:</P><PRE>metadata: { events: { action: {} } }, onAfterRendering: function () { var oDom = this.getDomRef(); oDom.data = this.getData(); if (!this._wired) { // wire once oDom.addEventListener("peppie-action", (e) =&gt; this.fireAction({ detail: e.detail })); this._wired = true; } }</PRE><P>Now <CODE>&lt;wc:WebComponent action=".onAction"/&gt;</CODE> works like any UI5 event handler.</P><H2 id="toc-hId-640030555">The honest alternative</H2><P>If you want to stay fully inside the SAP toolbox, you can: <CODE>sap.f.Card</CODE> for the layout, <CODE>sap.suite.ui.microchart</CODE> for the little charts. You get binding for free and a Fiori-native look. For <EM>this</EM> deliberately non-Fiori design, the Web Component route was less fighting and more reuse — the same components run outside UI5 too. Pick per project; just know both exist.</P><H2 id="toc-hId-443517050">Try it</H2><P>The full runnable demo is in <A href="https://community.sap.com/../ui5-demo/" target="_blank" rel="noopener nofollow noreferrer"><CODE>ui5-demo/</CODE></A> — OpenUI5 from CDN, one wrapper control, a JSONModel, no build step. <CODE>npx serve ui5-demo</CODE>, open the URL, hit <STRONG>Simulate reading</STRONG> and <STRONG>Theme</STRONG>.</P><BLOCKQUOTE><P><STRONG>What this shows about building with AI</STRONG></P><P>I described the goal — "wrap these custom elements as a data-bound UI5 control" — and the agent produced the control and got the app running fast, naming most pitfalls as we hit them: the attribute-vs-property gotcha, the <CODE>sap.m.Page</CODE> height trap, the upgrade-order bug. But not everything it produced was senior-grade. Its first fix for the object-binding problem was <CODE>model.refresh(true)</CODE> — and my own instinct said <EM>mutating model state and force-refreshing is not done</EM>. We replaced it with immutable <CODE>setProperty</CODE> updates. That's the real division of labour: the agent brings speed and breadth; you bring the judgement that turns <EM>working</EM> into <EM>clean</EM>. Treat its output as a strong first draft, not the final commit.</P></BLOCKQUOTE><H2 id="toc-hId-247003545">Your turn — a give-away</H2><P>This demo is JavaScript. It should be TypeScript: the model typed, the control's managed properties typed, the Web Components' <CODE>data</CODE> typed end to end. That's a real, multi-file refactor across the UI5 Tooling — exactly the kind of well-scoped job an agent is good at and you review.</P><P>I ran this prompt myself before publishing it, and it turned out to be the whole series' thesis in one artifact. Left vague, the agent produces something that <EM>looks</EM> converted but doesn't build: a broken generator dependency (<CODE>@ui5/ts-interface-generator</CODE> pulls in an ESM-only <CODE>yargs</CODE> and won't run), the OpenUI5 framework config the CDN app never had, a <CODE>setData</CODE> signature that clashes with the generated interface, a type-only module that gets stripped to a non-module. So the prompt below is deliberately specific — exact versions, and the three real blockers baked in. That specificity is the difference between <EM>"the agent tried"</EM> and <EM>the build is green</EM>.</P><P>Run it in the <CODE>ui5-demo/</CODE> folder and hold it to the acceptance criteria at the end.</P><PRE>You are a senior SAPUI5 + TypeScript engineer. Convert the JavaScript UI5 app in this folder to TypeScript — fully typed, strict:true — and make it BUILD and SERVE with the UI5 Tooling. Do NOT change runtime behaviour; the app must render and behave exactly as it does now. Work in small, reviewable steps: layout &amp; framework first, then toolchain, then types, then module conversion. 0. Environment. @ui5/cli 4 wants Node &gt;= 22.12; older Node only prints an EBADENGINE warning and still works. 1. Layout &amp; framework — the app currently loads OpenUI5 from a CDN; that must change, because the UI5 Tooling serves the framework itself. - Move all app sources under webapp/: index.html, Component.ts, manifest.json, controller/, view/, control/, model/, and the web-components module. - Add ui5.yaml that pins OpenUI5 locally (no CDN): specVersion: "3.0" metadata: { name: peppie } type: application framework: name: OpenUI5 version: "1.120.46" libraries: - name: sap.ui.core - name: sap.m - name: sap.ui.layout - name: themelib_sap_horizon # required for sap_horizon_dark builder: customTasks: - name: ui5-tooling-transpile-task afterTask: replaceVersion server: customMiddleware: - name: ui5-tooling-transpile-middleware afterMiddleware: compression - In webapp/index.html change the bootstrap src from the CDN URL to "resources/sap-ui-core.js" (served by the Tooling). Keep data-sap-ui-theme="sap_horizon_dark", the ComponentSupport oninit, and data-sap-ui-resourceroots='{"peppie": "./"}'. Ensure manifest.json sap.app.id = "peppie" so the resourceroots, the /** @namespace peppie.* */ values, and the manifest id all agree. 2. Toolchain &amp; exact, known-good versions. devDependencies: "@openui5/types": "1.120.46" "@ui5/cli": "^4.0.57" "@ui5/ts-interface-generator": "^0.11.1" "typescript": "^5.9.3" "ui5-tooling-transpile": "^3.11.3" overrides (MANDATORY — see step 4): "@ui5/ts-interface-generator": { "yargs": "^17.7.2" } scripts: "ts:check": "tsc --noEmit" "build": "ui5 build --clean-dest" "start": "ui5 serve --open index.html" tsconfig.json: strict:true, target/module "ES2022", moduleResolution "node", lib ["ES2022","DOM","DOM.Iterable"], skipLibCheck:true, rootDir "webapp", paths { "peppie/*": ["webapp/*"] }, types ["@openui5/types"], include ["webapp/**/*.ts","webapp/**/*.d.ts"]. 3. Convert each sap.ui.define([...], function(){}) into an ES-module TypeScript class with import / export default, UI5 TS style, keeping the namespace via /** @namespace peppie.control */. 4. The custom control — mind the interface generator. - Keep the metadata block; type the renderer (RenderManager), onAfterRendering, and the setData override. - Run @ui5/ts-interface-generator to emit WebComponent.gen.d.ts (typed getTag/setTag/ getData/setData plus $WebComponentSettings), and make sure tsconfig includes it. - BLOCKER: @ui5/ts-interface-generator@0.11.1 require()s yargs but resolves the ESM-only yargs@18 -&gt; ERR_REQUIRE_ESM, so the generator won't run at all. The overrides pin in step 2 (yargs ^17.7.2) fixes it. Install that BEFORE running the generator. - Two things the generator will NOT do for you: a) It prints three constructor(...) overload lines and says "copy &amp; paste manually" — paste them into the class (referencing $WebComponentSettings). It never edits your source. b) It also generates setData(data: object | null): this. Your hand-written setData MUST use the exact same `object | null` parameter type, or declaration-merging fails with an overload-compatibility error. 5. Types. Add webapp/model/types.ts with interfaces TileData, FlowRow, FlowData, MetricItem, MetricCardData, EvCardData, RingCam, RingCardData, LightGroup, LightsCardData, and a root DashboardModel including suggest:string and suggestType:"Success"|"Warning"|"Error"|"Information"|"None". Type the JSONModel and the controller's onSimulate against DashboardModel. 6. The web components — make it a real UI5 module. - Write webcomponents.ts with a typed `set data(d: …)` per element and augment HTMLElementTagNameMap so createElement("peppie-tile") and the control's getDomRef() are typed. - IMPORTANT: add at least one runtime `export` (e.g. export { PeppieTile, PeppieFlow, … }). A module with only TYPE exports gets type-stripped to a plain script and is NOT wrapped as a sap.ui.define AMD module — the build then warns it "requires eval". A real runtime export makes it a proper module. - Have Component.ts do a side-effect import "./webcomponents"; so the custom elements are defined before the controls render. - In the wrapper, type the element so assigning .data is checked. A single, commented `getDomRef() as HTMLElement &amp; { data: unknown }` cast is fine — do not use `any`. Acceptance criteria (all must pass): - `npm run ts:check` -&gt; zero errors with strict:true. - `npm run build` (ui5 build) -&gt; green, no warnings, produces a runnable build. - `npm start` (ui5 serve) -&gt; renders identically: four tiles, two-column cards, the MessageStrip, and "Simulate reading" flipping the strip between Warning and Success. - The generic WebComponent wrapper still wraps any element through one `data` property; no `any` anywhere (the one commented getDomRef() cast excepted). When done, show me the final tsconfig.json, ui5.yaml, package.json, the typed WebComponent, and model/types.ts.</PRE><P>Validated: this produced <CODE>tsc --noEmit</CODE> with zero strict errors, a green <CODE>ui5 build</CODE>, and a <CODE>ui5 serve</CODE> render pixel-identical to the JavaScript original. First draft from the agent; the specificity — and the final commit — from you.</P><H2 id="toc-hId-50490040">Next</H2><P>The house is connected (Part 1) and it has a real UI5 frontend (Part 2). In <STRONG>Part 3</STRONG> I pull back the curtain on how most of this actually got built: an AI agent that read a login captcha, hacked a solar inverter, and then deleted its own hack when the clean route opened up.</P><P><EM>Part 3 — I let an AI agent connect the whole house — is next.</EM></P> 2026-07-06T08:26:02.639000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/code-connect-2026-final-info-before-we-kick-off/ba-p/14434082 Code Connect 2026: Final Info Before We Kick Off! 2026-07-06T11:02:20.615000+02:00 BirgitS https://community.sap.com/t5/user/viewprofilepage/user-id/41902 <P><SPAN>Code Connect 2026 is just around the corner! From <STRONG>July 13–16, 2026</STRONG>, the SAP developer community will gather in <STRONG>St. Leon-Rot, Germany</STRONG> – onsite and online.</SPAN></P><P><SPAN>Here’s everything you need to know before we get started.</SPAN></P><P>&nbsp;</P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Code Connect 2026" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429665i8288C16ACE3EC08A/image-size/large?v=v2&amp;px=999" role="button" title="BirgitS_0-1783324399225.png" alt="BirgitS_0-1783324399225.png" /></span></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-1819132728">We Are Fully Booked!</H2><P><SPAN>All onsite tickets are <STRONG>fully booked</STRONG> - thank you for the amazing interest!</SPAN></P><P><SPAN>If you couldn’t secure a ticket:</SPAN></P><UL><LI><SPAN>Join the <STRONG>waiting lists on </STRONG><A href="https://code-connect.dev/" target="_blank" rel="noopener nofollow noreferrer"><STRONG>Code Connect 2026</STRONG></A> in case seats become available.</SPAN></LI><LI><SPAN>Follow selected sessions via <STRONG>live stream</STRONG> or <STRONG>Microsoft Teams</STRONG> link.</SPAN></LI></UL><P><SPAN>Even if you're not onsite, you can still be part of Code Connect. Selected sessions will be streamed live, and recordings of some sessions will be available afterwards. Check back soon for details.</SPAN></P><P>&nbsp;</P><TABLE><TBODY><TR><TD width="200px"><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="UI5con" style="width: 76px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429685i9BABBF665F151967/image-size/small?v=v2&amp;px=200" role="button" title="UI5conSmall.png" alt="UI5conSmall.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P><STRONG><SPAN>UI5con (July 14)</SPAN></STRONG></P></TD><TD width="465.047px"><UL><LI><SPAN>Main stage sessions will be live streamed on <A href="https://www.youtube.com/live/CMPudw4scSE" target="_blank" rel="noopener nofollow noreferrer">YouTube</A>. </SPAN></LI><LI><SPAN>Check the <A href="https://openui5.org/ui5con/program.html" target="_blank" rel="noopener nofollow noreferrer">agenda</A> to see which sessions are streamed.</SPAN></LI></UL></TD></TR><TR><TD width="200px"><P><STRONG><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="reCAP" style="width: 76px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429686i805B9E02FF474F62/image-size/small?v=v2&amp;px=200" role="button" title="reCAPSmall.png" alt="reCAPSmall.png" /></span></STRONG></P><P>&nbsp;</P><P>&nbsp;</P><P><STRONG>re&gt;≡CAP (July 15)</STRONG></P></TD><TD width="465.047px"><UL><LI><SPAN>Main stage sessions will be <A href="https://broadcast.sap.com/go/reCAP" target="_blank" rel="noopener noreferrer">broadcasted</A>. </SPAN></LI><LI><SPAN>Some sidetracks (W1/W2) can be attended via <A href="https://teams.microsoft.com/meet/347149883889697?p=ZkR4erzGjpPRtZ6zJh" target="_blank" rel="noopener nofollow noreferrer">Microsoft Teams link</A>. </SPAN></LI><LI><SPAN>Check the <A href="https://recap-conf.dev/program.html" target="_blank" rel="noopener nofollow noreferrer">agenda</A> to see which sessions are broadcasted or available via Microsoft Teams link.</SPAN></LI></UL></TD></TR><TR><TD width="200px"><P><STRONG><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="HANATechCon" style="width: 76px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429687iDED26458B98CF5BC/image-size/small?v=v2&amp;px=200" role="button" title="HANATechConSmall.png" alt="HANATechConSmall.png" /></span></SPAN></STRONG></P><P>&nbsp;</P><P>&nbsp;</P><P><STRONG><SPAN>HANA Tech Con (July 16)</SPAN></STRONG></P></TD><TD width="465.047px"><UL><LI><SPAN>The links will be announced on the event day.</SPAN></LI></UL></TD></TR></TBODY></TABLE><P>&nbsp;</P><H2 id="toc-hId-1622619223">The Agendas Are Live</H2><P><SPAN>The full program is already available - time to plan your schedule!</SPAN></P><P><SPAN>Check-In opens at 8.00 AM every day. Please bring the QR code with you that you can find in your ticket.</SPAN></P><P><STRONG><SPAN>July 13 – Code Jams &amp; Community Meetup</SPAN></STRONG></P><UL><LI><SPAN>Hands-on <STRONG>Code Jams</STRONG>:</SPAN></LI><UL><LI><STRONG><SPAN>OpenUI5</SPAN></STRONG></LI><LI><STRONG><SPAN>CAP</SPAN></STRONG></LI><LI><STRONG><SPAN>AI Agents</SPAN></STRONG></LI></UL><LI><SPAN>Informal <STRONG>pre-event meetup</STRONG> (no registration needed):<BR />Ihle Besen, Höfe am Sträßel 3, 69231 Rauenberg<BR />5:00 PM CEST</SPAN></LI></UL><P><STRONG><SPAN>July 14–16 – Main Conference Days</SPAN></STRONG></P><P><SPAN>Explore all agendas:</SPAN></P><UL><LI><STRONG><SPAN>July 14:</SPAN></STRONG><SPAN> <A href="https://openui5.org/ui5con/program.html" target="_blank" rel="noopener nofollow noreferrer">UI5con</A></SPAN></LI><LI><STRONG><SPAN>July 15:</SPAN></STRONG><SPAN> <A href="https://recap-conf.dev/program.html" target="_blank" rel="noopener nofollow noreferrer">re&gt;≡CAP</A> </SPAN></LI><LI><STRONG><SPAN>July 16:</SPAN></STRONG><SPAN> <A href="https://hanatech.community/" target="_blank" rel="noopener nofollow noreferrer">HANA Tech Con</A></SPAN></LI></UL><P><SPAN>On <STRONG>July 14</STRONG> also the <STRONG>HANA AI CodeJam</STRONG> takes place. </SPAN></P><P><SPAN>&nbsp;</SPAN></P><H2 id="toc-hId-1426105718">Location</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Location" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429667i854A5327BE35386B/image-size/large?v=v2&amp;px=999" role="button" title="BirgitS_4-1783324399247.png" alt="BirgitS_4-1783324399247.png" /></span></P><P><SPAN>Code Connect takes place at:</SPAN></P><P><SPAN>SAP-Allee 27<BR />68789 St. Leon-Rot, Germany</SPAN></P><P><SPAN>Find detailed information on <STRONG>directions, parking, and public transport</STRONG> <A href="https://code-connect.dev/location.html" target="_blank" rel="noopener nofollow noreferrer">here.</A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-1229592213"><SPAN>Final Tips</SPAN></H2><UL><LI>Bookmark your favorite sessions in advance</LI><LI><SPAN>Arrive early for popular sessions (rooms fill quickly!)</SPAN></LI><LI>Bring your laptop for Code Jams and hands-on sessions/ workshops</LI><LI><SPAN>Don’t miss the networking moments - some of the best conversations happen in between sessions</SPAN></LI><LI><SPAN>Follow the agendas closely for last-minute updates</SPAN></LI></UL><P><SPAN>&nbsp;</SPAN></P><H2 id="toc-hId-1033078708">See You Soon!</H2><P><SPAN>We’re looking forward to an inspiring week of learning, sharing, and connecting.</SPAN></P><P><STRONG><SPAN>See you at Code Connect 2026!</SPAN></STRONG></P> 2026-07-06T11:02:20.615000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-development-newsletter-issue-41-july-2026/ba-p/14434985 SAP Fiori Development newsletter issue #41 (July 2026) 2026-07-07T15:50:00.014000+02:00 PeterSpielvogel https://community.sap.com/t5/user/viewprofilepage/user-id/543 <P>Summer is here and it’s getting hot. I’m not just talking about the weather. AI is heating up both for the companies that create the large language models and everyone trying to keep up with the latest innovations. All while managing their token costs. SAP is embracing AI for our internal application development and the tools we provide customers and partners for building their own applications.</P><P>Sapphire produced several major announcements around our development tools, the biggest being Joule Studio 2.0. <A href="https://news.sap.com/2026/05/new-joule-studio-enterprise-scale-agentic-development/" target="_blank" rel="noopener noreferrer">Joule Studio 2.0</A> is SAP’s AI-first development environment for building custom AI agents, apps, and workflows. Powered by the SAP Business AI Platform.</P><P>As the development landscape shifts, we want to know what’s important to you. We are interested in how well SAPUI5 meets your needs today, which UI experiences you expect to dominate enterprise apps in the coming years, and which capabilities matter most for your future AI-powered development work. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/your-voice-on-the-future-of-sap-ui-technology/ba-p/14426910" target="_blank">Please take this 5-minute survey</A>.</P><P>If you want to join other developers to discuss various traditional and AI-powered Fiori development-related topics from the comfort of your office or home, I encourage you to join our monthly SAP Fiori development roundtable. <A href="https://eur03.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdocs.google.com%2Fforms%2Fd%2F1ZqIX3zzGBOOIqehmlIRz_HhpQEhqunkLSzN1gnaOwP8%2Fedit&amp;data=05%7C02%7Cpeter.spielvogel%40sap.com%7C1744854ba1f649092b6b08dd30c5a09e%7C42f7676cf455423c82f6dc2d99791af7%7C0%7C0%7C638720347573866887%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&amp;sdata=xgQxtmNy3e97xOOJC304tGHhsgIVLId5KJSRNyshxq0%3D&amp;reserved=0" target="_blank" rel="noopener nofollow noreferrer">Register online</A> or email me for an invitation.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="SAP_Fiori_Dev_Newsletter_41.png" style="width: 654px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/430186i2AF088A936F7FD78/image-size/large?v=v2&amp;px=999" role="button" title="SAP_Fiori_Dev_Newsletter_41.png" alt="SAP_Fiori_Dev_Newsletter_41.png" /></span></P><H2 id="toc-hId-1819141380">Development News</H2><P><STRONG>Optimizing SAP Fiori Elements for Mobile Phone Users</STRONG></P><P>While SAP Fiori elements applications are responsive, they adjust controls based on screen size and device type, it does not provide an optimized experience for users. We recently reimagined the SAP Fiori elements mobile experience to include native mobile feel, intuitive navigation, and smarter mobile-optimized layouts. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/optimizing-sap-fiori-elements-for-mobile-phone-users/ba-p/14414227" target="_blank">Read more</A>.</P><P><STRONG>Develop RAP-Based SAP Fiori Applications in Minutes with SAP Fiori Tools AI Project Accelerator</STRONG></P><P>Developing enterprise SAP Fiori applications using the ABAP RESTful Application Programming Model (RAP) provides a standardized way to build modern, cloud-ready applications. These applications are typically developed in environments such as SAP BTP ABAP Environment and SAP S/4HANA Cloud Public Edition (version 2602 or higher). The Project Accelerator now supports RAP-based projects. It is available in SAP Business Application Studio under an SAP Build Code subscription. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/develop-rap-based-sap-fiori-applications-in-minutes-with-sap-fiori-tools-ai/ba-p/14349988" target="_blank">Learn more</A>.</P><P><STRONG>TypeScript 6 and 7 – What UI5 TypeScript Developers Need to Know in 2026</STRONG></P><P>TypeScript is going through its biggest transition in years — and if you build UI5 applications with TypeScript, here we have all the details you need to know. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/typescript-6-and-7-what-ui5-typescript-developers-need-to-know-in-2026/ba-p/14393526" target="_blank">Read about it in this blog post</A>.</P><P><STRONG>UI5 Plugins for Coding Agents: Now Supporting GitHub Copilot</STRONG></P><P>The UI5 Plugins for Coding Agents — previously known as UI5 Plugins for Claude — are now also available for GitHub Copilot. The plugins bring up-to-date SAPUI5 and OpenUI5 knowledge directly into your AI-assisted development workflow, whether you work with Claude Code or GitHub Copilot. Version 0.1.3 also introduces three new skills covering UI Integration Cards, OPA5 integration tests, and OpenUI5 tables. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/ui5-plugins-for-coding-agents-now-supporting-github-copilot/ba-p/14427058" target="_blank">Read more</A>.</P><P><STRONG>Introducing the UI5 Modernization Plugin</STRONG></P><P>Keeping your SAPUI5 or OpenUI5 app up to date just got easier. The new UI5 Modernization Plugin automates the most time-consuming parts of app modernization: replacing deprecated APIs, eliminating global namespaces, updating manifest.json and Component.js, and getting your app ready for Content Security Policy compliance. The plugin is available as a Claude Code plugin and in the GitHub Copilot marketplace. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/introducing-the-ui5-modernization-plugin-modernize-your-openui5-sapui5-app/ba-p/14428191" target="_blank">Read the full walkthrough</A>.</P><P><STRONG>&nbsp;</STRONG><STRONG>What's next for UI technology in the age of AI?</STRONG></P><P>We're running a short survey to collect input from developers, architects, and UI technology enthusiasts in the SAP ecosystem — and we want to hear from you. Topics include how well SAPUI5 meets your needs today, how AI tools work with your current UI stack, and which capabilities matter most for your future development work. Shape the SAPUI5 road map by answering few questions in just 5 minutes, open July 1–19. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/your-voice-on-the-future-of-sap-ui-technology/ba-p/14426910" target="_blank">Take the survey</A>.</P><P><STRONG>Joule Studio 2.0 announced at Sapphire</STRONG></P><P>SAP announced its latest development environment for building apps, agents, and workflows. <A href="https://www.sap.com/products/artificial-intelligence/joule-studio.html" target="_blank" rel="noopener noreferrer">Read how it changes how you build apps</A>.</P><P><STRONG>SAP Joule for Developers, ABAP AI capabilities including agentic capabilities is now available!</STRONG></P><P>ABAP AI in SAP Joule for Developers include agentic capabilities and is now offered as a service across SAP BTP ABAP Environment, SAP S/4HANA Cloud Public Edition, and SAP S/4HANA Cloud Private Edition for releases 2021 and higher. Joule speaks ABAP, and now it speaks more than ever before. <A href="https://community.sap.com/t5/artificial-intelligence-blogs-posts/sap-joule-for-developers-abap-ai-capabilities-including-agentic/ba-p/14417633" target="_blank">See the details</A>.</P><P><STRONG>Upgrading your SAPUI5 Rich Text Editor: Beyond TinyMCE version 4</STRONG></P><P>The Rich Text Editor is an SAPUI5 control for data input and editing. It is used to enter rich text with different styles, colors, and formatting. The Rich Text Editor allows users to format text and add different types of elements, such as images and hyperlinks. he SAPUI5 team further plans to permanently remove TinyMCE version 4 from the SAPUI5 code later in 2026. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/upgrading-your-sapui5-rich-text-editor-beyond-tinymce-version-4/ba-p/14393982" target="_blank">Learn how to adjust your code to avoid any problems</A>.</P><H2 id="toc-hId-1622627875">Events</H2><P><STRONG>The future of UX is here. Are you ready for an AI -powered world?</STRONG></P><P>At Sapphire 2026 I had the opportunity to speak with many customers and partners about their reaction to the way we are moving with our UX strategy. Generally, people are excited about the direction we are going and having more automation to free up time for deeper thought and analysis, but they are concerned about trusting AI to do things that might have significant business impact. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sapphire-2026-the-future-of-ux-is-here-are-you-ready-for-an-ai-powered/ba-p/14396240#M190408" target="_blank">Read my full analysis</A>.</P><P><STRONG>SAP Connect Day for UX in Silicon Valley focused on AI strategy and tactics for implementation</STRONG></P><P>SAP Connect Day for UX in Silicon Valley connected customers with SAP experts and selected partners to discuss how AI will improve the lives of SAP Cloud ERP users. <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-connect-day-for-ux-in-silicon-valley-focused-on-ai-strategy-and-tactics/ba-p/14421614" target="_blank">Read about highlights and where you can learn more about the topics we discussed</A>.</P><H2 id="toc-hId-1311650375" id="toc-hId-1426114370"><STRONG>Back issues from the past year</STRONG></H2><P><A class="" href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-development-newsletter-issue-40-may-2026/ba-p/14378597" target="_blank">SAP Fiori Development Newsletter issue #40 (May 2026)</A></P><P><A class="" href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-issue-39-march-2026/ba-p/14334703" target="_blank">SAP Fiori Development newsletter&nbsp;</A><A class="" href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-issue-39-march-2026/ba-p/14334703" target="_blank">March 2026</A><A class="" href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-issue-39-march-2026/ba-p/14334703" target="_blank"><SPAN>&nbsp;</SPAN>(</A><A class="" href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-issue-39-march-2026/ba-p/14334703" target="_blank">issue #39</A><A class="" href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-issue-39-march-2026/ba-p/14334703" target="_blank">)</A></P><P><A href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-january-2026-issue-38/ba-p/14304753" target="_blank">SAP Fiori development newsletter January 2026 (issue #38)</A></P><P><A href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-november-2025-issue-37/ba-p/14268465" target="_blank">SAP Fiori development newsletter November 2025 (issue #37)</A></P><P><A href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-september2025-issue-36/ba-p/14212556" target="_blank">SAP Fiori development newsletter September 2025 (issue #36)</A></P><P><A href="https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/sap-fiori-development-newsletter-july-2025-issue-35/ba-p/14139032" target="_blank">SAP Fiori development newsletter July 2025 (issue #35)</A></P> 2026-07-07T15:50:00.014000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/adding-a-custom-column-in-fiori-elements-list-report-object-page/ba-p/14414702 Adding a Custom Column in Fiori Elements (List Report + Object Page) 2026-07-09T20:58:27.605000+02:00 rmaxence https://community.sap.com/t5/user/viewprofilepage/user-id/2083226 <H2 id="toc-hId-1817292165"><STRONG>Introduction&nbsp;</STRONG></H2><P><SPAN>The purpose of this blog post is to specify the elements to add in order to set up this <STRONG>custom column</STRONG>, and more specifically to <STRONG>understand</STRONG> the path to <STRONG>configure in the manifest</STRONG> when you want to modify a table, because using this extension in an association facet of an<STRONG> Object Page</STRONG> can be more complicated to understand.<BR />To do so, </SPAN><STRONG>two elements must be implemented</STRONG><SPAN>: first, the column definition through an XML fragment, and second, its registration in the </SPAN><STRONG>manifest file</STRONG><SPAN>.<BR /></SPAN></P><P><SPAN>I had to use this extension because I was asked to customize the data display by applying CSS styles, such as changing the color depending on the displayed value.<BR /></SPAN><SPAN>This blog post also aims to specify when to use this extension and why it is preferable not to use it, along with an example of a regression I experienced during its addition.</SPAN></P><P><SPAN>This column can be added either to a </SPAN><STRONG>List Report</STRONG><SPAN> page or to an </SPAN><STRONG>Object Page</STRONG><SPAN>. Each table type has its own extension name, and the implementation differs slightly depending on the table type.</SPAN></P><H3 id="toc-hId-1749861379"><STRONG>&nbsp; &nbsp; &nbsp; &nbsp;Why use this extension as a last resort?</STRONG></H3><P><SPAN>In this blog post, we will look at how to add a custom column. However, other approaches can often meet the requirement. This extension should be considered </SPAN><STRONG>only when no standard alternative is available</STRONG><SPAN>, for the following reasons:</SPAN></P><UL><LI><STRONG>Fiori Elements Flexibility: </STRONG><SPAN>Some requirements can be fulfilled through manifest customization or OData annotations without writing any JavaScript or XML code.</SPAN></LI><LI><STRONG>Maintainability: </STRONG><SPAN>View extensions are custom code that must be maintained whenever the Fiori Elements framework is updated, whereas standard annotations are automatically handled by SAP.</SPAN></LI><LI><STRONG>Regression Risk:</STRONG><SPAN> A poorly implemented extension can interfere with native table features such as sorting, filtering, Excel export, and P13n personalization.</SPAN></LI></UL><P><STRONG>Example of a regression I encountered with this extension:</STRONG></P><P>While developing my XML, I needed to add a specific width to my column. However, by defining this width in pixels (px), I caused a regression in the native behavior, which uses rem units. For example, the display variants were altered depending on the screen size, because rem units rely on a relative value, unlike pixels which impose a fixed size.</P><H3 id="toc-hId-1553347874"><STRONG>&nbsp; &nbsp; &nbsp; Table Types and Associated Extensions</STRONG></H3><P><SPAN>Fiori Elements supports several table types. Each has its own extension name that must be declared in the manifest.<BR /></SPAN></P><TABLE><TBODY><TR><TD><P><STRONG>Table Type&nbsp;</STRONG></P></TD><TD><P><STRONG>Extension Name&nbsp;</STRONG></P></TD><TD><P><STRONG>Description</STRONG></P></TD></TR><TR><TD><P><SPAN>ResponsiveTable</SPAN></P></TD><TD><P><SPAN>ResponsiveTableColumnsExtension</SPAN></P><P><SPAN>ResponsiveTableCellsExtension</SPAN></P></TD><TD><P><SPAN>Responsive table adapted for mobile and desktop (default)</SPAN></P></TD></TR><TR><TD><P><SPAN>GridTable</SPAN></P></TD><TD><P><SPAN>GridTableColumnsExtension</SPAN></P></TD><TD><P><SPAN>Table with fixed columns, suitable for large datasets</SPAN></P></TD></TR><TR><TD><P><SPAN>TreeTable</SPAN></P></TD><TD><P><SPAN>TreeTableColumnsExtension</SPAN></P></TD><TD><P><SPAN>Hierarchical (tree-like) table</SPAN></P></TD></TR></TBODY></TABLE><P><SPAN><STRONG>Important:</STRONG> For a ResponsiveTable, <STRONG>two extensions</STRONG> are required: one for the column header and one for the cell content.<BR /></SPAN></P><H2 id="toc-hId-1227751650"><STRONG>How to Implement the Column?</STRONG></H2><H3 id="toc-hId-1160320864"><STRONG>&nbsp; &nbsp; &nbsp;XML Extension</STRONG></H3><P><SPAN>To add this column, you need to</SPAN><STRONG> define a fragment for the view extension</STRONG><SPAN>. Create a file in the webapp/ext/fragments/ folder to add any desired column.</SPAN></P><P><SPAN>In this fragment, you </SPAN><STRONG>define the desired properties of the column</STRONG><SPAN> (position, displayed element, etc.). You can also add P13N parameters, such as sorting, to enable users to sort the column.</SPAN></P><P><STRONG>Example XML extension:</STRONG><SPAN><BR /></SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_0-1782144877705.png" style="width: 724px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/424843i2C71D454B2694AC0/image-dimensions/724x207?v=v2" width="724" height="207" role="button" title="rmaxence_0-1782144877705.png" alt="rmaxence_0-1782144877705.png" /></span></P><P>&nbsp;</P><H3 id="toc-hId-963807359"><STRONG>&nbsp; &nbsp; &nbsp; Manifest Extension</STRONG></H3><P><SPAN>In the </SPAN><STRONG>manifest.json file</STRONG><SPAN>, declare the </SPAN><STRONG>view extension</STRONG><SPAN> and the application parameters. Create an extension corresponding to the table type on the </SPAN><STRONG>desired entity</STRONG><SPAN>, then specify the fragment name and type.</SPAN></P><P><STRONG>Example Manifest:</STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_5-1781000840422.png" style="width: 660px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/419549i12D3D0E8A0F017F4/image-dimensions/660x236?v=v2" width="660" height="236" role="button" title="rmaxence_5-1781000840422.png" alt="rmaxence_5-1781000840422.png" /></span></P><H3 id="toc-hId-767293854"><STRONG>Result :</STRONG></H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_6-1781000840423.png" style="width: 624px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/419551i3186258994142D60/image-dimensions/624x422?v=v2" width="624" height="422" role="button" title="rmaxence_6-1781000840423.png" alt="rmaxence_6-1781000840423.png" /></span></P><P><SPAN>After adding the XML extension and declaring it in the manifest.json, a new column appears in the table. In the example, the column customizes data display by applying CSS styles, such as changing colors based on displayed values.</SPAN></P><P><SPAN>The document also highlights a date-coloring scenario where different colors indicate whether a deadline is distant, approaching, or overdue. In such cases, a view extension is generally unnecessary because OData Criticality annotations can provide the same functionality in a standard, Fiori Elements–compliant way.&nbsp;</SPAN></P><P><STRONG>Therefore, this standard approach should be preferred whenever they satisfy the requirement.</STRONG></P><H2 id="toc-hId-441697630"><STRONG>Adding a Column to an Object Page</STRONG></H2><H3 id="toc-hId-374266844"><STRONG>&nbsp; &nbsp; &nbsp; Differences Compared to a List Report</STRONG></H3><P><SPAN>An Object Page can contain </SPAN><STRONG>multiple sections</STRONG><SPAN>, each with its own table. The main </SPAN><STRONG>differences </STRONG><SPAN>are:</SPAN></P><TABLE><TBODY><TR><TD><P><STRONG>Aspect</STRONG></P></TD><TD><P><STRONG>List Report</STRONG></P></TD><TD><P><STRONG>Object Page</STRONG></P></TD></TR><TR><TD><P><SPAN>Target View&nbsp;</SPAN></P></TD><TD><P><SPAN>ListReport.view.ListReport</SPAN></P></TD><TD><P><SPAN>ObjectPage.view.Details</SPAN></P></TD></TR><TR><TD><P><SPAN>Extension key</SPAN></P></TD><TD><P><SPAN>TypeTable + '|' + EntitySet</SPAN></P></TD><TD><P><SPAN>TypeTable + '|' + EntitySet + FacetID</SPAN></P></TD></TR><TR><TD><P><SPAN>Number of Tables&nbsp;</SPAN></P></TD><TD><P><SPAN>One main table</SPAN></P></TD><TD><P><SPAN>Multiple tables (one per section)</SPAN></P></TD></TR><TR><TD><P><SPAN>Configuration Scope&nbsp;</SPAN></P></TD><TD><P><SPAN>Entity-wide</SPAN></P></TD><TD><P><SPAN>Specific to each section/facet</SPAN></P></TD></TR></TBODY></TABLE><H3 id="toc-hId-177753339"><STRONG>&nbsp; &nbsp; &nbsp; Example in an Object Page</STRONG></H3><P><SPAN>In this example, a custom column is added to a </SPAN><STRONG>Responsive Table</STRONG><SPAN> within the Object Page of the </SPAN><STRONG>Category</STRONG><SPAN> entity.&nbsp;</SPAN></P><P><SPAN>The custom column contains an </SPAN><STRONG>Order</STRONG><SPAN> button that is displayed only when the product stock is below 30 units. This behavior relies on business logic that cannot be implemented using standard Fiori Elements annotations.&nbsp;</SPAN></P><P><SPAN>As with the</SPAN><STRONG> Grid Table </STRONG><SPAN>example, an extension fragment is created for the column definition:</SPAN><SPAN><BR /></SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_7-1781000840423.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/419552iA3C68D32821C65B9/image-size/medium?v=v2&amp;px=400" role="button" title="rmaxence_7-1781000840423.png" alt="rmaxence_7-1781000840423.png" /></span></P><P><SPAN>However, because this is a </SPAN><STRONG>Responsive Table</STRONG><SPAN>, a second extension is required to define </SPAN><SPAN>the column header</SPAN><SPAN>:</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_8-1781000840424.png" style="width: 445px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/419553i558CB9AA5508B063/image-dimensions/445x147?v=v2" width="445" height="147" role="button" title="rmaxence_8-1781000840424.png" alt="rmaxence_8-1781000840424.png" /></span></P><P><SPAN>Since the extension is used within an </SPAN><STRONG>Object Page</STRONG><SPAN>, the manifest.json configuration differs slightly from a List Report. You must target the</SPAN><STRONG> ObjectPage.view.Details</STRONG><SPAN> view and </SPAN><STRONG>specify the corresponding facet</STRONG><SPAN> in the extension key to indicate which table should receive the custom column.</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_9-1781000840424.png" style="width: 478px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/419554i4F50BA3CF61D1767/image-dimensions/478x205?v=v2" width="478" height="205" role="button" title="rmaxence_9-1781000840424.png" alt="rmaxence_9-1781000840424.png" /></span></P><P>The point I spent time on due to a lack of understanding: '<STRONG>TypeTable</STRONG>' corresponds to the <STRONG>CDS of our table</STRONG> and <STRONG>not the CDS of the Object Page</STRONG>. In our example, 'Products' is a CDS association (added in a 'ProductsFacet' facet) of our application, which is based on the 'Category' CDS.</P><P><STRONG><BR />Result:&nbsp;</STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rmaxence_10-1781000840424.png" style="width: 437px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/419555i39EF5B497AB6EB16/image-dimensions/437x415?v=v2" width="437" height="415" role="button" title="rmaxence_10-1781000840424.png" alt="rmaxence_10-1781000840424.png" /></span></P><P>&nbsp;</P><H2 id="toc-hId-199411472"><STRONG>Purpose of the Extension</STRONG></H2><H3 id="toc-hId--290505040"><STRONG>&nbsp; &nbsp; &nbsp;When Should You Use This Extension?</STRONG></H3><P><SPAN>This extension is relevant in the following scenarios:</SPAN></P><UL><LI><STRONG>Complex conditional display:</STRONG><SPAN> color, icon, or text based on multi-field business logic that cannot be expressed through annotations.</SPAN></LI><LI><STRONG>Non-standard UI5 component: </STRONG><SPAN>action button, link, image, slider, or any component not natively supported by UI.DataField.</SPAN></LI><LI><STRONG>External data integration:</STRONG><SPAN> displaying data coming from another JSON model or a separate REST API call.&nbsp;</SPAN></LI><LI><STRONG>Frontend-side calculation:</STRONG><SPAN> combining or calculating values using a JavaScript formatter.&nbsp;</SPAN></LI></UL><H3 id="toc-hId--487018545"><STRONG>&nbsp; &nbsp; &nbsp;Best Practices&nbsp;</STRONG></H3><UL><LI><STRONG>Always prefix IDs:</STRONG><SPAN> avoid conflicts with IDs generated by Fiori Elements.&nbsp;</SPAN></LI><LI><STRONG>Use i18n:</STRONG><SPAN> never hardcode header texts; always use entries from the i18n.properties file.</SPAN></LI><LI><STRONG>Keep JavaScript out of fragments: </STRONG><SPAN>complex logic should be implemented in a dedicated formatter.js file, not inline in the XML.</SPAN></LI><LI><STRONG>Test P13n (personalization):</STRONG><SPAN> ensure that the custom column behaves correctly with sorting, filtering, and show/hide functionalities.</SPAN></LI></UL><P>&nbsp;</P> 2026-07-09T20:58:27.605000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/getting-started-with-github-in-sap-business-application-studio-for-sap-ui5/ba-p/14431984 Getting Started with GitHub in SAP Business Application Studio for SAP UI5 Development 2026-07-09T21:48:03.370000+02:00 Alhas-Parveen_29 https://community.sap.com/t5/user/viewprofilepage/user-id/2291373 <H4 id="toc-hId-2077217444">Overview:</H4><P>SAP Business Application Studio (BAS) trial accounts expire every 90 days, making it essential to safeguard SAP UI5 projects using a reliable version control system. GitHub not only helps prevent project loss but also introduces the collaboration practices widely used in professional software development.</P><P>This blog covers:</P><UL><LI>Connecting SAP Business Application Studio (BAS) with GitHub</LI><LI>Step-by-step process to push and manage SAP UI5 projects using GitHub.</LI></UL><P>The intent is to provide a simple and practical guide for SAP UI5 learners and developers to protect their projects, understand Git and GitHub fundamentals, and adopt industry-standard version control practices.</P><H4 id="toc-hId-1880703939">Introduction:</H4><P>When I started learning SAP UI5, I was using a trial account on SAP Business Application Studio (BAS).&nbsp;</P><P>Everything was going great — until I hit my first big obstacle.&nbsp;</P><UL><LI>Every 90 days, the trial account expires. And when it does, everything is gone. All my projects. All my code. Vanished.&nbsp;</LI></UL><P>My only option back then was to manually download each project as a zip file to my desktop before the account reset. Then when the new trial started, I had to upload everything back again.&nbsp;</P><P>It was painful. It was repetitive. And one time, I forgot — and lost a project I had worked on for weeks.&nbsp;</P><P>That's when I discovered <STRONG>GitHub — and it completely changed how I work.</STRONG></P><P class="">I started learning how to:</P><UL><LI>Upload my SAP UI5 projects from SAP Business Application Studio (BAS) to GitHub.</LI><LI>Access them anytime, from anywhere, even after my BAS trial account resets.</LI><LI>Share my projects easily with teammates and collaborate more effectively.</LI><LI>No more downloading ZIP files. No more worrying about the 90-day deadline. My code was safely stored in the cloud, always accessible, and backed by version control.</LI></UL><H4 id="toc-hId-1684190434">What Is GitHub and Why Do We Need It?</H4><P>The easiest way to think about GitHub is this: it's like Google Docs for source code. Instead of collaborating on documents, developers collaborate on code while Git keeps track of every change.</P><P>GitHub is a cloud-based platform built on top of Git, a distributed version control system. It enables developers to store their source code in the cloud, track changes over time, and collaborate with others while maintaining a complete history of every modification.</P><P>In addition to version control, GitHub provides features such as repositories, branches, pull requests, and code reviews, making it easier for individuals and teams to manage software projects efficiently.</P><P>Although GitHub is widely used for open-source development, it is equally important in enterprise environments. Development teams use it to collaborate on projects, review code, manage changes, and maintain high-quality software throughout the development lifecycle.</P><P>GitHub is not just a tool for beginners. It is an industry-standard platform used by organizations of all sizes to support real-world software development and team collaboration.</P><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><P class="">In real-world software projects, developers rarely work alone. Front-end developers, backend developers, testers, and other team members often work on the same application simultaneously. Without a version control platform like GitHub, collaboration can quickly become difficult, with developers accidentally overwriting each other's changes and having no clear way to track who changed what or revert to previous versions.</P><P>GitHub solves this challenge through features such as <STRONG>branching. </STRONG>Think of the main project as a tree trunk, where each developer creates their own branch to work independently on a feature, enhancement, or bug fix. Once the work is complete, the changes are reviewed through a Pull Request (PR) and then merged back into the main branch.</P><P>This approach enables multiple developers to work in parallel without interfering with one another, maintains a complete history of code changes, and ensures that every update is reviewed before becoming part of the main project. As a result, GitHub has become an industry-standard platform for version control and team collaboration in professional software development.</P><DIV class=""><P class=""><FONT face="arial black,avant garde">A Typical Team Workflow<BR /><FONT face="arial,helvetica,sans-serif">A typical GitHub workflow in a development team looks like this:</FONT></FONT></P><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class="">&nbsp;</DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV><pre class="lia-code-sample language-abap"><code>main branch → Stable, production-ready code ├── developer-1 → Working on the Login feature ├── developer-2 → Working on the Dashboard feature └── developer-3 → Fixing a bug in the Search feature</code></pre><P class="">Each developer works on their own branch without affecting the work of others. Once a feature or bug fix is complete, the developer creates a <STRONG>Pull Request (PR) </STRONG>to request that their changes be reviewed before they are merged into the main branch.</P><P>A senior developer or technical lead reviews the code, provides feedback if any changes are required, and approves the Pull Request once the code meets the project's quality standards. After approval, the changes are merged into the main branch.</P><P>This workflow helps ensure:</P><UL><LI>Developers can work independently without interfering with one another.</LI><LI>Every code change is reviewed before being merged.</LI></UL><P>A complete history of changes is maintained, making it easy to track who changed what and when.</P><P>The main branch remains stable and ready for deployment.</P><P><SPAN>&nbsp;Now, let's walk through the step-by-step process of connecting SAP Business Application Studio (BAS) to GitHub and uploading your SAP UI5 project.<SPAN><BR /><BR /><STRONG>Step- 1:&nbsp;Create a GitHub Account</STRONG></SPAN></SPAN></P><UL><LI>Sign up for a GitHub account if you don't already have one.</LI></UL><P><STRONG>Step 2: Initialize the Git Repository</STRONG></P><UL><LI><SPAN>Open your SAP UI5 project in <STRONG><SPAN>SAP Business Application Studio (BAS)<SPAN>.</SPAN></SPAN></STRONG></SPAN></LI><LI><SPAN>Open the <STRONG><SPAN>Terminal<SPAN> by navigating to <STRONG><SPAN>Terminal → New Terminal<SPAN><SPAN>.<BR /><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AlhasParveen_29_0-1782993306641.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428512i1AD275F547C51821/image-size/medium?v=v2&amp;px=400" role="button" title="AlhasParveen_29_0-1782993306641.png" alt="AlhasParveen_29_0-1782993306641.png" /></span><BR /></SPAN></SPAN></SPAN></STRONG></SPAN></SPAN></STRONG></SPAN>Run the following command to initialize a Git repository with <CODE>main</CODE> as the default branch:</LI></UL><pre class="lia-code-sample language-abap"><code>git init -b main​</code></pre><P><SPAN>This command initializes a new Git repository in your project directory and creates the default branch named </SPAN><STRONG><SPAN>main</SPAN></STRONG><SPAN>. Git will now start tracking changes made to your project.</SPAN></P><P><STRONG>Step 3: Stage All Project Files</STRONG></P><UL><LI>After initializing the Git repository, stage all the project files by running the following command in the terminal:</LI></UL><pre class="lia-code-sample language-abap"><code>git add .​</code></pre><UL><LI>The <STRONG>git add .</STRONG> command stages all the files and folders in the current project directory. Staging tells Git which changes should be included in the next commit.</LI><LI>Once the files are staged, Git is ready to create a snapshot of your project using a commit.</LI><LI><STRONG>Note:</STRONG> The . (dot) represents the current directory, so<STRONG> git add .</STRONG> stages all files within your project.</LI></UL><P><STRONG>Step 4: Commit the Changes</STRONG></P><UL><LI>Once all the project files have been staged, create your first commit by running the following command:</LI></UL><pre class="lia-code-sample language-abap"><code>git commit -m "Vendor Details"​</code></pre><UL><LI>Creates a commit with the message <STRONG>"Vendor Details"</STRONG>, which serves as a snapshot of your project at its current state.</LI><LI><STRONG>Verify the Commit</STRONG></LI><LI>To confirm that the commit was created successfully, run the following command:</LI><LI><CODE>git <SPAN class="">log</SPAN></CODE></LI><LI>Displays the commit history of the repository, including the commit ID, author, date, and commit message.</LI><LI>If the commit was successful, you should see an output similar to:</LI></UL><pre class="lia-code-sample language-abap"><code>commit 3f2c9b8e6d4f1a2b... Author: &lt;Your Name&gt; &lt;your-email@example.com&gt; Date: Sat Jun 6 10:30:15 2026 Vendor Details​</code></pre><UL><LI>This confirms that your project has been committed successfully and Git is now tracking this version of your code.</LI></UL><P><STRONG>Step 5: Configure Git (First-Time Setup)</STRONG></P><UL><LI>If you're using Git for the first time, configure your username and email address using the following commands:</LI></UL><pre class="lia-code-sample language-abap"><code>git config --global user.name "&lt;github_username&gt;" git config --global user.email "&lt;github_email&gt;"</code></pre><UL><LI>Configures your Git username and email address. These details are associated with every commit you create.</LI></UL><P><STRONG>Step 6: Create a GitHub Repository</STRONG></P><UL><LI>Sign in to your GitHub account.</LI><LI>Click New Repository.</LI><LI>Enter a repository name (for example, Vendor_Details_App).</LI><LI>Click Create repository.<BR /><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AlhasParveen_29_2-1783333219992.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429786iFDDFB23171307401/image-size/medium?v=v2&amp;px=400" role="button" title="AlhasParveen_29_2-1783333219992.png" alt="AlhasParveen_29_2-1783333219992.png" /></span></LI></UL><P><STRONG>Step 7: Connect the Local Repository to GitHub</STRONG></P><UL><LI>After creating the repository, copy its HTTPS URL and run the following command in the BAS terminal:</LI></UL><pre class="lia-code-sample language-abap"><code>git remote add origin https://github.com/sap-honey/Vendor_Details_App.git</code></pre><UL><LI>Connects your local Git repository to the remote GitHub repository.</LI></UL><P><STRONG>Step 8: Push the Project to GitHub</STRONG></P><UL><LI>Push your local commits to the remote GitHub repository using the following command:</LI></UL><pre class="lia-code-sample language-abap"><code>git push -u origin main​</code></pre><UL><LI>Uploads your local <CODE>main</CODE> branch to the remote GitHub repository. The <CODE>-u</CODE> option sets the upstream branch, so future pushes can be performed simply by running:</LI></UL><pre class="lia-code-sample language-abap"><code>git push​</code></pre><UL><LI><P>When you run the <CODE>git push</CODE> command for the first time, GitHub prompts you to authenticate your account.</P></LI><LI><P>Click Copy and Open GitHub.</P></LI><LI>This copies the verification code and opens the GitHub device activation page in your browser.</LI><LI>GitHub automatically opens the device activation page. If prompted, sign in to your GitHub account.</LI><LI>Paste the verification code and click <STRONG>Continue</STRONG>, then click <STRONG>Authorize</STRONG> to grant SAP Business Application Studio permission to access your GitHub repository.</LI><LI>After the authorization is complete, return to SAP Business Application Studio. The <CODE>git push</CODE> command will finish automatically.</LI><LI>Open your GitHub account and navigate to the repository you created.</LI><LI>Refresh the repository page. You should now see all your SAP UI5 project files successfully uploaded.<BR /><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AlhasParveen_29_0-1783335102908.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429827i4A7C33A0B5587BA5/image-size/medium?v=v2&amp;px=400" role="button" title="AlhasParveen_29_0-1783335102908.png" alt="AlhasParveen_29_0-1783335102908.png" /></span><P>This confirms that your local SAP UI5 project has been successfully pushed to GitHub. From now on, you only need to commit your changes and run:</P></LI></UL><pre class="lia-code-sample language-abap"><code>git push​</code></pre><P class="">GitHub is an essential tool for every SAP UI5 developer. By connecting SAP Business Application Studio with GitHub, you can securely store your projects, track changes, and continue your work even after a BAS trial reset.</P><P>I hope this blog helps you get started with GitHub and makes managing your SAP UI5 projects easier and more efficient. Happy coding!</P><P>&nbsp;</P><P><SPAN>&nbsp;</SPAN></P> 2026-07-09T21:48:03.370000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/how-to-add-a-custom-formatter-in-sapui5-view-vs-controller-approach/ba-p/14431823 How to Add a Custom Formatter in SAPUI5: View vs. Controller Approach 2026-07-13T08:22:22.969000+02:00 chetanbogali https://community.sap.com/t5/user/viewprofilepage/user-id/2081645 <P class="">Hi Everyone,</P><H2 id="toc-hId-1819050858">Introduction</H2><P class="">When building SAPUI5 applications, we often bind data directly from a model to the UI. While simple property binding works well for displaying raw values, there are many scenarios where the displayed value needs additional formatting before it reaches the user. For example, you might want to combine multiple fields into a single sentence, display semantic colors based on a value, or convert technical data into a more user-friendly format.</P><P>This is where custom formatters become useful. They allow you to keep formatting logic separate from your XML views, making your application easier to read, maintain, and reuse.</P><P class="">In this blog, we'll explore:</P><UL><LI>What a custom formatter is and why you should use one.</LI><LI>Two different ways to integrate an external formatter into your SAPUI5 application:<UL><LI>Using <CODE>core:require</CODE> directly in the XML View.</LI><LI>Importing the formatter through the Controller.</LI></UL></LI><LI>A practical example that demonstrates both approaches side by side using real code and application output.</LI><LI>When to use each approach, so you can choose the one that best fits your application's structure and development style.</LI></UL><H2 id="toc-hId-1622537353">Project Context</H2><P><SPAN>While practicing SAPUI5, I wanted to organize my formatting logic outside the XML View instead of writing formatting code directly in bindings. During this exercise, I discovered that SAPUI5 provides more than one way to use external formatter files.</SPAN></P><P>I experimented with both approaches, loading the formatter directly in the XML View using <CODE>core:require</CODE> and importing it through the controller. Since both approaches produce the same result but differ in how the formatter is referenced, I thought it would be useful to compare them side by side with a simple practical example.</P><H2 id="toc-hId-1426023848">What is a Custom Formatter, and Why Use One</H2><P class="">A custom formatter is a JavaScript function that transforms model data before it is displayed in the UI. It allows you to present data in a more meaningful or user-friendly format without modifying the original data in the model. Since a formatter only affects the displayed value, it supports one-way binding and cannot be used for two-way (editable) bindings.</P><P>Some common use cases include:</P><UL><LI>Converting technical values into user-friendly text.</LI><LI>Combining multiple model properties into a single display value.</LI><LI>Applying semantic states such as Success, Warning, or Error based on a value.</LI><LI>Formatting dates, currency, or performing simple calculations before displaying data.</LI></UL><P class="">In the following sections, we'll implement these concepts with a practical SAPUI5 example using reusable external formatter files.</P><H2 id="toc-hId-1229510343">Project Setup&nbsp;and Folder Structure</H2><P class="">Here's the folder structure used for this example, a simple table of users with their age and salary, where we'll apply one formatter for each column:</P><pre class="lia-code-sample language-bash"><code>project1/ ← SAPUI5 application └── webapp/ ├── controller/ ← Controller logic │ ├── App.controller.js │ └── View1.controller.js ← attaches AgeFormatter (controller approach) ├── formatter/ ← Reusable external formatter files │ ├── AgeFormatter.js ← used via controller │ └── SalaryFormatter.js ← used via core:require in the view ├── model/ │ ├── models.js │ └── users.json ← sample data ├── view/ │ ├── App.view.xml │ └── View1.view.xml ← table using both formatter approaches ├── Component.js ├── index.html └── manifest.json ← model &amp; routing configuration</code></pre><P class="">For this example, I created two separate formatter files to demonstrate both approaches independently. One formatter is loaded directly in the XML View using <CODE>core:require</CODE>, while the other is imported through the controller. This makes it easier to understand how each approach works without mixing the implementations.</P><UL class=""><LI><CODE>AgeFormatter.js</CODE>&nbsp;– Combines a user's name and age into a readable sentence.</LI><LI><CODE>SalaryFormatter.js</CODE>&nbsp;– Maps a salary value to an appropriate semantic state.</LI></UL><H4 id="toc-hId-1291162276">Sample Data (<CODE>users.json</CODE>)</H4><P><SPAN>The application uses a simple JSON model containing user information. The </SPAN><CODE>age</CODE><SPAN> and </SPAN><CODE>salary</CODE><SPAN> properties are used by the formatter functions in the following examples.</SPAN></P><pre class="lia-code-sample language-json"><code>{ "users": [ { "id": 1, "name": "Monkey D. Luffy", "age": 16, "salary": 22000 }, { "id": 2, "name": "Roronoa Zoro", "age": 21, "salary": 64000 }, { "id": 3, "name": "Nami", "age": 20, "salary": 2400 }, { "id": 4, "name": "Usopp", "age": 19, "salary": 2500 }, { "id": 5, "name": "Sanji", "age": 21, "salary": 7000 }, { "id": 6, "name": "Tony Tony Chopper", "age": 17, "salary": 6800 }, { "id": 7, "name": "Nico Robin", "age": 30, "salary": 9200 }, { "id": 8, "name": "Franky", "age": 36, "salary": 88000 }, { "id": 9, "name": "Brook", "age": 90, "salary": 33000 }, { "id": 10, "name": "Jinbe", "age": 46, "salary": 440000 } ] }</code></pre><H4 id="toc-hId-1094648771">Registering the Model (<CODE>manifest.json</CODE>)</H4><pre class="lia-code-sample language-json"><code>"oModel": { "type": "sap.ui.model.json.JSONModel", "uri": "model/users.json" }</code></pre><P>The JSON model is registered in <CODE>manifest.json</CODE>,&nbsp;making it available throughout the application using the model name <CODE>oModel</CODE>.</P><H2 id="toc-hId-639969828">Approach 1: Using <CODE>core:require</CODE> in the View</H2><P class="">This approach loads the formatter module directly in the XML view using the <CODE>core:require</CODE> attribute, and gives it an alias you can reference in any binding inside that view. No changes are needed in the controller.</P><P class=""><STRONG>SalaryFormatter.js</STRONG></P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([], function(){ "use strict"; return { /* * This formatter is required directly in the VIEW via core:require * (see View1.view.xml) and referenced as 'Salary.salaryState'. */ salaryState: function(salary){ if (salary &gt;= 10000) { return "Success"; // green color } else if (salary &gt;= 5000) { return "Warning"; // orange color } else { return "Error"; // red color } } } })</code></pre><P>While implementing this example, I kept the salary-related formatting in a dedicated formatter file instead of writing conditional logic directly in the XML View. This keeps the binding expression clean and makes the formatting logic reusable.</P><P><STRONG>View1.view.xml</STRONG></P><pre class="lia-code-sample language-markup"><code>&lt;mvc:View controllerName="project1.controller.View1" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:core="sap.ui.core" core:require="{ Salary: 'project1/formatter/SalaryFormatter' }"&gt; &lt;!-- Approach 1 (view-based): core:require loads the SalaryFormatter module and makes it available under the alias "Salary" for use anywhere inside this view. --&gt; &lt;Page id="page" title="{i18n&gt;title}"&gt; &lt;Table items="{oModel&gt;/users}"&gt; &lt;columns&gt; &lt;Column&gt;&lt;Text text="ID" /&gt;&lt;/Column&gt; &lt;Column&gt;&lt;Text text="Name" /&gt;&lt;/Column&gt; &lt;Column&gt;&lt;Text text="Age" /&gt;&lt;/Column&gt; &lt;Column&gt;&lt;Text text="Salary" /&gt;&lt;/Column&gt; &lt;Column&gt;&lt;Text text="Age Classification" /&gt;&lt;/Column&gt; &lt;/columns&gt; &lt;items&gt; &lt;ColumnListItem&gt; &lt;Text text="{oModel&gt;id}" /&gt; &lt;Text text="{oModel&gt;name}" /&gt; &lt;Text text="{oModel&gt;age}" /&gt; &lt;!-- "Salary" here refers to the alias declared in core:require above --&gt; &lt;ObjectStatus text="{oModel&gt;salary}" state="{path:'oModel&gt;salary', formatter:'Salary.salaryState'}" /&gt; &lt;/ColumnListItem&gt; &lt;/items&gt; &lt;/Table&gt; &lt;/Page&gt; &lt;/mvc:View&gt;</code></pre><P class="">One thing I found convenient about this approach is that everything required by the formatter stays inside the XML View. Since the formatter is only used in this view, there was no need to modify the controller.&nbsp;This also keeps the formatter dependency close to where it is actually used.</P><H2 id="toc-hId-443456323">Approach 2: Attaching the Formatter in the Controller</H2><P class="">While exploring formatter implementations, I also tried importing the formatter through the controller. In this approach, the formatter is loaded as a controller dependency and then exposed to the XML View through&nbsp;the <CODE>formatter</CODE> property.</P><P class=""><STRONG>AgeFormatter.js</STRONG></P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([], function () { "use strict"; return { // Classifies a person's age into a category and returns a readable sentence. ageClassification: function (sName, sAge) { var nAge = Number(sAge); var sCategory; if (nAge &lt; 13) { sCategory = "Child"; } else if (nAge &lt; 18) { sCategory = "Youth"; } else if (nAge &lt; 60) { sCategory = "Adult"; } else { sCategory = "Senior Citizen"; } // Pick "a" or "an" based on whether the category starts with a vowel sound var sArticle = /^[aeiou]/i.test(sCategory) ? "an" : "a"; return `${sName} is ${sArticle} ${sCategory}`; } }; });</code></pre><P class=""><STRONG>View1.controller.js</STRONG></P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([ "sap/ui/core/mvc/Controller", // Import the formatter module here and attach it to the controller "project1/formatter/AgeFormatter" ], (Controller, AgeFormatter) =&gt; { "use strict"; return Controller.extend("project1.controller.View1", { // Attaching the module as "formatter" is what makes // ".formatter.ageClassification" resolvable from the view formatter: AgeFormatter, onInit() { } }); });</code></pre><DIV class=""><STRONG>View1.view.xml (relevant part)</STRONG></DIV><pre class="lia-code-sample language-markup"><code>&lt;!-- The leading dot means "look this up on the controller instance", i.e. this.formatter.ageClassification --&gt; &lt;Text text="{parts:[{path:'oModel&gt;name'}, {path:'oModel&gt;age'}], formatter:'.formatter.ageClassification'}" /&gt;</code></pre><DIV class=""><P>Only the binding expression changes in the XML View. The remaining XML structure stays exactly the same as in the previous approach, so only the relevant binding is shown below.</P><H2 id="toc-hId-246942818"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot_2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428464i1CAFC534B29FD9F0/image-size/large?v=v2&amp;px=999" role="button" title="Screenshot_2.png" alt="Screenshot_2.png" /></span></H2><P><STRONG><FONT color="#FF0000">Figure 1:&nbsp;XML View showing where each formatter approach is used</FONT></STRONG></P><P><FONT color="#333333">After implementing both approaches, I noticed that the application behavior and output remain exactly the same. The only difference is how the formatter module is made available to the XML View. This observation helped me understand that the choice is primarily about code organization rather than functionality.</FONT></P><H2 id="toc-hId-50429313">Putting Both Together</H2><P class="">Here's the same app with both formatters wired up, salary status via the view-based approach, age classification via the controller-based approach, running against a simple JSON model:</P><P class=""><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot_1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428451iB9B309CCBA00D66E/image-size/large?v=v2&amp;px=999" role="button" title="Screenshot_1.png" alt="Screenshot_1.png" /></span></P><P class=""><STRONG><FONT color="#FF0000">Figure 2: Output showing salary status and age classification for all users</FONT></STRONG></P><P class="">Both approaches produce correct, working output. The difference is purely in <EM>where</EM> the dependency is declared and <EM>how</EM> it's referenced.</P><H2 id="toc-hId-201170165">View vs. Controller Approach</H2><P class="">Both approaches ultimately achieve the same goal. They allow you to keep formatting logic outside the XML View and produce the same output. After implementing both in the same application, I noticed that the primary difference isn't what they do, but where the formatter dependency is managed.</P><P>With the <STRONG>View approach (<CODE>core:require</CODE>)</STRONG>, the formatter is imported directly into the XML View. This makes the view self-contained because anyone reading the XML can immediately see which formatter modules it depends on. I found this approach straightforward when the formatter is only used within a single view.</P><P>With the <STRONG>Controller approach</STRONG>, the formatter is imported as a controller dependency and then exposed to the XML View through the <CODE>formatter</CODE> property. This keeps all module imports together inside the controller, which can make the XML slightly cleaner. I found this approach useful because the controller becomes the single place responsible for managing its dependencies.</P><P class="">Another thing I observed is that the formatter implementation itself doesn't change. The same formatter function can be used with either approach. Only the way SAPUI5 resolves and accesses that function is different.</P><P class="">From a functionality perspective, both approaches produce identical results. The choice mainly comes down to how you prefer to organize your application and how your project structures reusable modules.</P><H2 id="toc-hId-4656660">Conclusion</H2></DIV><DIV class=""><P class="">While building this example, I realized that both approaches ultimately achieve the same result. The difference lies in how the formatter dependency is organized rather than how the formatter behaves. Implementing and comparing both approaches side by side gave me a better understanding of how SAPUI5 resolves formatter functions, and I hope this practical comparison helps you make the right choice for your own applications.</P><P>Thank you for reading! If you've used a different approach or have additional insights, I'd love to hear about them in the comments.</P></DIV> 2026-07-13T08:22:22.969000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/how-to-create-value-help-in-capm/ba-p/14435241 How To Create Value Help In CAPM. 2026-07-13T11:43:54.619000+02:00 Pooja_HM https://community.sap.com/t5/user/viewprofilepage/user-id/2301907 <P>Hi Everyone,</P><P>In this blog, I'll walk you through one of the easiest ways to implement <STRONG>Value Help (F4 Help)</STRONG> in a SAP CAPM application.</P><P>While developing SAP Fiori applications, we often have fields such as <STRONG>Category, Department, Country, Employee, Supplier</STRONG>, and many others where users should select values instead of typing them manually. If users enter values manually, there is always a chance of entering incorrect or inconsistent data.</P><P>To solve this problem, SAP provides <STRONG>Value Help (F4 Help)</STRONG>, which allows users to choose values from a predefined list. This not only improves the user experience but also ensures that only valid data is saved.</P><P>In this blog, I'll demonstrate how to create a simple Value Help for the <STRONG>Category</STRONG> field by using a lookup entity, exposing it through a CAP service, and configuring the required CDS annotations.</P><P>To Demonstrate this I have Created a simple CAPM application, folder structure of this Looks like this below image</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Pooja_HM_0-1783417972448.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/430273iCD4AF2AF8BBB5CF3/image-size/medium?v=v2&amp;px=400" role="button" title="Pooja_HM_0-1783417972448.png" alt="Pooja_HM_0-1783417972448.png" /></span><BR /><BR /></P><P><STRONG>Step 1 – Create Database Entities</STRONG></P><P>First, open the <STRONG>db</STRONG> folder and create a <STRONG>schema.cds</STRONG> file.</P><P>In this example, I created two entities:</P><UL><LI><STRONG>Products</STRONG></LI><LI><STRONG>Categories</STRONG></LI></UL><P>The <STRONG>Products</STRONG> entity stores the product details, while the <STRONG>Categories</STRONG> entity acts as the lookup table that provides the values for the Value Help.</P><P>Below is the content of my <STRONG>schema.cds</STRONG> file.</P><pre class="lia-code-sample language-json"><code>namespace demo.valuehelp; entity Products { key ID : UUID; name : String(100); category : String(10); price : Decimal(10,2); } entity Categories { key code : String(10); description : String(50); }</code></pre><H2 id="toc-hId-1819164316">Step 2 – Create Sample Data</H2><P>Next, inside the <STRONG>db/data</STRONG> folder, I created two CSV files.</P><UL><LI>demo.valuehelp-Categories.csv</LI><LI>demo.valuehelp-Products.csv</LI></UL><P>The <STRONG>Categories</STRONG> CSV file contains the values that will be displayed in the Value Help dialog.</P><P>Below is the content of the Categories CSV file.</P><pre class="lia-code-sample language-json"><code>code,description ELEC,Electronics FURN,Furniture BOOK,Books</code></pre><P>The Products CSV file contains sample product records.</P><pre class="lia-code-sample language-json"><code>ID,name,category,price 3b5a1e2a-1111-4a1a-9a1a-000000000001,Laptop,ELEC,55000.00 3b5a1e2a-1111-4a1a-9a1a-000000000002,Office Chair,FURN,4500.00 3b5a1e2a-1111-4a1a-9a1a-000000000003,Notebook Set,BOOK,250.00</code></pre><H2 id="toc-hId-1622650811">Step 3 – Create the Service</H2><P>Next, create a <STRONG>service.cds</STRONG> file inside the <STRONG>srv</STRONG> folder.</P><P>In this file, I exposed both entities as projections so that they are available through OData services.</P><P>Below is the content of my <STRONG>service.cds</STRONG> file.</P><pre class="lia-code-sample language-json"><code>using demo.valuehelp as db from '../db/schema'; service CatalogService { entity Products as projection on db.Products; entity Categories as projection on db.Categories; }</code></pre><H2 id="toc-hId-1426137306">Step 4 – Configure Value Help</H2><P>After exposing the required entities in the service, I created a Fiori application inside the <STRONG>app</STRONG> folder by selecting <STRONG>New Project from Template</STRONG> in SAP Business Application Studio. I chose my local CAP application as the data source, and the project automatically generated the annotations.cds file.</P><P>In this file, I added the required UI annotations and configured the @Common.ValueList annotation for the <STRONG>Category</STRONG> field. The CollectionPath property points to the Categories entity, while LocalDataProperty and ValueListProperty map the selected value from the Value Help dialog back to the <STRONG>Product</STRONG> entity.</P><P>One thing I learned during this implementation is that even a small typo in an annotation or entity name can prevent the Value Help from working, while no clear error is shown in the UI. I found it helpful to verify the service metadata and ensure that all entity and property names matched exactly before troubleshooting further. This saved me a significant amount of debugging time.</P><P>Below is the content of my <STRONG>annotations.cds</STRONG> file.</P><pre class="lia-code-sample language-json"><code>using CatalogService as service from '../../srv/service'; annotate service.Products with @( UI.SelectionFields: [ category, name ], UI.LineItem: [ { Value: name }, { Value: category }, { Value: price } ] ) { category @Common.ValueList : { Label : 'Category', CollectionPath : 'Categories', Parameters : [ { $Type: 'Common.ValueListParameterInOut', LocalDataProperty: category, ValueListProperty: 'code' }, { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'description' } ] }; };</code></pre><H2 id="toc-hId-1229623801">Step 5 – Run the Application</H2><P>After completing the above configuration, open the terminal and execute the following command.<BR /><BR /></P><pre class="lia-code-sample language-json"><code>cds watch</code></pre><P>Once the application starts successfully, open the generated Fiori application.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="image.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/430281i6D96293B7CFC677B/image-size/large?v=v2&amp;px=999" role="button" title="image.png" alt="image.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P><BR />here click on the URL which is having <STRONG>index.html</STRONG><BR />In this application I created value Help for a Selection field I mean a Search field.<BR />Click On the Value Help Icon&nbsp;<BR />Here is the Image How My Value Help is looking</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Pooja_HM_1-1783419088723.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/430282i263E9DC945684C2D/image-size/large?v=v2&amp;px=999" role="button" title="Pooja_HM_1-1783419088723.png" alt="Pooja_HM_1-1783419088723.png" /></span></P><H2 id="toc-hId-1033110296">How Does It Work?</H2><P>Let's quickly understand what happens behind the scenes.</P><UL><LI>The <STRONG>Categories</STRONG> entity stores all valid category values.</LI><LI>The <STRONG>Products</STRONG> entity contains the Category field.</LI><LI>The <STRONG>service.cds</STRONG> file exposes both entities.</LI><LI>The <STRONG>annotations.cds</STRONG> file connects the Product Category field with the Categories entity by using the <STRONG><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1648847">@Common</a>.ValueList</STRONG> annotation.</LI><LI>When the application runs, SAP Fiori automatically recognizes the annotation and displays the Value Help dialog.</LI></UL><P>Because of this configuration, users don't have to type category values manually, reducing data entry mistakes and improving consistency.</P><HR /><H2 id="toc-hId-836596791">Conclusion</H2><P>In this blog, we learned how to implement <STRONG>Value Help in SAP CAPM</STRONG> by using a lookup entity, service projections, and CDS annotations.</P><P>We created the required database entities, loaded sample data using CSV files, exposed the entities through a CAP service, and configured the <STRONG><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1648847">@Common</a>.ValueList</STRONG> annotation to display the Value Help dialog in the SAP Fiori application.</P><P>This approach is simple, reusable, and can be applied to many business scenarios such as <STRONG>Customers, Suppliers, Departments, Countries, Employees</STRONG>, or any other master data.</P><P>I hope this blog helps you understand how Value Help works in SAP CAPM.</P><P><STRONG>Thank you for reading!</STRONG></P> 2026-07-13T11:43:54.619000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/the-fourth-way-to-go-offline-in-ui5-and-why-the-service-worker-stays-dumb/ba-p/14441667 The Fourth Way to Go Offline in UI5 (And Why the Service Worker Stays Dumb) 2026-07-15T23:10:06.816000+02:00 Noel_Hendrikx https://community.sap.com/t5/user/viewprofilepage/user-id/185962 <BLOCKQUOTE><P>Written up after attending <STRONG>"Offline capabilities in UI5 – Let's discuss"</STRONG> at UI5Con, run by Viktor Sperling and Lars Kissel from SAP's UI5 team as an open discussion rather than a one-way talk. The app described below is a real, shipped project — a handheld scanning app for warehouse/inventory operations — with client-identifying details deliberately left out.</P></BLOCKQUOTE><H2 id="toc-hId-1819972585">Three ideas, three shapes of "offline"</H2><P>The session's core insight, stated up front, was that "offline" isn't one problem — it has a shape, and the right architecture depends on that shape. They presented three ideas, each solving a distinct scenario:</P><UL><LI><STRONG>Idea 1 — OData V4 Model as Change Buffer.</STRONG> <CODE>v4.ODataModel</CODE> holds changes locally (<CODE>updateGroupId</CODE>), disables actions that can't succeed offline, submits once back online (<CODE>submitBatch</CODE>). Connectivity is actively probed — a <CODE>fetch</CODE>/<CODE>AbortController</CODE> ping, not just <CODE>navigator.onLine</CODE>. Scenario: a commuter on a train, signal drops between two stations. Duration: seconds to minutes.</LI><LI><STRONG>Idea 2 — Service Worker as OData Proxy.</STRONG> The service worker becomes an OData-aware proxy: it parses <CODE>$batch</CODE> and read requests, resolves query options, keeps its <STRONG>own IndexedDB store and changelog</STRONG>, and rebuilds a combined <CODE>$batch</CODE> on reconnect. Scenario: a field worker visiting customers, connection comes and goes. Duration: minutes to hours.</LI><LI><STRONG>Idea 3 — JSONModel with Explicit Sync.</STRONG> An explicit download → work → submit workflow. <CODE>JSONModel</CODE> as the UI model, a <CODE>v4.ODataModel</CODE> downloads the required entity sets up front, changes go into a changelog, submitted via <CODE>Context#setProperty</CODE>/<CODE>#delete</CODE>. Scenario: a traveler on a flight, working for hours in airplane mode. Duration: hours to days.</LI></UL><P>Idea 1 is elegant if you can stay inside the OData V4 model's own change-tracking. Idea 3 matches the shape of what we needed almost exactly — an explicit, user-initiated offline session, not an accidental one.</P><P>Idea 2 is the one worth pausing on, because it's easy to misread it as "just cache the OData responses." It doesn't. It builds its <EM>own</EM> IndexedDB store and changelog <STRONG>inside the service worker</STRONG>, and re-implements a slice of the OData protocol there — parsing <CODE>$batch</CODE> payloads, matching query options against locally held entity sets, replaying a changelog into a fresh <CODE>$batch</CODE> on reconnect. That's a genuinely more sophisticated design than an HTTP cache, and it's also exactly why it didn't fit our case: it means OData-protocol knowledge and business logic now live inside a service worker — a context that's already awkward to debug, awkward to unit test, and now also carries backend-shaped logic.</P><P>The session closed with a "Pain Points" slide worth quoting directly, because it names this tension independent of any particular implementation:</P><BLOCKQUOTE><P><STRONG>Back-end logic in the client</STRONG> — understanding the OData protocol, <CODE>$filter</CODE> options that quickly become complex, resolving navigation properties locally, duplicating business logic, OData Actions, server-side calculated fields.</P><P><STRONG>Conflicts at sync time</STRONG> — concurrent modifications or business-rule violations, and expired authentication or session tokens, refresh before sync.</P></BLOCKQUOTE><P>That first point is the crux of this whole post: <STRONG>the problem isn't "service workers shouldn't hold data." The problem is putting protocol-aware business logic inside a service worker specifically.</STRONG> The approach below solves the same problem idea 2 solves — resilience across a flaky connection — without paying that price.</P><H2 id="toc-hId-1623459080">Our shape of offline</H2><P>Before the architecture, the shape of the problem, because it explains every choice below:</P><UL><LI>Runs on ruggedized handheld scanners with genuinely intermittent connectivity — sometimes hours without a signal, not a brief tunnel dip.</LI><LI>Scans in bulk via RFID — hundreds of local mutations in a single session before anything needs to reach a server.</LI><LI>Submits as a batch at the end of a session, not request-per-scan — the backend should never see a half-finished state.</LI></UL><P>That's squarely "spontaneous, business-logic-heavy, shared" territory — borrowing the session's own three-axis framing (Preparation: predictable ↔ spontaneous; Interaction complexity: simple form ↔ business logic; Conflict sensitivity: exclusive ↔ shared) — which rules out idea 1 immediately and makes idea 2's in-worker protocol logic feel like the wrong place to carry that weight.</P><H2 id="toc-hId-1426945575">A fourth option: two layers, two jobs</H2><P>The architecture splits cleanly into two layers that don't know about each other's internals.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Noel_Hendrikx_0-1784149676126.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/433616i8A544B5E0F4ADD9F/image-size/medium?v=v2&amp;px=400" role="button" title="Noel_Hendrikx_0-1784149676126.png" alt="Noel_Hendrikx_0-1784149676126.png" /></span></P><P><EM>Two isolated layers on the device. The service worker never touches OData or $batch; the JSON model/IndexedDB layer owns all sync logic and only reaches the backend at two named moments — a fetch at the start of a session, a $batch submit at the end.</EM></P><H3 id="toc-hId-1359514789">Layer 1 — the shell, and only the shell</H3><P>The service worker knows nothing about OData, nothing about business entities. Its <CODE>fetch</CODE> strategy has three branches:</P><UL><LI><STRONG>Skip</STRONG> — OData entity sets, lookup endpoints, <CODE>$batch</CODE>: always straight to the network, never cached. Stale business data is worse than a failed fetch, and offline resilience for that data lives entirely in the layer below.</LI><LI><STRONG>Network-first with a timeout race</STRONG> — our own app resources (JS/CSS/XML/i18n): online gets fresh content plus a cache update, offline or a timeout falls back to cache. We deliberately avoid <CODE>navigator.onLine</CODE> — it lies in both directions. Racing a real <CODE>fetch()</CODE> against a timeout is symmetrically correct for "no network" and "server unreachable" alike.</LI><LI><STRONG>Cache-first</STRONG> — third-party/library resources that rarely change.</LI></UL><PRE>const NETWORK_TIMEOUT_MS = 2000; function rejectAfter(ms) { return new Promise((_, reject) =&gt; setTimeout(() =&gt; reject(new Error("SW network-timeout")), ms) ); } async function networkFirst(request) { const cache = await caches.open(CACHE_NAME); try { const response = await Promise.race([fetch(request), rejectAfter(NETWORK_TIMEOUT_MS)]); if (isCacheable(response)) cache.put(request, response.clone()); return response; } catch { const cached = await cache.match(request, { ignoreSearch: true }); if (cached) return cached; throw new Error("no network, no cache"); } }</PRE><P>One gotcha worth flagging for anyone building this themselves: cross-origin <CODE>&lt;script src&gt;</CODE> loads — like the UI5 runtime, loaded no-cors — come back as <CODE>opaque</CODE> responses. <CODE>response.ok</CODE> is <CODE>false</CODE> even on success, but the body is perfectly cacheable. Skip that check and your library bundles silently never get cached.</P><P><STRONG>Loading UI5 itself deserves its own paragraph.</STRONG> Our bootstrap script picks its UI5 source depending on where it's running: an on-premise deployment loads <CODE>sap-ui-core.js</CODE> from the same server (no internet dependency at all), local dev loads from the public SAPUI5 CDN. "Offline-first" starts at the very first <CODE>&lt;script&gt;</CODE> tag — if you fetch the UI5 runtime itself from a CDN, your app is, by definition, not offline-capable anywhere without internet access. Those two sources produce different response types (same-origin vs. cross-origin no-cors), which is exactly why the <CODE>opaque</CODE> check above matters — without it, cache-first would silently never cache the UI5 runtime in the on-premise case.</P><P>Also worth knowing: the service worker doesn't register at all when the app runs inside a Fiori Launchpad — more on why below.</P><H3 id="toc-hId-1163001284">Layer 2 — data, entirely in ordinary application code</H3><P>IndexedDB is the source of truth; the JSON model is loaded from it on boot and reloaded after every mutation. OData traffic isn't scattered through the app — it happens at exactly two named operations:</P><OL><LI><STRONG>Fetch</STRONG> — pull existing records from the backend when online, including a bulk status update so colleagues can see something's been picked up.</LI><LI><STRONG>Submit</STRONG> — at the end of a session, one <CODE>$batch</CODE> per document with everything — header update, item/serial changes — inside a single changeSet, so the backend never sees a half-written state.</LI></OL><PRE>private _queueHeaderUpdate(record: Order): void { this.model.update(getHeaderPath(record.id), { status: "3", processedBy: record.processedBy, processedAt: toBackendTimestamp() }, { merge: true, groupId: BATCH_GROUP, changeSetId: CHANGE_SET }); } private _submitBatch(): Promise&lt;void&gt; { return new Promise((resolve, reject) =&gt; { this.model.submitChanges({ groupId: BATCH_GROUP, success: (data) =&gt; this._hasError(data) ? reject(data) : resolve(), error: reject }); }); }</PRE><P>After a successful sync, the server's response is merged back into the local record — fields the server owns get overwritten, purely local UI state stays as-is.</P><P>This is, in workflow terms, closest to idea 3 — download, work, submit — but hardened with a persistent IndexedDB layer underneath instead of a purely in-memory <CODE>JSONModel</CODE>, so a page refresh mid-session doesn't throw everything away.</P><H2 id="toc-hId-837405060">The bit I'm not proud to admit: session expiry</H2><P>An architecture built around hours of offline work immediately raises a question: what happens to the session/XSRF token when you come back online? Honest answer: we don't have a good one yet.</P><UL><LI>There's no custom 401/403 detection or re-login flow anywhere in the app. Every OData call leans entirely on the OData V2 model's default XSRF fetch/retry behavior.</LI><LI>The batch-submit code only checks HTTP status codes (<CODE>&gt;= 400</CODE>) to decide whether a sync succeeded — it doesn't distinguish "the server rejected this data" from "the session expired, retry after a token refresh."</LI><LI>Practical consequence: if a device has been offline for a day and the underlying session has since expired, the first sync batch after reconnecting fails as a generic error, not a recognizable "please log in again."</LI></UL><BLOCKQUOTE><P><STRONG>Reassurance, not excuse.</STRONG> This isn't a shortcoming unique to this approach. The session's own Pain Points slide names it directly: "expired authentication or session tokens — refresh before sync." Even SAP's own UI5 team presents this as an open problem across all three of their ideas, not something any of them has solved. That took some pressure off having to invent something clever here — naming it honestly is enough.</P></BLOCKQUOTE><P>This isn't a showstopper in practice (logging back in on the device always fixes it), but it is a real gap: this architecture solves the <EM>transport</EM> problem, not the <EM>identity</EM> problem. "Offline-first" here does not mean "session-independent." You'd need to layer explicit 401 detection with a clear re-auth prompt on top if that matters for your case.</P><H2 id="toc-hId-640891555">How the cache invalidates itself</H2><P>Every build — production or local dev start — stamps a fresh build timestamp into a small generated file, imported into the service worker via <CODE>importScripts</CODE>. The service worker script itself never changes between builds; only that one value does.</P><PRE>self.BUILD_VERSION = "1737012345678"; // stamped per build const CACHE_NAME = "app-v" + self.BUILD_VERSION; self.addEventListener("activate", (event) =&gt; { event.waitUntil( caches.keys().then((keys) =&gt; Promise.all( keys.filter((k) =&gt; k.startsWith("app-v") &amp;&amp; k !== CACHE_NAME) .map((k) =&gt; caches.delete(k)) ) ) ); });</PRE><P>No semver, no manual version bump to forget — a timestamp per build means the cache invalidates itself automatically on every build/start. Worth noting separately: the app's manifest version is a static marker, unrelated to this cache version or the OData service version — a deliberate decoupling, since it's tempting to conflate all three kinds of "version."</P><H2 id="toc-hId-444378050">The Fiori Launchpad has its own appetite for the network</H2><P>A natural question: could this run inside a Fiori Launchpad instead of standalone? In our case, no — and it's worth explaining why, because the reason generalizes.</P><P>A Launchpad is itself an application full of network-dependent shell services that have nothing to do with the app you're hosting inside it: personalization (tile layout, saved filters), intent/navigation resolution against a catalog, notifications, enterprise search, usage tracking, user preferences and theming. None of that is "your app" — you don't control it as an app builder, and none of it is designed to gracefully degrade without a network. That's exactly why registering a custom service worker under a Launchpad route conflicts with the shell: it already has its own lifecycle and caching assumptions, and a second service worker layered on top produces, at best, inconsistent behavior. Practically, our app runs standalone via a direct deep link, not as a Launchpad tile.</P><BLOCKQUOTE><P><STRONG>Speculative — a wish, not a feature.</STRONG> An officially supported "shell offline mode" would let an individual app register its own service worker scoped strictly to its own sub-path — the shell owning the root scope — and would let shell services degrade gracefully (personalization/notifications/search no-op instead of blocking) rather than all-or-nothing. To be clear, I'm not aware this exists today; it's the question this project left open, and a good one to put back to the community.</P></BLOCKQUOTE><H2 id="toc-hId-247864545">What about Fiori Elements and OData V4?</H2><P>Also raised at the session: Fiori Elements with V4 bindings makes offline hard. I think that's right, and it's worth explaining why rather than just asserting it.</P><P>Fiori Elements is metadata- and annotation-driven: value helps, filters, side effects after an edit, and draft handling are, at their core, <EM>live</EM> interactions with the OData V4 service — they're not designed to be replayed from a local cache. <CODE>sap.ui.model.odata.v4.ODataModel</CODE> does keep an in-memory cache of already-loaded data, but that's request-level caching within a session, not a persistent offline store with conflict resolution the way a dedicated IndexedDB layer is. Historically, SAP's "real" offline-OData story has lived more at the native/mobile SDK level than in-browser Fiori Elements — worth double-checking the current state of that, since this area moves.</P><P>Practically, the options that hold up today, roughly in order of "how much Fiori Elements you keep":</P><OL><LI>Limit offline to read-mostly reference data (cache value-help lists locally), keep write flows online.</LI><LI>Freestyle islands inside an otherwise Fiori-Elements-driven app for exactly the screens that need to work offline.</LI><LI>Go fully freestyle, as we did — you lose FE's automation (annotations → UI), but gain full control over what lives locally.</LI></OL><P>Fiori Elements optimizes for speed and consistency while you're online; the moment offline becomes a hard requirement, you're trading away precisely the automation that makes FE valuable — and freestyle starts looking attractive again.</P><H2 id="toc-hId-51351040">Lessons that cost us a debugging session</H2><UL><LI><STRONG>Silent cache misses on lazy-loaded fragments.</STRONG> Dialogs that only load at runtime via <CODE>Fragment.load</CODE> failed silently if they weren't explicitly named in the pre-cache list — an empty dialog, no error — and the symptom only ever showed up under genuine offline testing, never in normal dev with a network.</LI><LI><STRONG><CODE>navigator.onLine</CODE> lies.</STRONG> It's <CODE>true</CODE> whenever a network interface exists, not when a server is reachable, and <CODE>false</CODE> isn't reliable either. A race between a real <CODE>fetch()</CODE> and a timeout is symmetrically correct in both directions.</LI><LI><STRONG>Two UI5 sources, two response types.</STRONG> The same bootstrap code sometimes loads UI5 same-origin, sometimes cross-origin/no-cors — normal vs. <CODE>opaque</CODE> responses — and a cacheability check that only looks at <CODE>response.ok</CODE> silently misses the second case.</LI><LI><STRONG>A service worker and a Fiori Launchpad shell don't mix.</STRONG> Registering a custom SW under a Launchpad route caused conflicts with the shell's own layer; registration had to be explicitly excluded for those routes.</LI><LI><STRONG>Deliberately no IndexedDB schema migration.</STRONG> On a breaking model change, we chose a new database name over a migration path — old local data is simply ignored, not converted. For a small, known fleet of devices carrying mostly in-transit data, that's a cheaper, safer call than maintaining migration code.</LI><LI><STRONG>Session expiry isn't solved for free</STRONG> by any of this — see above. Worth saying explicitly so the takeaway isn't "offline-first means never thinking about sessions again."</LI></UL><H2 id="toc-hId-202091892">When this pattern fits — and when it doesn't</H2><P>This isn't "the best option" — it's the option that fits a specific shape of app. The session's own "Technical Aspects" slide offers a genuinely useful way to reason about that shape: three axes — <STRONG>Preparation</STRONG> (predictable ↔ spontaneous), <STRONG>Interaction complexity</STRONG> (simple form ↔ business logic), and <STRONG>Conflict sensitivity</STRONG> (exclusive ↔ shared). Our app sits firmly at the right-hand end of every axis — unpredictable offline moments, non-trivial document logic, multiple colleagues touching the same stock. That's exactly why idea 1, built for predictable and simple cases, wasn't a fit.</P><P><STRONG>Fits well:</STRONG></P><UL><LI>Apps where users mutate a lot before anything needs to reach a server (scan sessions, inspections, field forms) — the win is decoupling "mutating" from "submitting."</LI><LI>Longer, unpredictable offline periods (minutes to hours), not just a brief tunnel dip.</LI><LI>Business logic that doesn't fit neatly into a generic entity cache — merges, batch/changeSet semantics, status derived from other fields, locally created records that only get a server ID at sync time.</LI><LI>A limited, known set of devices/users, where "no automatic IndexedDB migration" and "manual session recovery" are acceptable trade-offs.</LI></UL><P><STRONG>Fits less well:</STRONG></P><UL><LI>Apps where nearly every action needs a direct server round-trip anyway (real-time collaborative, prices/stock that must be correct <EM>right now</EM>).</LI><LI>Short, second-scale, unprepared offline blips: idea 1 is probably cheaper to build and maintain — you get most of the resilience for free from the model itself.</LI><LI>Many concurrent users editing the same records ("shared" on the conflict-sensitivity axis): this pattern solves "one device, local, sync later," not multi-user conflict resolution. Look at CRDTs, optimistic locking with real conflict UI, or just staying online with a good loading state instead.</LI><LI>Teams that would rather lean on a standardized offline framework than maintain a custom SW + IndexedDB layer.</LI></UL><H2 id="toc-hId-5578387">A short aside: why not Cordova, Capacitor, or a native SDK?</H2><P>A fair question: why a PWA/service-worker approach, and not a hybrid or native app that's offline-capable by default? First, a framing correction: <STRONG>a service worker isn't an experimental bet — it's a mature, broadly supported web standard.</STRONG> This isn't "proven vs. experimental," it's "proven vs. proven."</P><UL><LI><STRONG>Apache Cordova</STRONG> (formerly PhoneGap) wraps a web app in a native webview shell with plugin access to device APIs. Still an active Apache project, but development velocity has been low for years — much of the hybrid ecosystem has moved toward Capacitor. Worth checking current release cadence and plugin health before repeating that as fact, since it dates quickly.</LI><LI><STRONG>Capacitor</STRONG> (Ionic) is the de facto successor for "web app in a native shell" — a similar model, more actively maintained, a more modern plugin architecture.</LI><LI><STRONG>Native, vendor-specific offline SDKs</STRONG> often ship a ready-made offline layer with built-in conflict resolution — at the cost of <STRONG>vendor lock-in</STRONG>: a proprietary data model/SDK, lower portability, and the loss of "it's just a web app, patch with a new build."</LI></UL><P>We stayed on web/PWA for three reasons: it's built on proven, stable technology rather than a moving target; a new build is a set of static files picked up by the service worker on the next online load — no app-store review, no MDM rollout cycle per patch; and the sync logic is our own readable code against a standard OData service, not a proprietary SDK locking us to one vendor.</P><H2 id="toc-hId--190935118">Where this leaves things</H2><P>None of this is a silver bullet, and it isn't meant to replace the session's three ideas — it's meant to sit alongside them as a fourth shape for a fourth kind of problem. If your offline story is short and unprepared, idea 1 will get you further with less code. If you want the app itself to stay entirely unaware of offline handling, idea 2's service-worker-as-proxy is a genuinely clever answer, provided you're comfortable carrying protocol logic in that layer. If your offline sessions are long, deliberate, and business-logic-heavy — ours were — this is the shape that held up.</P><P>Thanks to Viktor and Lars for running the session as a discussion instead of a monologue — it's the reason this idea exists in writable form at all. Curious how others have handled the session-expiry gap in particular — that felt like the one open thread nobody in the room had a clean answer for.</P> 2026-07-15T23:10:06.816000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/advanced-vizframe-customization-in-sapui5-ovp-applications/ba-p/14427318 Advanced VizFrame Customization in SAPUI5 OVP Applications 2026-07-17T11:21:09.926000+02:00 VandanaVaswani https://community.sap.com/t5/user/viewprofilepage/user-id/1703070 <P><STRONG>Advanced VizFrame Customization in SAPUI5 OVP Applications</STRONG></P><P><STRONG>Introduction</STRONG></P><P>As we all know, that we can create Fiori charts easily using the standard templates provided by SAP, but it does not give enough flexibility to customize on top of that, when we want to make any changes in those standard templates then we must make use of custom charts. For custom charts, VizFrames are used to perform the customization on the chart as per the requirement.</P><P>In one of my recent implementations, I defined the custom chart using VizFrame with two lines and two columns on x and y axis. Apart from this there was a dynamic colour change option given to the user, so that they can dynamically change colors of both the lines and columns, and the colour once picked, has persisted in the localStorage of the browser for next time use.</P><P>This blog explains the implementation steps:</P><P><STRONG>Business Requirement</STRONG></P><UL><LI>Dual combination chart (2lines and 2 bars)</LI><LI>Allow user to personalize chart colours.</LI><LI>Saves the colour preference.</LI></UL><P><STRONG>Solution</STRONG></P><P>To achieve the above requirement, we have used</P><UL><LI>sap.viz.ui5.controls.VizFrame</LI><LI>dual_combinatoin chart type</LI><LI>ColorPickerPopover</LI><LI>Sap.m.Menu</LI><LI>Browser localStorage</LI></UL><P><U>Step1: Create a dual combination chart using VizFrame:</U></P><P class="lia-align-left" style="text-align : left;">The custom chart is created using VizFrame with dual_combination as the chart type.</P><pre class="lia-code-sample language-abap"><code>&lt;viz:VizFrame id="idVizFrame" vizType="dual_combination" uiConfig="{applicationSet:'fiori'}"&gt; &lt;/viz:VizFrame&gt;</code></pre><P><U>Step2: Define the chart dimensions and measures:</U></P><P>Here, I am defining the chart dimensions and measures.</P><pre class="lia-code-sample language-abap"><code>Dimension:&lt;vizDataset:DimensionDefinition name="Period" value="{Date}" /&gt; Measure: &lt;vizDataset:MeasureDefinition name="Metric A" value="{Value1}" /&gt; &lt;vizDataset:MeasureDefinition name="Metric B" value="{Value2}" /&gt; &lt;vizDataset:MeasureDefinition name="Metric C" value="{Value3}" /&gt; &lt;vizDataset:MeasureDefinition name="Metric D" value="{Value4}" /&gt; </code></pre><P><U>Step3: Configuring chart feeds:</U></P><P>First, I have configured the primary axis to display column measures and then the secondary axis to display line measures.</P><pre class="lia-code-sample language-abap"><code>&lt;vizFeeds:FeedItem uid="valueAxis" type="Measure" values="Metric A,Metric B"/&gt; &lt;vizFeeds:FeedItem uid="valueAxis2" type="Measure" values="Metric C,Metric D"/&gt; &lt;vizFeeds:FeedItem uid="categoryAxis" type="Dimension" values="Period"/&gt;</code></pre><P><U>Step4: Creating colour palette button:</U><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VandanaVaswani_4-1782392995898.png" style="width: 96px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/426101i5F665CF463CA7398/image-dimensions/96x45?v=v2" width="96" height="45" role="button" title="VandanaVaswani_4-1782392995898.png" alt="VandanaVaswani_4-1782392995898.png" /></span></P><P>Enabling palette button as drop-down menu, so that the user can select the measure for which personalized colour needs to be picked.</P><pre class="lia-code-sample language-abap"><code>&lt;Button icon="sap-icon://palette" type="Transparent" tooltip="Change Chart Colors" press=".onOpenColorSettings"/&gt; </code></pre><P>When the user clicks on the menu item, a color selection menu appears.</P><pre class="lia-code-sample language-abap"><code>onOpenColorSettings: function(oEvent) { if (!this._oColorMenu) { this._oColorMenu = new sap.m.Menu({ items: [ new sap.m.MenuItem({ text: "Metric A", press: this.onMeasureSelect.bind(this, "Metric A") }), new sap.m.MenuItem({ text: "Metric B", press: this.onMeasureSelect.bind(this, "Metric B") }) ] }); } this.oColorMenu.openBy(oEvent.getSource()); } </code></pre><P>As shown in the snap below, we get a colour palette button, with the dropdown menu and all the required options, and when we select on each one, we get the colour picker as shown in the snap below.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VandanaVaswani_5-1782392995899.png" style="width: 318px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/426102i8817DD186E64B2EF/image-dimensions/318x189?v=v2" width="318" height="189" role="button" title="VandanaVaswani_5-1782392995899.png" alt="VandanaVaswani_5-1782392995899.png" /></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VandanaVaswani_6-1782392995906.png" style="width: 313px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/426103i4F6FAA689911C155/image-dimensions/313x402?v=v2" width="313" height="402" role="button" title="VandanaVaswani_6-1782392995906.png" alt="VandanaVaswani_6-1782392995906.png" /></span></P><P><U>Step5: Define the default colours during Initialization:</U></P><P>I have set the default colour values for all measures inside onInit() method.</P><pre class="lia-code-sample language-abap"><code>this.aColors = { "Metric A": "#2E86AB", "Metric B": "#28B463", "Metric C": "#F39C12", "Metric D": "#8E44AD" };</code></pre><P><U>Step6: Applying personalized colours using dataPointStyle in VizFrame:</U></P><P>I have used VizFrame's dataPointStyle config to apply personalized colours to the measures.</P><pre class="lia-code-sample language-abap"><code>plotArea: { dataPointStyle: { rules: [ { dataContext: { measureNames: "Metric A"}, properties: {color: "#2E86AB"} }, { dataContext: {measureNames: "Metric B"}, properties: {color: "#28B463"} } ] </code></pre><P><U>Step7: Open color picker when the measure is selected:</U></P><P>When the measure is selected from the menu, a color picker dialog appears.</P><pre class="lia-code-sample language-abap"><code>_openColorPicker: function(oControl, sMeasure) { if (!this._oColorPicker) { this._oColorPicker = new sap.ui.unified.ColorPickerPopover({ colorString: "HEX", change: fuction(oEvent) { var sColor = oEvent.getParameter("colorString"); this.applyMeasureColor( sMeasure, sColor ); }.bind(this) }); } this._oColorPicker.openBy(oControl); } </code></pre><P><U>Step8: To dynamically update the VizFrame properties:</U></P><P>The selected color is updated dynamically by updating the VizFrame properties.</P><pre class="lia-code-sample language-abap"><code>_applyMeasureColor: function(sMeasure, sColor) { var oVizFrame = this.byId("idVizFrame"); var oProps = oVizFrame.getVizProperties(); oProps.plotArea.dataPointStyle.rules = oProps.plotArea.dataPointStyle.rules.map(function(oRule) { if (oRule.dataContext.measureNames === sMeasure) { oRule.properties.color = sColor; } return oRule; }); oVizFrame.setVizProperties(oProps); } </code></pre><P><U>Step9: Persist the selected colours using browser local storage:</U></P><P>The user selected colors are stored in the browser's local storage, so that the preferred colors are picked up when you reopen the application.</P><pre class="lia-code-sample language-abap"><code>localStorage.setItem( "CHART_COLORS", JSON.stringify(oColorObject) ); </code></pre><P>We can retrieve the same during initialization, so that the colours remain consistent even while reopening the application.<BR />&nbsp;</P><pre class="lia-code-sample language-abap"><code> JSON.parse( localStorage.getItem("CHART_COLORS") );</code></pre><P><STRONG>Output</STRONG></P><P>Here is the final working chart snap:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VandanaVaswani_7-1782392995908.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/426104iC86C9D1D812CACED/image-size/medium?v=v2&amp;px=400" role="button" title="VandanaVaswani_7-1782392995908.png" alt="VandanaVaswani_7-1782392995908.png" /></span></P><P><STRONG>Conclusion</STRONG></P><P>This shows that instead of limiting to pre-defined charts or templates, one can personalize the charts and visualize their own preferences and in turn makes it easy to read and analyse data using VizFrame.</P><P>Reference:&nbsp;<A href="https://ui5.sap.com/#/topic" target="_blank" rel="noopener noreferrer">Documentation - Demo Kit - SAPUI5 SDK</A></P> 2026-07-17T11:21:09.926000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/build-an-auto-rotating-image-slider-in-sapui5-using-carousel-control/ba-p/14424189 Build an Auto-Rotating Image Slider in SAPUI5 using Carousel Control. 2026-07-20T10:38:36.206000+02:00 RaniMJ https://community.sap.com/t5/user/viewprofilepage/user-id/2082928 <P class="lia-align-justify" style="text-align : justify;"><STRONG>Hello everyone,</STRONG></P><P><STRONG><SPAN>Introduction&nbsp;</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>While working on one of my SAPUI5 applications, I wanted the landing page to look more attractive instead of displaying a static image.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>After exploring different approaches, I found that combining the Carousel control with JavaScript's&nbsp;</SPAN><STRONG><I><SPAN>setInterval()</SPAN></I></STRONG><I><SPAN>&nbsp;</SPAN></I><SPAN>function provides a simple and effective solution. In this blog,&nbsp;I'll&nbsp;share how I implemented an auto-rotating image slider and explain the logic behind it so that you can use the same approach in your SAPUI5 applications.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The&nbsp;sap.m.Carousel&nbsp;control is a container control in SAPUI5 that allows users to navigate through a collection of content items. Each item within the Carousel can be an image, form, layout, or any other SAPUI5 control.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Overview&nbsp;</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The Carousel control is commonly used for displaying image galleries, but it can also&nbsp;contain&nbsp;any&nbsp;sap.m&nbsp;control.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Structure&nbsp;</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The carousel consists of&nbsp;the&nbsp;following elements:&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>Content area - displays the different items.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>Navigation - arrows to the left and right for switching between items.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>Paging&nbsp;(optional)&nbsp;- indicator at the bottom to show the current position in the set.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><P class="lia-align-justify" style="text-align : justify;"><SPAN>The paging indicator behaves as follows:&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>showPageIndicator:&nbsp;Determines&nbsp;whether the indicator is displayed.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>If the dataset&nbsp;contains&nbsp;</SPAN><STRONG><SPAN>less than 9 pages</SPAN></STRONG><SPAN>, the indicator&nbsp;displays as&nbsp;</SPAN><STRONG><SPAN>bullets</SPAN></STRONG><SPAN>.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>If the dataset&nbsp;contains&nbsp;</SPAN><STRONG><SPAN>9 or more pages</SPAN></STRONG><SPAN>, the indicator becomes&nbsp;</SPAN><STRONG><SPAN>numeric</SPAN></STRONG><SPAN>.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>pageIndicatorPlacement:&nbsp;Determines&nbsp;where the indicator is&nbsp;located. The default setting is&nbsp;sap.m.CarouselPageIndicatorPlacementType.Bottom&nbsp;(below the content).&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step&nbsp;1 :&nbsp;&nbsp;</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Create the SAPUI5 Project Using a Basic Template&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step&nbsp;2 :</SPAN></STRONG><SPAN>&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Configure the XML View (View1.view.xml)&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>In this view:&nbsp;</SPAN><SPAN>&nbsp;</SPAN></P><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>The left side&nbsp;contains&nbsp;the image slider using the&nbsp;Carousel&nbsp;control.&nbsp;&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><SPAN>The right side&nbsp;contains&nbsp;Login and&nbsp;Sign Up&nbsp;forms using&nbsp;IconTabBar.&nbsp;&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><P class="lia-align-justify" style="text-align : justify;"><SPAN>I chose this layout because it resembles a common login page design used in many business applications. Although the focus of this blog is the Carousel, placing it beside a login form&nbsp;demonstrates&nbsp;a practical use case.</SPAN><SPAN>&nbsp;</SPAN></P><pre class="lia-code-sample language-markup"><code>&lt;mvc:View controllerName="carouselcontrolproject.controller.View1" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:form="sap.ui.layout.form"&gt; &lt;Page id="page" title="{i18n&gt;title}"&gt; &lt;HBox width="100%" justifyContent="SpaceBetween"&gt; &lt;Carousel id="id1" loop="true" width="600px" height="500px"&gt; &lt;Image src="https://images.pexels.com/photos/16767121/pexels-photo-16767121.jpeg" /&gt; &lt;Image src="https://images.pexels.com/photos/32162192/pexels-photo-32162192.jpeg" /&gt; &lt;Image src="https://images.pexels.com/photos/32228687/pexels-photo-32228687.jpeg" /&gt; &lt;Image src="https://images.pexels.com/photos/32730210/pexels-photo-32730210.jpeg" /&gt; &lt;/Carousel&gt; &lt;VBox width="50%"&gt; &lt;IconTabBar&gt; &lt;items&gt; &lt;IconTabFilter text="Login"&gt; &lt;form:SimpleForm editable="true" layout="ResponsiveGridLayout"&gt; &lt;form:content&gt; &lt;Label text="Email" /&gt; &lt;Input id="inputLEmail" value="" valueState="None" valueStateText="" liveChange="onLiveChangeLEmail" /&gt; &lt;Label text="Password" /&gt; &lt;Input id="inputLPassword" value="" valueState="None" valueStateText="" type="Password" liveChange="onLiveChangeLPass" /&gt; &lt;Label /&gt; &lt;VBox alignItems="Center"&gt; &lt;Button text="Log in" type="Emphasized" press="onPressLogin" /&gt; &lt;/VBox&gt; &lt;Label /&gt; &lt;Link text="Forgot Password?" press="onPressOpenForPassFrag" /&gt; &lt;/form:content&gt; &lt;/form:SimpleForm&gt; &lt;/IconTabFilter&gt; &lt;IconTabFilter text="Sign up"&gt; &lt;form:SimpleForm editable="true" layout="ResponsiveGridLayout"&gt; &lt;form:content&gt; &lt;Label text="Name" /&gt; &lt;Input id="inputSName" value="" valueStateText="" liveChange="onLiveChangeName" /&gt; &lt;Label text="Email" /&gt; &lt;Input id="inputSEmail" value="" valueStateText="" liveChange="onLiveChangeEmail" /&gt; &lt;Label text="Password" /&gt; &lt;Input id="inputSPassword" type="Password" value="" valueStateText="" liveChange="onLiveChangePass" /&gt; &lt;Label text="Confirm Password" /&gt; &lt;Input id="inputCPassword" type="Password" value="" valueStateText="" /&gt; &lt;Label /&gt; &lt;HBox justifyContent="SpaceBetween"&gt; &lt;Button text="Reset" type="Emphasized" press="resetSFields" /&gt; &lt;Button text="Sign Up" type="Emphasized" press="onPressSignUp" /&gt; &lt;/HBox&gt; &lt;/form:content&gt; &lt;/form:SimpleForm&gt; &lt;/IconTabFilter&gt; &lt;/items&gt; &lt;/IconTabBar&gt; &lt;/VBox&gt; &lt;/HBox&gt; &lt;/Page&gt; &lt;/mvc:View&gt;</code></pre><P><SPAN>In my example, I configured the Carousel with&nbsp;</SPAN><SPAN>loop="true"</SPAN><SPAN>&nbsp;so that the images continue rotating without interruption. I also assigned a fixed width and height to&nbsp;maintain&nbsp;a consistent layout beside the login form. Each&nbsp;</SPAN><SPAN>Image</SPAN><SPAN>&nbsp;control&nbsp;represents&nbsp;one slide in the Carousel.</SPAN><SPAN>&nbsp;</SPAN></P><P><SPAN>Note: Since external image URLs are used from&nbsp;Pexels, an active internet connection is&nbsp;required&nbsp;to load the images.</SPAN><SPAN>&nbsp;</SPAN></P><P><STRONG><SPAN class=""><SPAN class="">Step&nbsp;</SPAN><SPAN class="">3:</SPAN></SPAN><SPAN class=""><SPAN class="">&nbsp;</SPAN></SPAN></STRONG></P><P><SPAN><SPAN class=""><SPAN class="">Implement Auto-Rotation Logic (View1.controller.js)</SPAN></SPAN><SPAN class="">&nbsp;</SPAN></SPAN></P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([ "sap/ui/core/mvc/Controller" ], function (Controller) { "use strict"; return Controller.extend("carouselcontrolproject.controller.View1", { onInit: function () { // Fetch the Carousel control instance var oCarousel = this.byId("id1"); // Set up a safe interval function for auto-rotation this._iIntervalId = setInterval(function () { if (oCarousel) { oCarousel.next(); } }, 1000); }, onExit: function () { // Clean up the interval when the view is destroyed to prevent memory leaks if (this._iIntervalId) { clearInterval(this._iIntervalId); } } }); });</code></pre><P><SPAN>I used JavaScript's&nbsp;</SPAN><SPAN>setInterval()</SPAN><SPAN>&nbsp;function because it&nbsp;provides&nbsp;a simple way&nbsp;to execute the same action repeatedly. Every second, it calls the Carousel's&nbsp;</SPAN><SPAN>next()</SPAN><SPAN>&nbsp;method, allowing the images to rotate automatically without any user interaction.</SPAN><SPAN>&nbsp;</SPAN></P><P><SPAN><STRONG>Note :</STRONG> For demonstration purposes, the interval is set to 1 second. In production applications, a value between 3 and 5 seconds&nbsp;generally provides&nbsp;a better user experience.</SPAN><SPAN>&nbsp;</SPAN></P><P><STRONG>Step-by-Step Execution&nbsp;</STRONG></P><OL><LI><SPAN>View loads&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI><LI><SPAN>onInit()</SPAN><SPAN>&nbsp;executes&nbsp;automatically&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI><LI><SPAN>Carousel with ID&nbsp;</SPAN><SPAN>id1</SPAN><SPAN>&nbsp;is fetched&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI><LI><SPAN>The&nbsp;</SPAN><SPAN>setInterval()</SPAN><SPAN>&nbsp;callback function executes.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI><LI><SPAN>The&nbsp;</SPAN><SPAN>next()</SPAN><SPAN>&nbsp;method is&nbsp;called on&nbsp;the Carousel.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI><LI><SPAN>The Carousel navigates&nbsp;to the next image.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI><LI><SPAN>If the last image is reached and&nbsp;</SPAN><SPAN>loop="true"</SPAN><SPAN>&nbsp;is enabled, navigation continues from the first image.</SPAN><SPAN>&nbsp;</SPAN></LI></OL><P><STRONG>Output</STRONG><SPAN>&nbsp;</SPAN></P><P><SPAN>The application displays:</SPAN><SPAN>&nbsp;</SPAN></P><UL><LI><SPAN>An auto-rotating image slider on the left side&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL><LI><SPAN>Login and&nbsp;Sign Up&nbsp;forms on the right side&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL><LI><SPAN>Images automatically change every second using JavaScript interval logic</SPAN><SPAN>&nbsp;</SPAN></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RaniMJ_0-1782111053105.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/424513i730993881AEA3859/image-size/medium?v=v2&amp;px=400" role="button" title="RaniMJ_0-1782111053105.png" alt="RaniMJ_0-1782111053105.png" /></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RaniMJ_1-1782111064947.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/424514i1C433C4D9F0E6A73/image-size/medium?v=v2&amp;px=400" role="button" title="RaniMJ_1-1782111064947.png" alt="RaniMJ_1-1782111064947.png" /></span></P><P><STRONG>Conclusion&nbsp;</STRONG></P><P class="">While implementing this feature, I realized that SAPUI5 already provides everything needed to build a simple image slider without relying on external UI libraries. By combining the standard sap.m.Carousel&nbsp;control with a small amount of JavaScript, I was able to create an auto-rotating banner that can easily be reused in landing pages, dashboards, or product showcase applications.</P><P class="lia-align-justify" style="text-align : justify;">I hope this example helps anyone looking to add a more dynamic and engaging user interface to their SAPUI5 applications.</P> 2026-07-20T10:38:36.206000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/ui5-linter-in-action-a-hands-on-workshop-at-ui5con-2026/ba-p/14444463 UI5 Linter in Action: A Hands-On Workshop at UI5con 2026 2026-07-20T15:50:50.154000+02:00 FlorianVogt https://community.sap.com/t5/user/viewprofilepage/user-id/216201 <H1 id="toc-hId-1690977313">UI5 Linter in Action: A Hands-On Workshop at UI5con 2026</H1><P>Still running legacy OpenUI5/SAPUI5 code with deprecated APIs, inline scripts, and global variables? It's time to modernize — and this workshop shows you exactly how.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="FlorianVogt_0-1784555298510.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/435120iD9FBB14D58234A98/image-size/large?v=v2&amp;px=999" role="button" title="FlorianVogt_0-1784555298510.png" alt="FlorianVogt_0-1784555298510.png" /></span></P><H2 id="toc-hId-1623546527">What Happened</H2><P>At UI5con 2026 Germany, we ran a one-hour hands-on workshop where attendees took a real OpenUI5/SAPUI5 application from "legacy" to "future-proof" using <STRONG>UI5 linter</STRONG>. Step by step, participants:</P><UL><LI><STRONG>Ran their first UI5 linter report</STRONG> and learned what it tells them</LI><LI><STRONG>Auto-fixed issues</STRONG> with a single command</LI><LI><STRONG>Manually resolved</STRONG> deprecated APIs, CSP violations, and legacy patterns</LI><LI><STRONG>Integrated UI5 linter into a CI pipeline</STRONG> so regressions never come back</LI></UL><P>The feedback was fantastic — attendees loved the practical, hands-on format and walked away with real skills they could immediately apply to their own projects.</P><H2 id="toc-hId-1427033022">Do It Yourself</H2><P>Couldn't make it to UI5con? No problem. This workshop is fully self-paced and available for you to complete on your own. All you need:</P><H3 id="toc-hId-1359602236">Prerequisites</H3><UL><LI>Node.js (LTS) and Git installed</LI><LI>A code editor of your choice</LI><LI>Basic UI5 development experience</LI></UL><P>Simply clone the <A href="https://github.com/SAP-samples/ui5con-2026-ui5-linter-workshop" target="_blank" rel="noopener nofollow noreferrer">workshop repository</A> and follow the step-by-step instructions. You'll get the same experience as the live attendees — modernizing a real app from start to finish at your own pace.</P><H2 id="toc-hId-1034006012">What You'll Learn</H2><UL><LI>How the UI5 linter identifies deprecated APIs, CSP violations, and legacy patterns</LI><LI>How to auto-fix common issues with a single command</LI><LI>How to manually resolve findings that require deeper changes</LI><LI>How to integrate UI5 linter into your CI pipeline for ongoing code quality</LI></UL><H2 id="toc-hId-837492507">Bonus: AI-Assisted Modernization</H2><P>As an outlook, the workshop briefly explores the <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/introducing-the-ui5-modernization-plugin-modernize-your-openui5-sapui5-app/ba-p/14428191" target="_blank">UI5 Modernization plugin</A> for coding agents — automating the very steps you'll learn manually.</P><HR /><P><STRONG><A href="https://github.com/SAP-samples/ui5con-2026-ui5-linter-workshop" target="_blank" rel="noopener nofollow noreferrer">Clone the repo</A>, follow the steps, and modernize your first app today!</STRONG></P> 2026-07-20T15:50:50.154000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/visualizing-business-processes-in-sapui5-using-the-sap-suite-ui-commons/ba-p/14432676 Visualizing Business Processes in SAPUI5 using the sap.suite.ui.commons.ProcessFlow Control 2026-07-21T10:56:11.351000+02:00 snehar11 https://community.sap.com/t5/user/viewprofilepage/user-id/2084837 <H4 id="toc-hId-2077244323"><FONT face="arial,helvetica,sans-serif"><STRONG>Hello everyone,</STRONG></FONT></H4><P class=""><FONT face="arial,helvetica,sans-serif">In this blog post I'm going to explain the ProcessFlow control in SAPUI5 in a simple way, with the steps I followed while trying it out.</FONT></P><H4 id="toc-hId-1880730818"><FONT face="arial,helvetica,sans-serif"><STRONG>Overview:</STRONG></FONT></H4><P class=""><FONT face="arial,helvetica,sans-serif">So, in simple words, ProcessFlow is a control which is used to show a chain of connected documents as one single picture, instead of showing them as separate rows in a table.</FONT></P><P class=""><FONT face="arial,helvetica,sans-serif">For example, in most SAP scenarios one document leads to another document. A Sales Order leads to a Delivery, and the Delivery leads to an Invoice. If we show these in a normal list, all three will appear as separate rows, and the user cannot easily tell how they are connected or which one is stuck. So, by using the ProcessFlow control, we can show all three documents together in one single flow, with colors showing the status of each document.</FONT></P><P class=""><FONT face="arial,helvetica,sans-serif">So here I have created one small project to try this out, where I have taken a simple Order-to-Cash example - Sales Order, Delivery, and Invoice - and shown them using the ProcessFlow control.</FONT></P><H4 id="toc-hId-1684217313"><FONT face="arial,helvetica,sans-serif"><STRONG>Why I used ProcessFlow instead of building it myself</STRONG></FONT></H4><P class=""><FONT face="arial,helvetica,sans-serif">So, first I thought of building this on my own using some divs and CSS and manually giving colors based on status. But then I realized SAPUI5 already gives a standard control for this, so I did not have to build the status colors, click behaviour, or zoom behaviour on my own. Also, since it is a standard Fiori control, it looks and behaves the same as other Fiori apps, so I did not have to do any extra styling.</FONT></P><H4 id="toc-hId-1487703808"><FONT face="arial,helvetica,sans-serif"><STRONG><SPAN>Project Structure</SPAN></STRONG><SPAN>&nbsp;</SPAN></FONT></H4><P class="lia-align-left" style="text-align : left;"><FONT face="arial,helvetica,sans-serif">The important files for this control are inside the webapp folder, mainly the view, controller, model, and manifest.json file, same as any normal SAPUI5 app.</FONT></P><P class="lia-align-left" style="text-align : left;"><FONT face="arial,helvetica,sans-serif"><SPAN>processflowcontrol/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>├── webapp/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── controller/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;│ &nbsp;&nbsp;├── App.controller.js</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;│ &nbsp;&nbsp;└── View1.controller.js</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├──&nbsp;css/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── i18n/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── model/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;│ &nbsp;&nbsp;├──&nbsp;data.json&nbsp;&nbsp;</SPAN><SPAN><BR /></SPAN><SPAN>│ &nbsp;&nbsp;│ &nbsp;&nbsp;└── models.js</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── test/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── view/</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;│ &nbsp;&nbsp;├── App.view.xml</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;│ &nbsp;&nbsp;└── View1.view.xml</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── Component.js</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;├── index.html</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>│ &nbsp;&nbsp;└── manifest.json</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>├── package.json</SPAN><SPAN>&nbsp;<BR /></SPAN><SPAN>└── README.md</SPAN></FONT></P><H4 id="toc-hId-1291190303"><FONT face="arial,helvetica,sans-serif">Step 1: Declaring the Data Model in&nbsp;manifest.json&nbsp;</FONT></H4><P class="lia-align-left" style="text-align : left;"><FONT face="arial,helvetica,sans-serif">So, first we have to declare our JSON model in the manifest.json file, so that the framework will load it automatically when the app starts. We don't need to write any extra code in the controller for this.</FONT></P><P class="lia-align-left" style="text-align : left;"><FONT face="arial,helvetica,sans-serif">"models": {&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;"i18n": {&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;&nbsp;&nbsp;"type": "sap.ui.model.resource.ResourceModel",&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;&nbsp;&nbsp;"settings": {&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"bundleName": "processflowcontrol.i18n.i18n"&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;&nbsp;&nbsp;}&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;},&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;"process": {&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;&nbsp;&nbsp;"type": "sap.ui.model.json.JSONModel",&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;&nbsp;&nbsp;"uri": "model/data.json"&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">&nbsp;}&nbsp;</FONT><BR /><FONT face="arial,helvetica,sans-serif">}<SPAN>&nbsp;<BR /></SPAN></FONT></P><H4 id="toc-hId-1094676798"><FONT face="arial,helvetica,sans-serif">Step 2: Create the JSON data (model/data.json)</FONT></H4><P class=""><FONT face="arial,helvetica,sans-serif">So, the ProcessFlow control needs two things in the JSON file - lanes and nodes.</FONT></P><P class=""><FONT face="arial,helvetica,sans-serif">lanes means the stages, like Sales Order, Delivery, and Invoice. nodes means the actual documents which sit inside these stages.</FONT></P><pre class="lia-code-sample language-json"><code>{ "lanes": [ { "id": "order", "label": "Sales Order", "icon": "sap-icon://sales-order", "position": 0 }, { "id": "delivery", "label": "Delivery", "icon": "sap-icon://shipping-status", "position": 1 }, { "id": "invoice", "label": "Invoice", "icon": "sap-icon://receipt", "position": 2 } ], "nodes": [ { "id": "SO-1001", "lane": "order", "title": "SO 1001", "abbr": "SO", "state": "Positive", "stateText": "Completed", "children": ["DEL-2001"] }, { "id": "DEL-2001", "lane": "delivery", "title": "Delivery 2001", "abbr": "DE", "state": "Positive", "stateText": "Shipped", "children": ["INV-3001"] }, { "id": "INV-3001", "lane": "invoice", "title": "Invoice 3001", "abbr": "IN", "state": "Critical", "stateText": "Overdue", "children": null } ] }</code></pre><DIV class=""><DIV><DIV><DIV><DIV><DIV><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><H4 id="toc-hId-898163293">&nbsp;</H4><H5 id="toc-hId-830732507"><FONT face="arial,helvetica,sans-serif"><STRONG>A few important points about the JSON data:</STRONG></FONT></H5><UL class=""><LI><P><FONT face="arial,helvetica,sans-serif"><STRONG><CODE>position</CODE></STRONG> (on lanes) — controls the left-to-right order of the lanes. I removed it once in my own testing file just to check, and the Delivery lane came before the Sales Order lane. So this field decides the order, and it's easy to forget.</FONT></P></LI><LI><P><FONT face="arial,helvetica,sans-serif"><STRONG><CODE>children</CODE></STRONG> (on nodes) — this is what draws the arrow to the next node. SO-1001 has <CODE>DEL-2001</CODE> as its child, so an arrow is drawn from SO-1001 to DEL-2001, and so on. Since <CODE>children</CODE> is written as an array, we can give more than one document ID here if the process branches into two paths.</FONT></P></LI><LI><P><FONT face="arial,helvetica,sans-serif"><STRONG><CODE>state</CODE></STRONG> (on nodes) — decides the color of the node:</FONT></P><UL class=""><LI><P><FONT face="arial,helvetica,sans-serif"><CODE>Positive</CODE> → green</FONT></P></LI><LI><P><FONT face="arial,helvetica,sans-serif"><CODE>Critical</CODE> → orange</FONT></P></LI><LI><P><FONT face="arial,helvetica,sans-serif"><CODE>Negative</CODE> → red</FONT></P></LI><LI><P><FONT face="arial,helvetica,sans-serif"><CODE>Neutral</CODE> → grey</FONT></P></LI></UL></LI><LI><P><FONT face="arial,helvetica,sans-serif"><STRONG><CODE>stateText</CODE></STRONG> (on nodes) — just the small label shown under the node (like "Completed" or "Overdue").</FONT></P></LI></UL><H4 id="toc-hId-505136283"><FONT face="arial,helvetica,sans-serif"><STRONG>Step 3: Bind the control in the view (View1.view.xml)</STRONG></FONT></H4></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV><pre class="lia-code-sample language-markup"><code>&lt;mvc:View controllerName="processflowcontrol.controller.View1" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:n="sap.suite.ui.commons"&gt; &lt;Page id="page" title="{i18n&gt;title}"&gt; &lt;Panel headerText="Order Fulfillment Status"&gt; &lt;n:ProcessFlow id="orderProcessFlow" scrollable="false" nodes="{process&gt;/nodes}" lanes="{process&gt;/lanes}" nodePress="onNodePressed"&gt; &lt;n:nodes&gt; &lt;n:ProcessFlowNode laneId="{process&gt;lane}" nodeId="{process&gt;id}" title="{process&gt;title}" titleAbbreviation="{process&gt;abbr}" children="{process&gt;children}" state="{process&gt;state}" stateText="{process&gt;stateText}" highlighted="{process&gt;highlighted}" /&gt; &lt;/n:nodes&gt; &lt;n:lanes&gt; &lt;n:ProcessFlowLaneHeader laneId="{process&gt;id}" text="{process&gt;label}" iconsrc="{process&gt;icon}" position="{process&gt;position}" /&gt; &lt;/n:lanes&gt; &lt;/n:ProcessFlow&gt; &lt;/Panel&gt; &lt;/Page&gt; &lt;/mvc:View&gt;</code></pre><P class=""><FONT face="arial,helvetica,sans-serif">So here we are simply binding the nodes and lanes arrays from our JSON model to the ProcessFlow control, and inside that giving the aggregation binding for ProcessFlowNode and ProcessFlowLaneHeader.</FONT></P><H4 id="toc-hId-308622778"><FONT face="arial,helvetica,sans-serif"><SPAN><STRONG><SPAN class=""><SPAN class="">Step 4: Handling Node Clicks (</SPAN><SPAN class="">View1.controller.js</SPAN><SPAN class="">)</SPAN></SPAN><SPAN class="">&nbsp;<BR /></SPAN></STRONG></SPAN></FONT></H4><pre class="lia-code-sample language-javascript"><code>sap.ui.define([ "sap/ui/core/mvc/Controller", "sap/m/MessageToast" ], (Controller, MessageToast) =&gt; { "use strict"; return Controller.extend("processflowcontrol.controller.View1", { onInit() { }, onNodePressed: function (oEvent) { var oNode = oEvent.getSource(); var sTitle = oNode.getTitle(); MessageToast.show("Node selected: " + sTitle); } }); });</code></pre><P class=""><FONT face="arial,helvetica,sans-serif">So, in the above code, whenever a user clicks on any node, the onNodePressed function will be called, and we get the clicked node using oEvent.getSource(). From there we can read its title, and any other property we need. I was a little confused initially whether to read the node details from getSource() or from the event parameters, but getSource() gave me the correct node object, so I used that.</FONT></P><P class=""><FONT face="arial,helvetica,sans-serif">Here I have just shown a MessageToast with the node title when clicked, but in an actual application, this is the place where we can navigate to the object page of that particular document.</FONT></P><H4 id="toc-hId--385607822"><FONT face="arial,helvetica,sans-serif"><SPAN>Output<BR /></SPAN></FONT></H4><P><FONT face="arial,helvetica,sans-serif"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="snehar11_0-1783939030603.png" style="width: 415px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/432402iEEE5774C5F725E8F/image-dimensions/415x230?v=v2" width="415" height="230" role="button" title="snehar11_0-1783939030603.png" alt="snehar11_0-1783939030603.png" /></span></FONT></P><DIV class=""><DIV><DIV><DIV><DIV><DIV><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><DIV class=""><H4 id="toc-hId--582121327"><FONT face="arial,helvetica,sans-serif"><STRONG>Conclusion:</STRONG></FONT></H4><P class=""><FONT face="arial,helvetica,sans-serif">So, this is how we can use the ProcessFlow control in SAPUI5 to show a chain of connected documents in a single, easy-to-understand picture. Instead of showing Sales Order, Delivery, and Invoice as separate rows in a table, we can show them together as one flow, with colors telling us the status of each document at a glance.We just need to give the control two things - lanes (the stages) and nodes (the documents) - in a simple JSON format, and the control takes care of the rest: drawing the lanes in order, placing the nodes correctly, drawing the arrows between them, coloring them based on status, and also giving us zoom and click functionality without any extra code.This same JSON structure is not limited to Sales Order-Delivery-Invoice - we can reuse it for any multi-step process, like a support ticket lifecycle, a purchase approval chain, or an onboarding process, just by changing the lane and node data.So this is a simple and quick way to build a document flow screen without writing our own timeline UI from scratch.</FONT></P></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV> 2026-07-21T10:56:11.351000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/creating-a-custom-action-to-export-selected-records-to-excel-using-fiori/ba-p/14432048 Creating a Custom Action to Export Selected Records to Excel Using Fiori Elements and CAP 2026-07-21T12:30:55.085000+02:00 Pranesh_SD https://community.sap.com/t5/user/viewprofilepage/user-id/2083024 <H3 id="toc-hId-1948155747"><STRONG>Introduction</STRONG></H3><P>While working on a Fiori Elements application, I came across a requirement that initially seemed straightforward but wasn't available as a standard feature. The application already had the built-in <STRONG>Export to Excel</STRONG> option, but it always exported every row displayed in the table. The business users wanted something slightly different—they wanted to select only a few records and export just those.</P><P>My first thought was to implement a backend action that generated an Excel file and returned it to the UI. After exploring the available options, I realized that wasn't necessary. SAPUI5 already provides a Spreadsheet API that can generate Excel files directly in the browser, making the implementation much simpler while avoiding additional backend development.</P><P>In this blog, I'll walk through the approach I used to add a custom <STRONG>Export Selected</STRONG> button to a Fiori Elements List Report. By the end, you'll have a working example that exports only the selected rows and integrates naturally with the standard Fiori toolbar.</P><H3 id="toc-hId-1751642242"><STRONG>Step 1: Initialize the CAP Project</STRONG></H3><P>First, create a new directory for your project and initialize a standard SAP CAP application. Open your terminal and run:</P><pre class="lia-code-sample language-abap"><code>mkdir employee cd employee cds init</code></pre><H3 id="toc-hId-1555128737"><STRONG>Step 2: Define the Database Schema</STRONG></H3><P>Next, we will define the data model for our employees.</P><P>Create a file named schema.cds inside the db folder and add the following code:</P><pre class="lia-code-sample language-abap"><code>// db/schema.cds namespace company.hr; entity Employees { key ID : UUID; name : String; department : String; role : String; email : String; }</code></pre><H3 id="toc-hId-1358615232"><STRONG>Step 3: Create the Service and UI Annotations</STRONG></H3><P>Now, expose the database entity as an OData service and add the basic Fiori UI annotations to define what columns will appear in our Fiori List Report table.</P><P>Create a file named cat-services.cds (typically in the srv folder) and add this code:</P><pre class="lia-code-sample language-abap"><code>// srv/cat-services.cds using { company.hr as db } from '../db/schema'; service EmployeeService { entity Employees as projection on db.Employees; } annotate EmployeeService.Employees with @( UI.LineItem : [ { $Type : 'UI.DataField', Label : 'Employee Name', Value : name }, { $Type : 'UI.DataField', Label : 'Department', Value : department }, { $Type : 'UI.DataField', Label : 'Role', Value : role }, { $Type : 'UI.DataField', Label : 'Email', Value : email } ] );</code></pre><H3 id="toc-hId-1162101727"><STRONG>Step 4: Add Mock Data</STRONG></H3><P>To test our application, we need some dummy data. SAP CAP automatically loads CSV files that match the namespace and entity.</P><P>Create a folder named data inside your db folder, then create a file named company.hr-Employees.csv and paste the following:</P><pre class="lia-code-sample language-abap"><code>ID;name;department;role;email 1;Alice Walker;Engineering;Backend Developer;alice@company.com 2;Bob Smith;Sales;Account Executive;bob@company.com 3;Charlie Davis;Engineering;Frontend Developer;charlie@company.com 4;Diana Prince;HR;HR Manager;diana@company.com</code></pre><H3 id="toc-hId-965588222"><STRONG>Step 5: Configure the Custom Action in the Page Map</STRONG></H3><P>Once your Fiori Elements application has been generated, the next step is to add a custom action to the table toolbar.</P><P class="">Follow these steps:</P><OL><LI>Right-click on the <STRONG>webapp</STRONG> folder in your project.</LI><LI>Select <STRONG>Show Page Map</STRONG>.</LI><LI>In the Page Map, select the <STRONG>List Report</STRONG> page.</LI><LI>Click the <STRONG>Edit</STRONG> (pencil) icon to modify the page.</LI><LI>Under the <STRONG>Table / Table Toolbar</STRONG> section, click the <STRONG>+</STRONG> button next to <STRONG>Actions</STRONG>.</LI><LI>Select <STRONG>Add Custom Action</STRONG>.</LI></OL><P class="">Configure the custom action with the following properties:</P><UL><LI><STRONG>Label:</STRONG> Export Selected</LI><LI><STRONG>Requires Selection:</STRONG> Enabled</LI><LI><STRONG>Press Event: </STRONG>exportSelected</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Pranesh_SD_4-1782987185397.png" style="width: 460px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428465i135C0E5F74ABCF26/image-dimensions/460x359?v=v2" width="460" height="359" role="button" title="Pranesh_SD_4-1782987185397.png" alt="Pranesh_SD_4-1782987185397.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Pranesh_SD_5-1782987201645.png" style="width: 460px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428466i973A571EE85266B9/image-dimensions/460x343?v=v2" width="460" height="343" role="button" title="Pranesh_SD_5-1782987201645.png" alt="Pranesh_SD_5-1782987201645.png" /></span></P><H3 id="toc-hId-769074717"><STRONG>Step 6: Implement the Custom UI5 Export Logic</STRONG></H3><P>Now, we will write the logic that extracts the selected rows and converts them to an Excel file using the Fiori V4 API.</P><P>Open the newly generated controller extension file (ListReportExt.js) and replace its contents with the following:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Pranesh_SD_6-1782987238201.png" style="width: 440px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428467iD19F6E30E98CF22F/image-dimensions/440x462?v=v2" width="440" height="462" role="button" title="Pranesh_SD_6-1782987238201.png" alt="Pranesh_SD_6-1782987238201.png" /></span></P><pre class="lia-code-sample language-abap"><code>sap.ui.define([ "sap/ui/export/Spreadsheet", "sap/m/MessageToast" ], function (Spreadsheet, MessageToast) { "use strict"; return { exportSelected: function (oEvent) { // 1. Access the Fiori V4 API var oExtensionAPI = this.editFlow ? this : this.getExtensionAPI(); // 2. Get only the rows selected by the user var aSelection = oExtensionAPI.getSelectedContexts(); if (!aSelection || aSelection.length === 0) { MessageToast.show("Please select at least one employee to export."); return; } // 3. Extract the raw JSON data from those selected rows var aData = aSelection.map(function (oContext) { return oContext.getObject(); }); // 4. Map the Excel columns to your CDS database fields var aColumns = [ { label: 'Employee Name', property: 'name' }, { label: 'Department', property: 'department' }, { label: 'Role', property: 'role' }, { label: 'Email', property: 'email' } ]; // 5. Configure the Excel Workbook properties var oSettings = { workbook: { columns: aColumns, context: { sheetName: 'Selected Employees' } }, dataSource: aData, fileName: "Custom_Employee_Export.xlsx", worker: false }; // 6. Build and Download the Excel file var oSheet = new Spreadsheet(oSettings); oSheet.build().then(function() { MessageToast.show("Excel export successful!"); }).catch(function(sMessage) { MessageToast.show("Export failed: " + sMessage); }).finally(function () { oSheet.destroy(); // Destroy object to prevent memory leaks in the browser }); } }; });</code></pre><H3 id="toc-hId-572561212"><STRONG>Step 7: Run and Test the Application</STRONG></H3><P>With the code in place, start your server by running:</P><pre class="lia-code-sample language-abap"><code>cds watch</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Pranesh_SD_7-1782987322562.png" style="width: 661px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428468i4DF882532B9FE367/image-dimensions/661x342?v=v2" width="661" height="342" role="button" title="Pranesh_SD_7-1782987322562.png" alt="Pranesh_SD_7-1782987322562.png" /></span></P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>&nbsp;</P><P>After running the application, select one or more employee records from the table. You'll notice that the <STRONG>Export Selected</STRONG> button is enabled only when records are selected. Clicking the button downloads an Excel file containing only the selected employees instead of the entire table.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Pranesh_SD_8-1782987471241.png" style="width: 664px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/428469i3245DD8F730DE447/image-dimensions/664x166?v=v2" width="664" height="166" role="button" title="Pranesh_SD_8-1782987471241.png" alt="Pranesh_SD_8-1782987471241.png" /></span></P><H3 id="toc-hId-376047707">&nbsp;</H3><H3 id="toc-hId-179534202">&nbsp;</H3><H3 id="toc-hId--92210672">&nbsp;</H3><H3 id="toc-hId--288724177">&nbsp;</H3><H3 id="toc-hId--485237682"><STRONG>Conclusion</STRONG></H3><P>What I liked most about this approach is that it keeps the implementation lightweight. Since the Excel file is generated in the browser using the SAPUI5 Spreadsheet library, there was no need to create a custom CAP action or write backend logic just for exporting data.</P><P>One thing I found useful was enabling <STRONG>Requires Selection</STRONG> for the custom action. Without it, users could click the button even when no rows were selected, resulting in unnecessary validation. Letting Fiori handle the button state provides a much better user experience.</P><P>Although this example exports employee data, the same approach can be used in almost any Fiori Elements application, such as Sales Orders, Purchase Orders, Expense Reports, or Products. If you've ever wanted to provide users with more control over what they export, this is a simple customization that's worth adding to your applications.</P> 2026-07-21T12:30:55.085000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/jsx-runtime-for-ui5-write-your-views-as-tsx-and-tell-us-what-you-think/ba-p/14445443 JSX Runtime for UI5: write your views as TSX, and tell us what you think 2026-07-21T14:36:39.304000+02:00 pmuessig https://community.sap.com/t5/user/viewprofilepage/user-id/42075 <H1 id="toc-hId-1691007042">JSX Runtime for UI5: write your views as TSX, and tell us what you think</H1><P>We've been experimenting with something and we'd like to put it in front of the community early: a <STRONG>JSX runtime for UI5</STRONG>. It lets you author UI5 views as <CODE>.tsx</CODE> files, with full per-control TypeScript typing, and it does so without a virtual DOM, and without a reconciler.</P><P>This is an <STRONG>incubation</STRONG>. It works, the showcase runs end-to-end, but the whole point of this post is to get it into your hands and hear back from you before we commit to more. If you write UI5 apps, we want your opinion.</P><H2 id="toc-hId-1623576256">What it actually does</H2><P>A JSX element compiles to a plain constructor call. That's the entire idea:</P><PRE><CODE>import Button from "sap/m/Button"; import Page from "sap/m/Page"; export default class Main extends View { createContent() { return ( &lt;Page title="Hello"&gt; &lt;Button text="Click me" press=".onPress" /&gt; &lt;/Page&gt; ); } }</CODE></PRE><P><CODE>&lt;Button text="Click me" /&gt;</CODE> becomes <CODE>new Button({ text: "Click me" })</CODE>. Nothing more magical than that. Babel emits the calls, the runtime hands them to UI5, and from there it's UI5 exactly as you already know it: metadata, <CODE>applySettings</CODE>, bindings, aggregations, events. There's no re-render loop, because a JSX expression is a constructor call, not a render description. Reactivity comes from UI5 bindings, same as in an XML view.</P><P>One thing to set expectations up front: this JSX describes the <STRONG>UI5 control tree, not the DOM</STRONG>. There's no native HTML. A&nbsp;<CODE>&lt;div&gt;</CODE> or <CODE>&lt;span&gt;</CODE> won't work, because a lowercase tag can't become a UI5 control. You compose controls (<CODE>&lt;Page&gt;</CODE>, <CODE>&lt;Button&gt;</CODE>, <CODE>&lt;VBox&gt;</CODE>, …), exactly as you do in an XML view, and UI5's renderers produce the actual markup. (An HTML-aware renderer is on the radar, but it's not here today.)</P><P>If you don't use TypeScript, plain <CODE>.jsx</CODE> works too. You still get the <CODE>new Control(settings)</CODE> calls, you just skip the author-time type checking.</P><H2 id="toc-hId-1427062751">Why we think it's worth a look</H2><UL><LI><STRONG>Per-control typing that we didn't hand-write.</STRONG> <CODE>&lt;Button&gt;</CODE> is typed from <CODE>Button</CODE>'s own <CODE>$ButtonSettings</CODE> interface, which <CODE>@openui5/types</CODE> already publishes from each control's metadata. Wrong prop names, wrong value types, wrong event payloads. The TypeScript checker catches them. No code generation step in the project.</LI><LI><STRONG>It's the control tree you already have.</STRONG> XML views and TSX views coexist in the same app. You can migrate one view at a time and keep everything runnable, or never migrate at all and just write new views in TSX.</LI><LI><STRONG>No fork of UI5.</STRONG> The runtime doesn't patch or replace anything in the core. It glues Babel's output into UI5's existing settings model.</LI><LI><STRONG>A small set of directives that read well.</STRONG> <CODE>&lt;Fragment&gt;</CODE> for grouping, <CODE>&lt;For&gt;</CODE> for bound aggregations, <CODE>&lt;If&gt;</CODE> for conditional rendering and falsy children (<CODE>{flag &amp;&amp; &lt;X/&gt;}</CODE>) just work without crashing an aggregation.</LI><LI><STRONG>A plugin SPI, if you want to extend it.</STRONG> Five extension points let libraries add their own directives and behaviors on top of the core without touching it. Everything the core itself does, the dot-handler events, <CODE>&lt;For&gt;</CODE>, <CODE>&lt;If&gt;</CODE>, is built on that same SPI, so nothing in the core is privileged. We ship a <CODE>&lt;Switch&gt;</CODE>/<CODE>&lt;Case&gt;</CODE>/<CODE>&lt;Default&gt;</CODE> sample plugin as a worked example.</LI></UL><H2 id="toc-hId-1230549246">Where to start</H2><P>The fastest way to get a feel for it is the <STRONG>live showcase</STRONG>.&nbsp;It's a running UI5 app that doubles as the documentation, with runnable samples for every concept:</P><P><span class="lia-unicode-emoji" title=":backhand_index_pointing_right:">👉</span><STRONG><A href="https://ui5-community.github.io/jsx-runtime/" target="_blank" rel="noopener nofollow noreferrer">https://ui5-community.github.io/jsx-runtime/</A></STRONG></P><P>The source, the setup guide, and the monorepo (the library, the showcase, and a minimal from-scratch reference app) are on GitHub:</P><P><span class="lia-unicode-emoji" title=":backhand_index_pointing_right:">👉</span><STRONG><A href="https://github.com/ui5-community/jsx-runtime" target="_blank" rel="noopener nofollow noreferrer">https://github.com/ui5-community/jsx-runtime</A></STRONG></P><P>If you already have an app with XML views, there's a step-by-step guide for converting them one at a time.</P><H2 id="toc-hId-1034035741">This is where you come in</H2><P>We're calling this an incubation on purpose. Before we invest further, we want to know whether it solves a real problem for you, where it feels awkward, and what's missing. Concretely, we'd love to hear:</P><UL><LI>Would you write new views in TSX? Would you migrate existing ones?</LI><LI>Does the typing carry its weight, or is it more ceremony than value?</LI><LI>What breaks, or what did you expect to work and it didn't?</LI><LI>What would you need before you'd use this in a real project?</LI></UL><P><STRONG>Please open an issue</STRONG> with anything: a bug, a rough edge, a "this is great," a "this is not for me and here's why":</P><P><span class="lia-unicode-emoji" title=":backhand_index_pointing_right:">👉</span><STRONG><A href="https://github.com/ui5-community/jsx-runtime/issues" target="_blank" rel="noopener nofollow noreferrer">https://github.com/ui5-community/jsx-runtime/issues</A></STRONG></P><P>Honest feedback, including the critical kind, is exactly what makes an incubation worth doing. Thanks for taking a look.</P> 2026-07-21T14:36:39.304000+02:00 https://community.sap.com/t5/technology-blog-posts-by-members/export-selected-rows-to-excel-in-sap-fiori-elements-using-custom-actions/ba-p/14444995 Export Selected Rows to Excel in SAP Fiori Elements Using Custom Actions 2026-07-22T08:23:34.383000+02:00 RaniMJ https://community.sap.com/t5/user/viewprofilepage/user-id/2082928 <P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Hello Everyone,</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P><SPAN>While working on a CAP-based SAP Fiori Elements application, I came across a requirement where users needed to export only the selected records from the List Report page instead of the entire dataset.</SPAN><SPAN>&nbsp;</SPAN></P><P><SPAN>SAP Fiori Elements&nbsp;provides&nbsp;a standard&nbsp;</SPAN><STRONG><SPAN>Export</SPAN></STRONG><SPAN>&nbsp;option that downloads all the records displayed in the table. However, there may be business scenarios where users need to export only specific rows that they have selected.</SPAN><SPAN>&nbsp;</SPAN></P><P><SPAN>In this blog,&nbsp;I'll&nbsp;show you how to create a custom action that exports only the selected rows to an Excel file using the&nbsp;</SPAN><STRONG><SPAN>sap.ui.export.Spreadsheet</SPAN></STRONG><SPAN>&nbsp;API.</SPAN><SPAN>&nbsp;</SPAN></P><P><STRONG><SPAN>Prerequisites</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P><SPAN>Before implementing this solution, ensure that:</SPAN><SPAN>&nbsp;</SPAN></P><UL><LI><SPAN>You have a CAP-based SAP Fiori Elements application.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL><LI><SPAN>Your List Report page is already displaying data.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL><LI><SPAN>You are using&nbsp;SAPUI5&nbsp;version that supports&nbsp;</SPAN><SPAN>sap.ui.export.Spreadsheet</SPAN><SPAN>.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL><LI><SPAN>You have created a Controller Extension for your List Report.</SPAN><SPAN>&nbsp;</SPAN></LI></UL><P><STRONG><SPAN>Step 1:&nbsp;</SPAN></STRONG><SPAN>Create a Custom Action Using a Controller Extension</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Add a handler function&nbsp;in&nbsp;the controller extension and update the&nbsp;</SPAN><SPAN>manifest.json</SPAN><SPAN>&nbsp;file to register the custom action.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>In this step, you create a controller extension to define a handler function for the custom action button and add the extension settings of the custom action to the&nbsp;manifest.json&nbsp;file.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>If a controller extension is already available for a page, you can copy the code and use it.</SPAN><SPAN>&nbsp;</SPAN></P><P><SPAN><SPAN class=""><SPAN class="">Add the following configuration to the&nbsp;</SPAN></SPAN><SPAN class=""><SPAN class="">manifest.json</SPAN></SPAN><SPAN class=""><SPAN class="">&nbsp;file:</SPAN></SPAN><SPAN class="">&nbsp;</SPAN></SPAN></P><pre class="lia-code-sample language-javascript"><code>"controlConfiguration": { "@com.sap.vocabularies.UI.v1.LineItem": { "tableSettings": { "type": "ResponsiveTable" }, "actions": { "controller1": { "press": "project1.ext.controller.controller1.exportSelectedRowData", "visible": true, "enabled": true, "requiresSelection": true, "text": "Export Selected Rows of Data" } } } }</code></pre><P><SPAN class=""><SPAN class=""><STRONG>Step 2</STRONG>:<SPAN>&nbsp;</SPAN></SPAN></SPAN><SPAN class=""><SPAN class="">Implement the Export Logic in the Controller Extension</SPAN></SPAN><SPAN class="">&nbsp;<BR /></SPAN></P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([ "sap/m/MessageToast", "sap/ui/export/Spreadsheet", "sap/ui/export/library" ], function(MessageToast, Spreadsheet, exportLibrary) { 'use strict'; const EdmType = exportLibrary.EdmType; return { /** * Generated event handler. * * <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1387962">@Param</a> oContext the context of the page on which the event was fired. `undefined` for list report page. * <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1387962">@Param</a> aSelectedContexts the selected contexts of the table rows. */ exportSelectedRowData:function(oContext, aSelectedContexts) { // 1. Guard clause: Ensure there are selected rows if (!aSelectedContexts || aSelectedContexts.length === 0) { MessageToast.show("Please select at least one row to export."); return; } // 2. Extract the raw JSON data from the selected contexts const aSelectedData = aSelectedContexts.map(function(oRowContext) { return oRowContext.getObject(); }); // 3. Define the columns for the Excel file const aColumns = [ { label: 'Department ID', property: 'ID', type: EdmType.String }, { label: 'Department Name', property: 'deptName', type: EdmType.String }, { label: 'Location', property: 'location', type: EdmType.String } ]; // 4. Configure the Spreadsheet settings const mSettings = { workbook: { columns: aColumns, context: { sheetName: 'Selected Row Data Sheet' } }, dataSource: aSelectedData, fileName: 'Selected_Departments.xlsx', worker: false // Set to false because we are passing client-side JSON data directly }; // 5. Generate and download the Excel file const oSheet = new Spreadsheet(mSettings); oSheet.build() .then(function() { MessageToast.show("Exported selected rows successfully."); }) .catch(function(sMessage) { MessageToast.show("Export failed: " + sMessage); }) .finally(function() { // Always destroy the spreadsheet instance to prevent memory leaks oSheet.destroy(); }); } }; });</code></pre><P><STRONG><SPAN>Now&nbsp;let's&nbsp;understand how the export functionality works internally</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 1: Import the Required Libraries</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Import the required SAPUI5 libraries for Excel export.</SPAN><SPAN>&nbsp;</SPAN></P><UL class="lia-align-justify" style="text-align : justify;"><LI><STRONG><SPAN>sap/ui/export/Spreadsheet</SPAN></STRONG><SPAN>&nbsp;– Used to create and generate Excel files.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><UL class="lia-align-justify" style="text-align : justify;"><LI><STRONG><SPAN>sap/ui/export/library</SPAN></STRONG><STRONG><SPAN>&nbsp;</SPAN></STRONG><SPAN>– Provides export-related data types such as&nbsp;</SPAN><SPAN>EdmType</SPAN><SPAN>, which are used while defining the Excel columns.&nbsp;</SPAN><SPAN>&nbsp;</SPAN></LI></UL><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 2: User Selects Rows</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The user selects one or more rows from the List Report table and clicks the&nbsp;</SPAN><STRONG><SPAN>Export Selected Rows of Data</SPAN></STRONG><SPAN>&nbsp;button. This action triggers the&nbsp;</SPAN><STRONG><SPAN>exportSelectedRowData()</SPAN></STRONG><SPAN>&nbsp;function.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 3:&nbsp;Validate&nbsp;the Selection</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The application checks whether at least one row has been selected.&nbsp;If no rows are selected, a message is displayed to the user, and the export process is stopped.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 4: Convert Context Objects to JSON</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The selected rows are received as binding context objects. Using the&nbsp;</SPAN><STRONG><SPAN>getObject()</SPAN></STRONG><SPAN>&nbsp;method, each context is converted into a plain JavaScript object. These objects are then stored in an array, which serves as the data source for the Excel export.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 5: Define the Excel Columns</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The application defines the structure of the Excel file by specifying the column headers (</SPAN><SPAN>label</SPAN><SPAN>), the corresponding data properties (</SPAN><SPAN>property</SPAN><SPAN>), and the data type (</SPAN><SPAN>EdmType</SPAN><SPAN>).</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 6: Configure the Spreadsheet</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>A Spreadsheet configuration object is created by specifying the workbook details, sheet name, column definitions, data source, and output file name. These settings&nbsp;determine&nbsp;how the Excel file will be generated.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 7: Generate the Excel File</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>A new&nbsp;</SPAN><SPAN>Spreadsheet</SPAN><SPAN>&nbsp;instance is created using the configuration object. The&nbsp;</SPAN><SPAN>build()</SPAN><SPAN>&nbsp;method then generates the Excel workbook and automatically downloads it to the user's system.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 8: Handle Success or Errors</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The application displays a&nbsp;success&nbsp;message if the export is completed successfully.&nbsp;If an error occurs during the export process, an appropriate error message is displayed.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Step 9: Destroy the Spreadsheet Object</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Finally, the Spreadsheet object is destroyed to release the resources occupied by it and prevent memory leaks.</SPAN><SPAN>&nbsp;</SPAN></P><P><STRONG>Output</STRONG><SPAN>&nbsp;</SPAN></P><P><STRONG><SPAN>Selecting the Required Rows</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>The user selects one or more rows from the List Report and clicks the&nbsp;</SPAN><STRONG><SPAN>Export Selected Rows of Data</SPAN></STRONG><SPAN>&nbsp;button.</SPAN><SPAN>&nbsp;</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RaniMJ_1-1784617374216.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/435330i1B951879D057E704/image-size/medium?v=v2&amp;px=400" role="button" title="RaniMJ_1-1784617374216.png" alt="RaniMJ_1-1784617374216.png" /></span></P><P><STRONG><SPAN>Excel Export in Progress</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P><SPAN>After clicking the export button, the Spreadsheet API generates the Excel document. Once the process is completed, a&nbsp;success&nbsp;message is displayed.</SPAN><SPAN>&nbsp;</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RaniMJ_2-1784617374217.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/435331i83E59923819E60F9/image-size/medium?v=v2&amp;px=400" role="button" title="RaniMJ_2-1784617374217.png" alt="RaniMJ_2-1784617374217.png" /></span></P><P><SPAN>&nbsp;</SPAN><STRONG><SPAN>Downloaded Excel File</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P><SPAN>The generated Excel file is automatically downloaded to the user's local system with the configured file name (</SPAN><SPAN>Selected_Departments.xlsx</SPAN><SPAN>).</SPAN><SPAN>&nbsp;</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RaniMJ_3-1784617374220.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/435332i8D0B493A99A252CD/image-size/medium?v=v2&amp;px=400" role="button" title="RaniMJ_3-1784617374220.png" alt="RaniMJ_3-1784617374220.png" /></span></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><SPAN>Conclusion</SPAN></STRONG><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>In this blog, we learned how to implement a custom action in SAP Fiori Elements that exports only the selected rows from the List Report into an Excel file.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Unlike the standard&nbsp;</SPAN><STRONG><SPAN>Export</SPAN></STRONG><SPAN>&nbsp;functionality, which exports all records, this approach gives users greater flexibility by allowing them to download only the data they need.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Since the implementation uses the&nbsp;</SPAN><SPAN>sap.ui.export.Spreadsheet</SPAN><SPAN>&nbsp;API, it can be easily reused across different SAP Fiori Elements applications with minimal changes to the column configuration.</SPAN><SPAN>&nbsp;</SPAN></P><P class="lia-align-justify" style="text-align : justify;"><SPAN>Thank you for reading! I hope you found this blog helpful and that it helps you implement a similar requirement in your own projects.</SPAN></P><P><SPAN>Happy Coding!</SPAN><SPAN>&nbsp;</SPAN></P> 2026-07-22T08:23:34.383000+02:00 https://community.sap.com/t5/technology-blog-posts-by-sap/recap-2026-recordings-of-our-annual-cap-developer-conference/ba-p/14446111 reCAP 2026 – Recordings of our Annual CAP Developer Conference 2026-07-22T12:07:54.874000+02:00 BirgitS https://community.sap.com/t5/user/viewprofilepage/user-id/41902 <P><SPAN>On&nbsp;15 July 2026, the CAP community came together for&nbsp;reCAP 2026&nbsp;in&nbsp;St. Leon-Rot. Again, tickets were sold out within just a few minutes. We had more than&nbsp;400 participants onsite&nbsp;and many more joining online.</SPAN></P><P><SPAN>For everyone who couldn’t attend live, most sessions were recorded. Below is a summary of the recorded sessions, along with links to the recordings. You can find all recordings on <A href="https://www.youtube.com/@reCAP_unconference" target="_blank" rel="noopener nofollow noreferrer">YouTube</A>.</SPAN></P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Entrance of the building where reCAP took place" style="width: 750px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/435982iE1D020350C44B8E6/image-size/large?v=v2&amp;px=999" role="button" title="reCAP2026.png" alt="reCAP2026.png" /></span><BR /></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-1820116574"><SPAN>Opening &amp; Keynote</SPAN></H2><P><STRONG><SPAN>Intro </SPAN></STRONG><SPAN><EM>by&nbsp;Ole Lilienthal</EM><BR />Setting the stage, Ole gave an overview of the Autonomous Enterprise and how CAP plays a key role in enabling the SAP Business AI platform as the underlying platform for the Autonomous Enterprise. He also explained the importance of CAP in the age of AI. &nbsp;</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=qzsbXd9VUwY" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Keynote</SPAN></STRONG><SPAN>&nbsp;<EM>by&nbsp;Daniel Hutzel</EM><BR />In his keynote, Daniel shared an overview of the biggest CAP achievements since the last reCAP, including AI-related topics.<BR /></SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=mRs3oQPOcgM" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-1623603069"><SPAN>Main Track Sessions in Audimax</SPAN></H2><P><STRONG><SPAN>CAP Tools – What’s New and Hot </SPAN></STRONG><EM><SPAN>by Christian Georgi</SPAN></EM><SPAN><BR />A tour through the latest and greatest in CAP tooling, including:</SPAN></P><UL><LI><SPAN>`cds repl` with queries</SPAN></LI><LI><SPAN>Debugging apps on Kyma</SPAN></LI><LI><SPAN>CDS Project outline in Visual Studio Code</SPAN></LI><LI><SPAN>CAP in browser</SPAN></LI></UL><P><SPAN><A href="https://www.youtube.com/watch?v=B3qQJ6b3tcg&amp;pp=0gcJCZsLAYcqIYzv" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Optimizing CAP Performance with Data Replication and Caching – Part II </SPAN></STRONG><EM><SPAN>by Mike Zaschka</SPAN></EM><SPAN><BR />As a follow-up to last year’s talk on&nbsp;cds-caching, this session broadened the view to the&nbsp;full landscape of caching, replication, and federation&nbsp;strategies in the CAP ecosystem. Mike walked through a curated selection of solutions and offered practical guidance on which approach fits which scenario.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=pUlnHE8iDNk" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>AI-Assisted Development of CAP Apps </SPAN></STRONG><EM><SPAN>by Daniel Schlachter and Stefan Rudi</SPAN></EM><SPAN><BR />A look at the&nbsp;CAP MCP development server, which gives AI coding assistants direct access to CAP documentation improving code quality in tools like&nbsp;<EM>Claude Code</EM>&nbsp;and&nbsp;<EM>GitHub Copilot</EM>. </SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=FQON2eNn0OY" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Expressions and Abstractions – Two Developer Superpowers in CAP </SPAN></STRONG><EM><SPAN>by Patrice Bender and DJ Adams</SPAN></EM><SPAN><BR />A demonstration of two powerful CAP affordances:&nbsp;expressions&nbsp;(what they are, how to use them, and why they matter) and&nbsp;mocking&nbsp;(letting you iterate fast on data, auth, messaging, and remote services).</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=51Uuj2dGEtE" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>CAP Plugins: Latest and Greatest </SPAN></STRONG><EM><SPAN>by Max Eckert, Dries Van Vaerenbergh, Matthis Vansteenhuyse, and Lisa Julia Nebel</SPAN></EM><SPAN><BR />CAP plugins dramatically simplify integration with SAP BTP services. This session demystified the plugin model and spotlighted the enhanced&nbsp;attachments plugin, the renovated&nbsp;change tracking plugin, a new&nbsp;notifications plugin for Java, and the new&nbsp;SAP Build Process Automation plugin.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=XDQ2FHrQtPc&amp;pp=0gcJCZsLAYcqIYzv" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Plug, Connect, Play: CAP Integration Made Effortless </SPAN></STRONG><EM><SPAN>by Ram Prasad GS and Bilal El-Massoudi</SPAN></EM><SPAN><BR />An introduction to&nbsp;cap-connect, a production-ready, open-source CAP plugin from Aarini Consulting that brings first-class connection management to any CAP application. The talk also showcased how cap-connect powers&nbsp;<EM>Prodsphere</EM>, Aarini’s own CAP-based product.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=U0U7ixe8Cpg" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Production GenAI in CAP: Beyond the Demo </SPAN></STRONG><EM><SPAN>by Moudhaffer Azizi</SPAN></EM><SPAN><BR />This session explored the architecture behind a production-grade CAP application on SAP BTP that uses RAG to analyse custom ABAP code against Clean Core standards. The session highlighted the journey from a single prompt to a resilient solution orchestrating 14 AI-powered operations. </SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=eFifbqiYQh0" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Beyond Vibe Coding: Engineering Deterministic CAP Applications with SDD </SPAN></STRONG><EM><SPAN>by Maximilian Hartig and Abdulbasıt Gülşen</SPAN></EM><SPAN><BR />A live "0-to-1" walkthrough of&nbsp;Spec-Driven Development&nbsp;using the open-source&nbsp;Spec Kit&nbsp;in Visual Studio Code to transform a raw business requirement into a CAP application.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=E6F7FFMO3Js" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>From Click-Ops to Code-Ops (Terraform) </SPAN></STRONG><EM><SPAN>by Nicholas Arefta</SPAN></EM><SPAN><BR />Nicholas demonstrated how to use Terraform to deploy multitenant apps on SAP BTP. He demonstrated provisioning of a Provider subaccount, deploying a multi-tenant application and onboarding a Subscriber completely hands-free.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=u8U4r3zTmTk" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Custom Business Logic with Predefined Extension Points – Using CDS-Oyster </SPAN></STRONG><EM><SPAN>by Nick Josipovic and Konrad Koschel</SPAN></EM><SPAN><BR />Together with Subscription Billing and Cloud Foundation, Nick and Konrad built an extensive&nbsp;Extension Box&nbsp;using CAP and the&nbsp;CDS-Oyster Code Sandbox&nbsp;enabling secure code extensions in a complex cloud environment.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=eqmgy6GqBMM" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Implementing Runtime Plugins in CAP Java </SPAN></STRONG><EM><SPAN>by Joshua Mitchell</SPAN></EM><SPAN><BR />Joshua demonstrated in his live session how to program a basic&nbsp;soft delete plugin&nbsp;for CAP Java using annotations, the IntelliJ debugger, and an expression evaluator.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=uhN6UXiPp7w" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-1427089564"><SPAN>Side Track Sessions in W1/W2</SPAN></H2><P><STRONG><SPAN>Building Code-Based AI Agents with CAP and UI5: From Concept to Production </SPAN></STRONG><SPAN><EM>by</EM> <EM>Wouter Lemaire</EM></SPAN></P><P><SPAN>Using the Smart Monitoring Agent (previously demonstrated at SAP TechEd) as a real-world example, Wouter showed how to build intelligent, code-based AI agents by combining SAP AI Core with CAP and UI5. The session covered the complete journey from development to deployment. </SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=EIT4XRrACLQ" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Building AI-enabled Applications with CAP </SPAN></STRONG><SPAN><EM>by Max Eckert, Simon Engel, Markus Riedinger, and Peter W. Szabo</EM></SPAN></P><P><SPAN>An overview of how CAP is evolving to meet the AI era including: exposing CAP services as endpoints for AI agents, leveraging the CAP AI plugin, exploring additional features, and building intelligent, code-driven agents. </SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=Pf6qaC56cl0" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Building a CAP Application with Latest Best Practices and AI </SPAN></STRONG><SPAN><EM>by Lukas Theis, Johannes Vogt, Marc Becker, and Robin de Silva Jayasinghe</EM></SPAN></P><P><SPAN>A practical, end-to-end walkthrough from idea to production-ready CAP app, combining proven CAP architecture patterns with AI-assisted workflows. </SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=lFZN0YA5dG0&amp;pp=0gcJCZsLAYcqIYzv" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Declarative Programming Patterns in CAP - Less Code, More Power </SPAN></STRONG><SPAN><EM>by Adrian Görler, Robin de Silva Jayasinghe, and Vitaly Kozyura</EM></SPAN></P><P><SPAN>A look at how declarative mechanisms in CAP such as calculated elements, annotation-based constraints (e.g. @assert, @assert.range, @assert.format), and field control help to reduce custom code. The session also covered how status transition flows help reducing the need for custom validation logic while ensuring data integrity.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=fY_zMS0Il2E" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Update from the cap-js-community - Experience the latest additions to CAP </SPAN></STRONG><SPAN><EM>by Oliver Klemenz</EM></SPAN></P><P><SPAN>A focused technical update on the latest enhancements in the cap-js-community, with a deep dive into the WebSocket modules and the newly introduced common utilities module.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=BkyW8e4a_xU" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>AI-Assisted Security Hardening of CAP Applications</SPAN></STRONG><SPAN><EM> by Matthias Braun</EM></SPAN></P><P><SPAN>Matthias showed how AI-powered tools can help identify authorization gaps, generate comprehensive security tests, and detect attack vectors. This significantly reduces the effort required to build and maintain secure CAP applications. </SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=uqjjxnAFgeA" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Event Queues Scheduling&nbsp;</SPAN></STRONG><SPAN><EM>by&nbsp;Sebastian Van Syckel, Dietrich Mostowoj, Thomas Bonk, and Lars Plessing<BR /></EM>A deep dive into Transactional Event Queues, which allow scheduling of events and background tasks for asynchronous, exactly-once processing with strong resilience.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=54reW0ZdxLI" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Automated Testing with HANA Cloud&nbsp;</SPAN></STRONG><SPAN><EM>by&nbsp;Armin Hatting</EM><STRONG><BR /></STRONG>Armin presented practical approaches to integrating database-supported testing in CAP applications, setting up realistic test environments, and ensuring the functionality of SAP HANA Cloud native logic.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=7ypZydfAG1c" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P><STRONG><SPAN>Did you know about infix filters and projection functions?</SPAN></STRONG><SPAN><EM> by Nico Schoenteich</EM></SPAN></P><P><SPAN>A live-coding overview of infix filters and projection functions (Node.js) and how they were applied in a recent project to reduce custom service.</SPAN></P><P><SPAN><A href="https://www.youtube.com/watch?v=z6jyo82-Tgs" target="_blank" rel="noopener nofollow noreferrer"><EM>Recording link</EM></A></SPAN></P><P>&nbsp;</P><H2 id="toc-hId-1230576059"><SPAN>Thank You </SPAN></H2><P><SPAN>A huge thank-you to all speakers, attendees, and community members who made reCAP 2026 such an inspiring day. </SPAN><STRONG><span class="lia-unicode-emoji" title=":raising_hands:">🙌</span></STRONG><SPAN>&nbsp;The energy, the ideas, and the conversations are exactly what makes the CAP community special.</SPAN></P><P><SPAN>If you missed any session, use the recording links above to catch up. </SPAN></P><P><SPAN>We look forward to seeing how you use what you learned at reCAP 2026. If you have any feedback, please let us know. </SPAN></P><P><SPAN>Happy coding! </SPAN><span class="lia-unicode-emoji" title=":smiling_face_with_smiling_eyes:">😊</span></P> 2026-07-22T12:07:54.874000+02:00