https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/topics/JavaScript-blog-posts.xmlSAP Community - JavaScript2026-02-09T00:10:46.527762+00:00python-feedgenJavaScript blog posts in SAP Communityhttps://community.sap.com/t5/technology-blog-posts-by-members/1-2-performance-tuning-in-sapui5/ba-p/141387351/2 Performance Tuning in SAPUI52025-07-02T14:07:47.915000+02:00PetrBeckahttps://community.sap.com/t5/user/viewprofilepage/user-id/152296<H2 id="toc-hId-1733371014"><STRONG>Why Performance Tuning Matters</STRONG></H2><P class="">Performance optimization is a critical aspect of SAPUI5 application development. In enterprise apps, users often deal with large datasets and complex UIs – without tuning, these can make the app sluggish or unresponsive . For example, displaying thousands of table rows or multiple complex charts can significantly slow down both loading and runtime response. Such sluggish performance leads to user frustration and lower productivity . Therefore, it’s important to understand why performance can degrade (large data volume, heavy UI components) and proactively address it. The goal is to ensure a <SPAN class=""><STRONG>fast, fluid user experience</STRONG></SPAN>, even as the app’s data and UI complexity grow.</P><P class=""><SPAN class=""><STRONG>Why do SAPUI5 apps slow down?</STRONG></SPAN> Two common reasons are: <SPAN class=""><STRONG>(a)</STRONG></SPAN> Loading or rendering <SPAN class=""><STRONG>too much data at once</STRONG></SPAN>, and <SPAN class=""><STRONG>(b)</STRONG></SPAN> Using <SPAN class=""><STRONG>very complex UI controls or deep component trees</STRONG></SPAN>. If an app tries to fetch and show all available data in one go, or if the UI creates hundreds of DOM elements upfront, the browser can become overwhelmed. The result is long initial load times (sometimes tens of seconds) and laggy interactions. Users today expect snappy responses, so we must optimize SAPUI5 apps to meet those expectations. In the next sections, we’ll look at key areas to focus on for performance tuning.</P><P class=""> </P><H2 id="toc-hId-1536857509"><STRONG>Key Areas for Performance Optimization</STRONG></H2><P class="">To improve SAPUI5 app performance, we should focus on several strategies in the app’s design and implementation. Below are the main areas to address:</P><UL><LI><P class=""><SPAN class=""><STRONG>Lazy Loading for Data-Heavy Controls</STRONG></SPAN> – Load data or UI components on demand instead of all at once.</P></LI><LI><P class=""><SPAN class=""><STRONG>Optimizing OData Requests</STRONG></SPAN> – Fetch only what you need (projection) and reduce round-trips (batching).</P></LI><LI><P class=""><SPAN class=""><STRONG>Caching Data and Models</STRONG></SPAN> – Reuse already loaded data/models and leverage caching to avoid redundant fetches.</P></LI><LI><P class=""><SPAN class=""><STRONG>Minimizing DOM Operations</STRONG></SPAN> – Use efficient data binding and avoid excessive direct DOM manipulation.</P></LI><LI><P class=""><SPAN class=""><STRONG>Performance Measurement & Tools</STRONG></SPAN> – Continuously measure performance using available tools to catch bottlenecks.</P></LI></UL><P class="">Let’s dive into each area with practical tips and examples.</P><P class=""> </P><H3 id="toc-hId-1469426723"><STRONG>1. Lazy Loading for Data-Heavy Controls</STRONG></H3><P class="">When dealing with large collections of data, <SPAN class=""><STRONG>lazy loading</STRONG></SPAN> is essential. Instead of rendering hundreds of items upfront, load only what’s needed and fetch more on demand. SAPUI5 provides a built-in mechanism for this via the “growing” feature on list and table controls. As the official documentation states: <I>“A growing list has a loading mechanism that requests data from the model in a lazy way,”</I> fetching additional items only as necessary . This means the app initially loads a small set of records, and as the user scrolls or requests more, additional data is loaded, improving initial load performance.</P><P class="">For example, you can enable lazy loading on a table by setting the <SPAN class=""><STRONG>growing</STRONG></SPAN> property:</P><pre class="lia-code-sample language-markup"><code><!-- Example: A table that loads 20 items at a time, loading more as needed -->
<Table id="productTable"
growing="true"
growingThreshold="20"
growingScrollToLoad="true"
items="{/Products}">
<!-- Define columns ... -->
<items>
<ColumnListItem>
<cells>
<Text text="{Name}" />
<Text text="{Price}" />
<!-- other cells ... -->
</cells>
</ColumnListItem>
</items>
</Table></code></pre><P class="">In the snippet above, the table will initially display 20 items (the <SPAN class="">growingThreshold</SPAN>). As the user scrolls down (with <SPAN class="">growingScrollToLoad="true"</SPAN>, it will auto-load more; if set to false, a “More” button is shown instead). This lazy loading approach dramatically improves performance by not loading all data at once. It’s especially beneficial for very large datasets – the app remains responsive by only fetching data <SPAN class=""><STRONG>“as and when necessary”</STRONG></SPAN> instead of upfront.</P><P class="">Lazy loading isn’t limited to tables; you can also <SPAN class=""><STRONG>lazy load views, fragments or components</STRONG></SPAN>. For instance, deferring the loading of a view until the user navigates to it can speed up the initial app load. In SAPUI5 routing, you might use asynchronous loading of views or the <SPAN class="">ComponentContainer</SPAN> with dynamic component loading on demand. The principle is: <SPAN class=""><STRONG>load heavy content only when needed</STRONG></SPAN>, keeping the initial payload light.</P><P class=""> </P><H3 id="toc-hId-1272913218"><STRONG>2. Optimize OData Requests (Batching and Projection)</STRONG></H3><P class="">Frequent or heavy OData calls can be a major performance bottleneck. Each network request has overhead, and pulling large payloads (with many fields or records) slows down the app. To optimize data requests in SAPUI5, consider these techniques:</P><UL><LI><P class=""><SPAN class=""><STRONG>Batch multiple OData calls into one</STRONG></SPAN>: If your page needs data from several OData endpoints, use OData $batch to combine them into a single HTTP request. This reduces the number of round trips to the server. In OData V2 Model, you can enable batch mode by setting <SPAN class="">useBatch:true</SPAN> when creating the model. In fact, SAPUI5’s OData V4 model does this by default – it <I>“collects all requests made to the OData service in a batch request to reduce the number of roundtrips”</I> . Fewer requests mean less latency and quicker overall data retrieval.</P></LI></UL><pre class="lia-code-sample language-javascript"><code>// Initializing an OData V2 model with batch mode enabled and other optimizations
var oModel = new sap.ui.model.odata.v2.ODataModel("/sap/opu/odata/sap/YourService/", {
useBatch: true, // combine multiple calls into one batch
defaultCountMode: "None" // disable $count calls if not needed for lists
});
this.getView().setModel(oModel);</code></pre><P class="">In the code above, we enable batch mode. We also set <SPAN class="">defaultCountMode:"None"</SPAN>, which prevents automatic <SPAN class="">$count</SPAN> requests for list bindings (since counting all records can be expensive on the backend ). These settings help cut down unnecessary network calls.</P><UL><LI><P class=""><SPAN class=""><STRONG>Use projection ($select) to limit fields</STRONG></SPAN>: Only request the data you actually need. By default, an OData entity request might return all properties, many of which your UI might not display. This wastes bandwidth and parsing time. Instead, specify a <SPAN class="">$select</SPAN> query option to retrieve only the necessary fields (projection). For example, if you only need product name and price, don’t fetch the entire product entity including large descriptions or images. As a best practice, <I>“ensure that you only retrieve the necessary data… utilize OData services efficiently by specifying $select and $expand options.”</I> . This avoids <SPAN class=""><STRONG>over-fetching</STRONG></SPAN> data that will never be used on the client.</P></LI></UL><pre class="lia-code-sample language-javascript"><code>// Example: fetching only specific fields using $select
oModel.read("/Products", {
urlParameters: { "$select": "Name,Price,Stock" },
success: function(oData) { /* ... */ }
});</code></pre><P class="">In the snippet above, the OData request will only return the Name, Price, and Stock properties of each Product, rather than the full product data structure. This leaner payload can significantly improve network performance.</P><UL><LI><P class=""><SPAN class=""><STRONG>Filter and paginate on the server</STRONG></SPAN>: Similar to lazy loading on the UI, use OData query options like <SPAN class="">$filter</SPAN>, <SPAN class="">$top</SPAN>, and <SPAN class="">$skip</SPAN> to have the server send only a slice of data. For instance, if a user needs to see records for a specific category, using <SPAN class="">$filter</SPAN> on the OData request means the server does the heavy lifting and sends back only relevant records, reducing client work. Likewise, implement server-side paging: request the first N records with <SPAN class="">$top</SPAN>, and load more on demand (many SAPUI5 controls with growing support will automatically append <SPAN class="">$skip/$top</SPAN> parameters when bound to an OData list). By <SPAN class=""><STRONG>filtering at the source and paging results</STRONG></SPAN>, you minimize data transfer and client-side processing .</P></LI><LI><P class=""><SPAN class=""><STRONG>Consider OData Model version</STRONG></SPAN>: If possible, use the <SPAN class=""><STRONG>OData V4 model</STRONG></SPAN> for new developments, as it has performance improvements over V2 . OData V4 not only batches requests by default, but also handles $expand more efficiently and has a reduced memory footprint on the client. If you are using OData V2, apply the above suggestions and also ensure metadata loading is optimized (see caching below).</P></LI></UL><P class="">In summary, treat your OData calls with care: batch them, narrow them (select only needed fields), and avoid pulling huge datasets in one go. Your app will load faster with fewer and lighter requests.</P><P class=""> </P><H3 id="toc-hId-1076399713"><STRONG>3. Caching Data and Models</STRONG></H3><P class="">Caching is a powerful technique to enhance performance. By caching, we mean reusing data that has already been loaded, instead of fetching it again from the source. There are a few layers of caching relevant to SAPUI5 apps:</P><UL><LI><P class=""><SPAN class=""><STRONG>OData Metadata Caching</STRONG></SPAN>: When an SAPUI5 app starts, the OData service’s metadata (the <SPAN class="">$metadata</SPAN> XML) is usually fetched. This is needed to understand the data model but can be a bulky request. To speed up app launch, SAP Fiori Launchpad and SAPUI5 support metadata caching using cache tokens. <I>“OData metadata is cached on the web browser using cache tokens,”</I> which append a <SPAN class="">sap-context-token</SPAN> to the metadata request URL . This ensures that after the first load, subsequent loads use the cached metadata, avoiding repeated downloads of the service definition. If you run your app standalone, you can still leverage this by enabling caching on the server or using the manifest property <SPAN class="">"preload": true</SPAN> for models to preload metadata at startup . Always check via browser dev tools that metadata requests are returning HTTP 304 (not modified) or using cache tokens appropriately.</P></LI><LI><P class=""><SPAN class=""><STRONG>Model and Data Caching</STRONG></SPAN>: For data payloads, consider caching frequently used data in a client-side model. For example, if you have lookup data or reference lists that rarely change, load them once and store them in a JSONModel or in the ODataModel so it can be reused across views. SAPUI5’s models typically cache data by default (the ODataModel keeps an in-memory cache of fetched entities). You can further take advantage by <SPAN class=""><STRONG>reusing model instances</STRONG></SPAN>. Instead of creating a new ODataModel for each view or component, instantiate one ODataModel at application startup (in your Component.js) and share it across the app (set it on the core or component). This way, if View A already loaded some data, when View B requests the same data, it can be served from the model’s cache without another round trip. Also, when navigating back to a view, the data is still present.</P></LI><LI><P class=""><SPAN class=""><STRONG>Browser Caching for static resources</STRONG></SPAN>: Ensure that app resources (JS/CSS files, theme assets, libraries) are served with caching headers so that the browser caches them. SAPUI5 framework resources are usually served with cache-busting hash parameters (to invalidate the cache on new versions). You as a developer should also bundle and minify your own resources (e.g., use the UI5 build tooling to create Component-preload.js). By bundling and minifying, you reduce the number of requests and size of files, which the browser can then cache for subsequent visits . This dramatically decreases load times on repeat visits because most files are loaded from cache rather than network.</P></LI></UL><P class="">In practice, caching is often handled for you (browser caching static files, ODataModel caching data in memory, etc.), but you should be mindful to <SPAN class=""><STRONG>enable</STRONG></SPAN> it and <SPAN class=""><STRONG>leverage</STRONG></SPAN> it. For example, don’t unnecessarily reload data that you already have. If you know a particular dataset changes rarely, you might even cache it in <SPAN class="">window.localStorage</SPAN> for persistence. By utilizing both client-side and server-side caching, you can <SPAN class=""><STRONG>decrease load times for repeat visits</STRONG></SPAN> significantly .</P><P class=""> </P><H3 id="toc-hId-879886208"><STRONG>4. Minimize DOM Operations with Efficient Data Binding</STRONG></H3><P class="">Manipulating the DOM (Document Object Model) is one of the most expensive operations in a web app. Each time you add, remove, or change a UI element, the browser may re-compute layouts and paint updates, which can be slow if done excessively. In SAPUI5, you should let the framework’s data binding mechanism handle as much of the UI update work as possible, and avoid direct DOM manipulation or heavy re-rendering in your code.</P><P class="">Here are some best practices to reduce DOM overhead:</P><UL><LI><P class=""><SPAN class=""><STRONG>Use Aggregation Binding vs. Manual DOM Updates</STRONG></SPAN>: When displaying lists or tables of data, use <SPAN class=""><STRONG>aggregation binding</STRONG></SPAN> (<SPAN class="">bindAggregation</SPAN>) to let SAPUI5 create and manage list items for you. This is far more efficient than manually creating controls in a loop and inserting them into a container. For example, rather than writing a loop to append <SPAN class="">ColumnListItem</SPAN> controls to a table, define the binding in the XML view (as shown in the earlier snippet) or via <SPAN class="">oTable.bindItems(...)</SPAN> in JS. The framework will template the items and only render what’s needed. Using <SPAN class="">bindAggregation</SPAN> for lists ensures that UI5 can reuse templates and handle diffing, reducing the amount of DOM manipulation. In contrast, <SPAN class="">bindProperty</SPAN> is used for individual property binding on an existing control (e.g., binding a text field’s value). As a rule of thumb: <SPAN class=""><STRONG>use bindProperty for simple, singular values, but use bindAggregation for collections</STRONG></SPAN>. This way, you avoid manually touching the DOM for each data entry – the framework does it in bulk efficiently.</P></LI><LI><P class=""><SPAN class=""><STRONG>Batch UI updates</STRONG></SPAN>: If you need to make multiple changes to the UI, try to batch them in one go rather than many small updates. For instance, if you have to hide or show multiple controls, it’s better to wrap them in a single container and hide/show the container, instead of toggling each control individually. Or use a model property to bind the <SPAN class="">visible</SPAN> property of several controls and change that one property – the framework will update all the UI states in one logical re-render. Excessive incremental DOM updates can thrash the browser’s rendering engine. Minimizing such changes leads to smoother performance .</P></LI><LI><P class=""><SPAN class=""><STRONG>Avoid heavy computations in bindings</STRONG></SPAN>: Data binding is powerful, but don’t abuse it by putting expensive calculations in formatter functions or property bindings that trigger frequently. Keep formatter logic lightweight. If a computation is heavy, consider pre-calculating it in the model data instead of in a formatter that runs on every digest cycle.</P></LI><LI><P class=""><SPAN class=""><STRONG>One-Time vs. Two-Way Binding</STRONG></SPAN>: If you have content that does not change after initialization (static text, labels, etc.), use one-time binding (<SPAN class="">{= ... , mode:'OneTime'}</SPAN> or in XML <SPAN class="">{path: '...', mode: 'OneTime'}</SPAN>). This way, the framework will not keep observing that property for changes – it binds once and is done, reducing runtime overhead . One-time binding is great for things like form labels or initial view title that won’t update. Similarly, if two-way binding is not needed, prefer one-way binding (the default) which is less overhead than two-way (which has to propagate changes back to the model).</P></LI><LI><P class=""><SPAN class=""><STRONG>Keep the DOM lean</STRONG></SPAN>: Try to avoid creating an excessively deep or large DOM structure. For example, in list items, using multiple nested Layouts (Horizontal/Vertical Layouts) and Containers can blow up the number of DOM nodes for each item. Multiply that by 100 items and it can degrade performance. Favor simpler structures or use CSS for spacing instead of extra markup. The SAPUI5 guidelines suggest keeping views “short and simple” – meaning a shallower control hierarchy where possible, especially in repeated elements.</P></LI></UL><P class="">In summary, every extra element or reflow counts. <SPAN class=""><STRONG>Excessive manipulation of the DOM can slow down your UI5 app</STRONG></SPAN> , so lean on data binding to handle UI updates and avoid direct DOM hacking. By using the right binding strategies (aggregation vs property binding, one-time where applicable) and keeping the UI structure efficient, you minimize the work the browser has to do.</P><P class=""> </P><H2 id="toc-hId-554289984"><STRONG>Conclusion</STRONG></H2><P class="">Optimizing SAPUI5 performance requires a deliberate approach—lazy load data/UI, optimize OData requests, leverage caching, and minimize DOM manipulation. By applying these practices proactively, you deliver SAPUI5 applications that scale efficiently, meeting user expectations and ensuring smooth performance.</P><P class="">Stay tuned for <SPAN class=""><STRONG>Part 2</STRONG></SPAN>, where we’ll dive deep into performance measurement and quality assurance.</P><P> </P><P> </P>2025-07-02T14:07:47.915000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/find-the-bug-formatting-dates-and-times-in-build-apps/ba-p/14146230Find the Bug 🐞 Formatting dates and times in Build Apps2025-07-07T17:10:26.360000+02:00Dan_Wroblewskihttps://community.sap.com/t5/user/viewprofilepage/user-id/72<P>Someone sent me a problem they were having in a private message, that made me chuckle.</P><P>To be honest, after 2 years of war and 2 weeks of intense war, and having to live out of a suitcase for a week in Athens because my plane was diverted and could not enter my country -- this was a fun, amusing diversion (it actually made me laugh).</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2025-07-07_18-17-17.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/283549i2718837831B59874/image-size/large?v=v2&px=999" role="button" title="2025-07-07_18-17-17.png" alt="2025-07-07_18-17-17.png" /></span></P><P>Here's the message I received:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rule.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/242171i90366DC4F4BD6F45/image-size/large?v=v2&px=999" role="button" title="rule.png" alt="rule.png" /></span></P><P> </P><pre class="lia-code-sample language-javascript"><code>FORMAT_DATETIME_WITH_TIMEZONE(DATETIME("2025-04-03T02:00:00.000Z"), "MMM d, yyyy", "America/Chicago") </code></pre><P> </P><P>"I am using formula to format the date. Actually the output should be Apr 2, 2025, 9 pm. But I am getting Apr 3, 2025, 9 pm. How to solve this? I tried multiple ways. It's only changing time but not date. I used other timezone format as well. Please help. This is an issue in production."</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rule.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/242171i90366DC4F4BD6F45/image-size/large?v=v2&px=999" role="button" title="rule.png" alt="rule.png" /></span></P><P>After running the formula in my Build Apps and getting the same result, I assumed it was a bug in the tool (<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/163703">@Mari</a> and <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1641528">@Pekka_Aaltonen</a>, please forgive me). For the programmer, the obvious thing to think was that you asked for the date in the format April 2 (because it had to give the day before because the time zone they gave was minus 6 hours from UTC) and instead it was giving April 3. Why? Probably because the formula was too stupid to return the previous day's date.</P><P>It was supposed to show this:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dan_Wroblewski_1-1751859860631.png" style="width: 198px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/283321iB0EAE6A724332757/image-dimensions/198x204?v=v2" width="198" height="204" role="button" title="Dan_Wroblewski_1-1751859860631.png" alt="Dan_Wroblewski_1-1751859860631.png" /></span></P><P>But kept showing this:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dan_Wroblewski_0-1751859812914.png" style="width: 0px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/283320i6A6CCB35A7939D05/image-size/small?v=v2&px=200" width="0" height="0" role="button" title="Dan_Wroblewski_0-1751859812914.png" alt="Dan_Wroblewski_0-1751859812914.png" /></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dan_Wroblewski_0-1751859948461.png" style="width: 195px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/283322i073475760A3806EE/image-dimensions/195x203?v=v2" width="195" height="203" role="button" title="Dan_Wroblewski_0-1751859948461.png" alt="Dan_Wroblewski_0-1751859948461.png" /></span></P><P>But, alas, there was a perfectly logical explanation, but it was hidden because of the actual date and time used in the example. Another case of all-too-human error.</P><P>Can you find the bug in the function code?</P><P>P.S.: If you like this puzzle, wait for Devtoberfest in October. We'll have plenty more.</P><P> </P><P> </P>2025-07-07T17:10:26.360000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/exploring-sap-generative-ai-sdk-python-javascript-and-java-libraries/ba-p/14150705Exploring SAP Generative AI SDK: Python, JavaScript, and Java Libraries 🎁2025-07-11T20:59:21.243000+02:00Yoganandahttps://community.sap.com/t5/user/viewprofilepage/user-id/75<P><A href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/libraries-and-sdks" target="_self" rel="noopener noreferrer">SAP's Generative AI SDK</A> offers powerful tools for integrating AI capabilities into your business applications.</P><P>This blog will guide you through the libraries available for <FONT color="#0000FF">Python, JavaScript, and Java</FONT>, and how to use them effectively.<span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2025-07-11_20-51-00.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/285646iE39F1A18CFDFB822/image-size/large?v=v2&px=999" role="button" title="2025-07-11_20-51-00.png" alt="2025-07-11_20-51-00.png" /></span></P><H4 id="toc-hId-1993145073">1.<SPAN><span class="lia-unicode-emoji" title=":blue_circle:">🔵</span> </SPAN>SAP Generative AI SDK for Python</H4><P>The SAP Generative AI SDK for Python allows developers to leverage generative models from the SAP AI Core. This SDK supports models from providers like OpenAI, Amazon, and Google, and integrates with LangChain for enhanced functionality.</P><P>Documentation and Examples : <A href="https://help.sap.com/doc/generative-ai-hub-sdk/CLOUD/en-US/index.html" target="_blank" rel="noopener noreferrer">https://help.sap.com/doc/generative-ai-hub-sdk/CLOUD/en-US/index.html</A> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Yogananda_1-1752259770526.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/285644i13469DC27D9AC732/image-size/large?v=v2&px=999" role="button" title="Yogananda_1-1752259770526.png" alt="Yogananda_1-1752259770526.png" /></span></P><P>Installation: To install the SDK, use the following command:</P><pre class="lia-code-sample language-python"><code>pip install sap-ai-sdk-gen[all]</code></pre><P><SPAN>You can also install specific model providers:</SPAN></P><pre class="lia-code-sample language-python"><code>pip install "sap-ai-sdk-gen[google, amazon]"</code></pre><P><STRONG>Configuration:</STRONG><SPAN> Set up your environment variables or configuration file to authenticate and connect to SAP AI Core:</SPAN></P><pre class="lia-code-sample language-json"><code>export AICORE_CLIENT_ID="your_client_id"
export AICORE_CLIENT_SECRET="your_client_secret"
export AICORE_AUTH_URL="your_auth_url"
export AICORE_BASE_URL="your_base_url"</code></pre><P><STRONG>Usage:</STRONG><SPAN> Here's a simple example to generate text using the OpenAI model:</SPAN></P><pre class="lia-code-sample language-python"><code>from sap_ai_sdk_gen import OpenAI
client = OpenAI()
response = client.generate_text(prompt="Hello, world!")
print(response)</code></pre><H4 id="toc-hId-1796631568">2.<SPAN><span class="lia-unicode-emoji" title=":blue_circle:">🔵</span> </SPAN>SAP Generative AI SDK for JavaScript</H4><P>The JavaScript SDK enables Node.js applications to interact with SAP AI Core, providing seamless integration with generative models.</P><P><A href="https://github.com/SAP/ai-sdk-js" target="_blank" rel="noopener nofollow noreferrer">https://github.com/SAP/ai-sdk-js</A> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Yogananda_0-1752259610583.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/285643iBB7D6B6568C71C17/image-size/large?v=v2&px=999" role="button" title="Yogananda_0-1752259610583.png" alt="Yogananda_0-1752259610583.png" /></span></P><P>Installation: Install the SDK via npm:</P><pre class="lia-code-sample language-javascript"><code>npm install -ai-sdk/ai-api</code></pre><P><STRONG>Configuration:</STRONG><SPAN> Ensure your environment variables are set up correctly:</SPAN></P><pre class="lia-code-sample language-javascript"><code>export AICORE_CLIENT_ID="your_client_id"
export AICORE_CLIENT_SECRET="your_client_secret"
export AICORE_AUTH_URL="your_auth_url"
export AICORE_BASE_URL="your_base_url"</code></pre><H4 id="toc-hId-1600118063"><STRONG>Usage:</STRONG><SPAN> Here's a basic example to generate text:</SPAN></H4><pre class="lia-code-sample language-javascript"><code>import { OpenAI } from '@sap-ai-sdk/ai-api';
const client = new OpenAI();
client.generateText('Hello, world!').then(response => {
console.log(response);
});</code></pre><H4 id="toc-hId-1403604558">3.<SPAN><span class="lia-unicode-emoji" title=":blue_circle:">🔵</span> </SPAN>SAP Generative AI SDK for Java</H4><P>The Java SDK integrates AI capabilities into Java-based applications, leveraging the SAP AI Core's generative models.</P><P><A href="https://github.com/SAP/ai-sdk-java" target="_blank" rel="noopener nofollow noreferrer">https://github.com/SAP/ai-sdk-java</A></P><P>Installation: Add the SDK to your Maven project:</P><P> </P><pre class="lia-code-sample language-java"><code><dependency>
<groupId>com.sap.ai</groupId>
<artifactId>sap-ai-sdk</artifactId>
<version>5.6.0</version>
</dependency></code></pre><P> </P><P><STRONG>Configuration:</STRONG><SPAN> Configure your application properties:</SPAN></P><pre class="lia-code-sample language-java"><code>aicore.client.id=your_client_id
aicore.client.secret=your_client_secret
aicore.auth.url=your_auth_url
aicore.base.url=your_base_url</code></pre><P><STRONG>Usage:</STRONG><SPAN> Here's an example to generate text:</SPAN></P><pre class="lia-code-sample language-java"><code>import com.sap.ai.sdk.OpenAI;
public class Main {
public static void main(String[] args) {
OpenAI client = new OpenAI();
String response = client.generateText("Hello, world!");
System.out.println(response);
}
}</code></pre><H3 id="toc-hId-1078008334">Conclusion</H3><P>SAP's Generative AI SDKs for Python, JavaScript, and Java provide robust tools for integrating AI into your applications. By following the installation, configuration, and usage examples provided, you can start leveraging the power of generative AI in your projects.</P><P>Feel free to reach out if you have any questions or need further assistance! Happy coding!</P><P> </P><P> </P>2025-07-11T20:59:21.243000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/connect-to-public-cloud-api-in-node-js-via-authorisation-code/ba-p/14158313Connect to Public Cloud API in Node.js via Authorisation Code2025-07-22T13:54:57.292000+02:00Eganhttps://community.sap.com/t5/user/viewprofilepage/user-id/1504852<P>Connecting to a Public Cloud API (from <A href="https://api.sap.com/" target="_self" rel="noopener noreferrer">Business Accelerator Hub</A>) via Node.js CAP application can be challenging. I hope this blog helps others attempting the same task.</P><H2 id="toc-hId-1735214148"> Prerequisites</H2><P>• SAP BTP subaccount (Cloud Foundry)<BR />• Service key for the target Public Cloud API<BR />• XSUAA instance bound to your CAP app<BR />• SAP BTP Destination service instance<BR />• Bruno installed for manual testing</P><P> </P><H2 id="toc-hId-1538700643">1. Confirm the API's Auth Options</H2><P>First, check which authentication methods are available for the API you plan to consume.</P><P>In my example I connect to the <STRONG>Specification Management API</STRONG>, but the steps apply to any Public Cloud API.</P><P>If the <EM>OAuth 2.0 Access Code Flow</EM> is listed under “Authentication Methods”, the API supports the authorization-code flow. That flow confirms a user context and usually grants wider authorisations, such as update and POST requests.</P><P>(<EM>OAuth 2.0 Application Flow</EM> corresponds to the client-credentials flow.)</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Auth-method screenshot" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/289462iBE738F54BD94C2E9/image-size/medium?v=v2&px=400" role="button" title="Egan_0-1753113819867.png" alt="Egan_0-1753113819867.png" /></span></P><H2 id="toc-hId-1342187138">2. Connect manually with Bruno (<A href="https://community.sap.com/t5/technology-blog-posts-by-sap/data-export-api-getting-started-with-bruno/ba-p/14120240" target="_blank">Good blog on Bruno</A>)</H2><P>Because our API supports the auth-code flow, we first prove we can log in by calling it via Bruno.</P><P>Create a new collection and paste the base URI of your Public Cloud API instance (you’ll find it in the service key in your service instance of the saas application your connecting to).</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Service key showing URI" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/289463iD5B5C161AD23500A/image-size/medium?v=v2&px=400" role="button" title="Egan_3-1753114108426.png" alt="Egan_3-1753114108426.png" /></span></P><P>When you send a test GET request you will probably receive an authorisation error. Configure OAuth 2.0 on the <STRONG>Auth</STRONG> tab</P><P>Select Grant type Authorisation Code</P><P>Then fill in the below fields using your service key from your service instance</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Bruno auth settings" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/289461i1BABC87E61152C2F/image-size/medium?v=v2&px=400" role="button" title="Egan_1-1753113819868.png" alt="Egan_1-1753113819868.png" /></span></P><UL><LI>Callback URL: <your Public Cloud API hostname></LI><LI>Auth URL:** https://<subdomain>.authentication.<region>.hana.ondemand.com/oauth/authorize</LI><LI>Access-Token URL: https://<subdomain>.authentication.<region>.hana.ondemand.com/oauth/token</LI><LI>Client ID / Secret: from the uaa section of the service key</LI></UL><P>Request the token, pick your identity provider, and Bruno will return an access token. Inspect the scopes there to see what you can do.</P><H2 id="toc-hId-1145673633">3. Connect to the API from Node.js</H2><P>After the manual check we can now automate the flow in a Node.js service.</P><H3 id="toc-hId-1078242847">3.1 Destination setup</H3><P>Create a destination that points to the same API endpoint and bind it to your app.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Destination configuration" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/289460i1560E45388730B53/image-size/medium?v=v2&px=400" role="button" title="Egan_2-1753113819869.png" alt="Egan_2-1753113819869.png" /></span></P><P>The required scopes and the audience (aud) value are visible inside the Bruno access token.</P><P>the Audience should start with the client id of your CAP application on BTP</P><P>Once the destination is saved, ensure your application is bound to the Destination service.</P><H3 id="toc-hId-881729342">3.2 xs-security.json</H3><P>Add every scope your service needs so the approuter, it will forward it in the user’s JWT: in my example i needed this scope.</P><pre class="lia-code-sample language-json"><code>{
"name": "$XSAPPNAME.Spc_Write",
"description": "Allow modification of specifications."
}</code></pre><P>Also reference the scope in authorities (and role templates, if used) and assign the resulting role collection to your user.</P><H3 id="toc-hId-685215837">3.3 Connect in code</H3><P>Now time to connect to the API in the code itself.</P><P>First, obtain the user’s JWT. This helper below tries every possible source and falls back to LOCAL_USER_JWT in local development:</P><pre class="lia-code-sample language-javascript"><code>/** Try every source for a JWT and fall back to LOCAL_USER_JWT. */
function getJwt(capReq) {
// CAP ≥ 7.3 keeps the raw token here
if (capReq.user?.jwt) return capReq.user.jwt;
// Plain HTTP / WebSocket headers
const hdrs = capReq.http?.req?.headers || {};
const token =
hdrs['x-approuter-authorization'] || // CF WebSocket
hdrs.authorization; // CF HTTP
if (token) return token.replace(/^Bearer\s+/, '');
// Last resort for local dev
return process.env.LOCAL_USER_JWT;
}</code></pre><H4 id="toc-hId-617785051">3.3.1 Testing locally</H4><P>When testing locally we don't have access to the user JWT so we can add it manually to the environment file so we can still test locally, below is the process to get the JWT for user testing (each token lasts about 7 to 8 hours)</P><H5 id="toc-hId-550354265">Step 1 – Get an auth code</H5><pre class="lia-code-sample language-markup"><code>https://<instance>.eu10.authentication.eu10.hana.ondemand.com/oauth/authorize
?response_type=code
&client_id=<XSUAA client id of your deployed app>
&redirect_uri=http://localhost
&state=xyz</code></pre><P>Paste your version into your browsers address bar, after login you will land on <A href="http://localhost/?code=" target="_blank" rel="noopener nofollow noreferrer">http://localhost/?code=</A><AUTH_CODE>&state=xyz. Copy the value of AUTH_CODE.</P><H5 id="toc-hId-353840760">Step 2 – Exchange the code for a JWT</H5><P>I made the Curl Request inside BAS itself. it will return a number of tokens, we only need the first and biggest one.</P><pre class="lia-code-sample language-markup"><code>curl -v -X POST \
-u '<same client id>' \
--data-urlencode grant_type=authorization_code \
--data-urlencode code=<AUTH_CODE> \
--data-urlencode redirect_uri=http://localhost \
https://<instance>.eu10.authentication.eu10.hana.ondemand.com/oauth/token</code></pre><P>Save the token from the response into .env:</P><pre class="lia-code-sample language-markup"><code>LOCAL_USER_JWT=<paste JWT here></code></pre><H4 id="toc-hId--469472559">3.3.2 Call the destination with the JWT</H4><pre class="lia-code-sample language-javascript"><code>const jwt = getJwt(req);
if (!jwt) req.reject(401, 'No user token found.');
const dest = await getDestination({
destinationName: '<DestinationName>',
jwt
});</code></pre><pre class="lia-code-sample language-javascript"><code>await executeHttpRequest(dest, {
method: 'POST',
url: `/odata/v4/api/specification/v1/${EntityName}`,
data: v
});</code></pre><P>If you reach the destination but receive 401 Unauthorized, your JWT is missing the correct scope—ask the product team which one you need.</P><P>Other common pitfall is using the different XSUAA instances to get your JWT for the local run. use your applications XSUAA client since this is the same one your application will check the token against</P><P>And that’s it! You should now be able to read from and write to the Public Cloud API. For read-only workloads the client-credentials flow is usually simpler.</P><P>If I missed anything or you’d like more details, please comment below—happy to help.</P><P> </P><P><STRONG>## Frequently Asked Questions</STRONG></P><P><STRONG>### What’s the difference between authorization-code and client-credentials flows?</STRONG><BR />The authorization-code flow runs in a user context, so it can carry user-level scopes (read, write, update). The client-credentials flow operates with a technical user—perfect for background jobs or read-only integrations.</P><P><STRONG>### Why do I get “401 Unauthorized” after deployment?</STRONG><BR />Your user’s JWT is probably missing the required scope (e.g., `Spc_Write`). Make sure the scope is listed in <STRONG>xs-security.json</STRONG>, mapped to a role template, and assigned via a role collection in the BTP cockpit.</P><P><STRONG>### Which audience value belongs in my destination?</STRONG><BR />Use the <STRONG>client ID</STRONG> <STRONG>of your CAP application’s XSUAA instance</STRONG>; it usually starts with `sb-<appName>!t<number>`.</P><P><a href="https://community.sap.com/t5/c-khhcw49343/Node.js/pd-p/723714486627645412834578565527550" class="lia-product-mention" data-product="322-1">Node.js</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Cloud+Identity+Services/pd-p/67837800100800007337" class="lia-product-mention" data-product="155-1">SAP Cloud Identity Services</a><a href="https://community.sap.com/t5/c-khhcw49343/API/pd-p/b31da0dd-f79a-4a1e-988c-af0755c2d184" class="lia-product-mention" data-product="123-1">API</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Authenticator/pd-p/73554900100700000789" class="lia-product-mention" data-product="435-1">SAP Authenticator</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Fiori+Cloud/pd-p/73554900100800000375" class="lia-product-mention" data-product="20-1">SAP Fiori Cloud</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Cloud+Application+Programming+Model/pd-p/9f13aee1-834c-4105-8e43-ee442775e5ce" class="lia-product-mention" data-product="100-1">SAP Cloud Application Programming Model</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Business+Application+Studio/pd-p/67837800100800007077" class="lia-product-mention" data-product="13-1">SAP Business Application Studio</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Business+Technology+Platform/pd-p/73555000100700000172" class="lia-product-mention" data-product="1215-1">SAP Business Technology Platform</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+S%25252F4HANA+Cloud+Public+Edition+Extensibility/pd-p/270c4f37-c335-46e1-bfad-a256637d5e26" class="lia-product-mention" data-product="37-1">SAP S/4HANA Cloud Public Edition Extensibility</a><a href="https://community.sap.com/t5/c-khhcw49343/SAP+Business+Accelerator+Hub/pd-p/73555000100800001091" class="lia-product-mention" data-product="1214-1">SAP Business Accelerator Hub</a><a href="https://community.sap.com/t5/c-khhcw49343/JavaScript/pd-p/506421944534752500398156104608974" class="lia-product-mention" data-product="321-1">JavaScript</a><a href="https://community.sap.com/t5/c-khhcw49343/API+Management/pd-p/67838200100800006828" class="lia-product-mention" data-product="358-1">API Management</a></P><!-- END BLOG FRAGMENT --><P> </P>2025-07-22T13:54:57.292000+02:00https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-members/easy-way-to-move-zeroes-in-sap-btp-abap-steampunk-js-amp-python/ba-p/14176847Easy way to move zeroes in SAP BTP ABAP(Steampunk), JS & Python2025-08-10T15:27:33.552000+02:00kallolathomehttps://community.sap.com/t5/user/viewprofilepage/user-id/14879<H2 id="toc-hId-962976781" id="toc-hId-1737006510">Introduction</H2><P><SPAN>This is part of the </SPAN><A href="https://blogs.sap.com/2022/12/20/easy-way-to-write-algorithms-in-abap-series-01/" target="_blank" rel="noopener noreferrer"><STRONG>Easy way to write algorithms in ABAP: Series 01</STRONG></A><SPAN>. For more algorithms, please check the main blog-post.</SPAN></P><H2 id="toc-hId-766463276" id="toc-hId-1540493005">Problem</H2><P>Given an integer array<SPAN> </SPAN>nums, move all<SPAN> </SPAN>0's to the end of it while maintaining the relative order of the non-zero elements.</P><P><STRONG>Note</STRONG><SPAN> </SPAN>that you must do this in-place without making a copy of the array.</P><P><STRONG>Example 1:</STRONG></P><PRE><STRONG>Input:</STRONG> nums = [0,1,0,3,12]
<STRONG>Output:</STRONG> [1,3,12,0,0]</PRE><P><STRONG>Example 2:</STRONG></P><PRE><STRONG>Input:</STRONG> nums = [0]
<STRONG>Output:</STRONG> [0]</PRE><P><STRONG>Constraints:</STRONG></P><UL><LI>1 <= nums.length <= 104</LI><LI>-231 <= nums[i] <= 231 - 1</LI></UL><P><STRONG>Follow up:</STRONG><SPAN> Could you minimize the total number of operations done?</SPAN></P><H2 id="toc-hId-569949771" id="toc-hId-1343979500">Solution</H2><P><SPAN>Time Complexity: <STRONG>O(n)</STRONG><BR />Space Complexity: <STRONG>O(1)</STRONG></SPAN></P><H3 id="toc-hId-502518985" id="toc-hId-1276548714">ABAP</H3><pre class="lia-code-sample language-abap"><code>CLASS zmove_zeroes DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
" Define a table type for integers
TYPES ty_nums TYPE STANDARD TABLE OF i WITH EMPTY KEY.
" Method to move zeroes in-place
METHODS moveZeroes
CHANGING lt_nums TYPE ty_nums.
ENDCLASS.
CLASS zmove_zeroes IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Initialize the number array with some zeroes and non-zeroes
DATA(lt_nums) = VALUE ty_nums( ( 0 ) ( 1 ) ( 0 ) ( 3 ) ( 12 ) ).
" Output the array before moving zeroes
out->write( |Array before moving zeroes: | ).
LOOP AT lt_nums INTO DATA(lv_num).
out->write( lv_num ).
ENDLOOP.
" Call the method to move zeroes to the end
moveZeroes( CHANGING lt_nums = lt_nums ).
" Output the array after moving zeroes
out->write( |Array after moving zeroes: | ).
LOOP AT lt_nums INTO lv_num.
out->write( lv_num ).
ENDLOOP.
ENDMETHOD.
METHOD moveZeroes.
DATA(lv_count) = 0. " Counter for non-zero elements
" First pass: Move all non-zero elements to the front
LOOP AT lt_nums ASSIGNING FIELD-SYMBOL(<lf_num>).
IF <lf_num> <> 0.
" Place the non-zero element at the next available position
lt_nums[ lv_count + 1 ] = <lf_num>.
lv_count += 1.
ENDIF.
ENDLOOP.
" Second pass: Fill the rest of the array with zeroes
WHILE lv_count < lines( lt_nums ).
lt_nums[ lv_count + 1 ] = 0.
lv_count += 1.
ENDWHILE.
ENDMETHOD.
ENDCLASS.</code></pre><H3 id="toc-hId-306005480" id="toc-hId-1080035209">JavaScript</H3><pre class="lia-code-sample language-javascript"><code>function moveZeroes(nums) {
let left = 0;
for (let right = 0; right < nums.length; right++) {
if (nums[right] !== 0) {
// Swap nums[left] and nums[right]
let temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
}
}
return nums;
}</code></pre><H3 id="toc-hId-883521704"> </H3><H3 id="toc-hId-502518985" id="toc-hId-687008199">Python</H3><pre class="lia-code-sample language-python"><code>def moveZeroes(nums):
left = 0
for right in range(len(nums)):
if nums[right] != 0:
nums[left], nums[right] = nums[right], nums[left]
left += 1
return nums</code></pre><P> </P><P><SPAN>N.B: For ABAP, I am using SAP BTP ABAP Environment 2309 Release.</SPAN><BR /><BR /><SPAN>Happy Coding! </SPAN><SPAN class="lia-unicode-emoji"><span class="lia-unicode-emoji" title=":slightly_smiling_face:">🙂</span></SPAN></P>2025-08-10T15:27:33.552000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/%EF%B8%8F-real-time-weather-display-on-sap-btp-with-openweathermap-api-step-by-step/ba-p/14172919☁️ Real-time Weather Display on SAP BTP with OpenWeatherMap API — Step-by-Step Fiori Application2025-08-11T12:01:26.753000+02:00ilkertkn25https://community.sap.com/t5/user/viewprofilepage/user-id/154635<P><STRONG><span class="lia-unicode-emoji" title=":magnifying_glass_tilted_left:">🔍</span> Introduction</STRONG></P><P><BR />This article I will show you step by step how to display real-time weather data in a Fiori application using the OpenWeatherMap API on SAP BTP. The application will request city information from the user and display information such as current temperature, humidity, and wind speed in a table format.</P><P>Output:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ilkertkn25_0-1754457909087.png" style="width: 834px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/297009iFCC529E3D17D615D/image-dimensions/834x144?v=v2" width="834" height="144" role="button" title="ilkertkn25_0-1754457909087.png" alt="ilkertkn25_0-1754457909087.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ilkertkn25_1-1754457934109.png" style="width: 809px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/297010iE41B2AB2AA702B91/image-dimensions/809x421?v=v2" width="809" height="421" role="button" title="ilkertkn25_1-1754457934109.png" alt="ilkertkn25_1-1754457934109.png" /></span></P><P> </P><P><STRONG><span class="lia-unicode-emoji" title=":hammer_and_wrench:">🛠</span>️ 1. Obtaining an OpenWeatherMap API Key</STRONG></P><P><BR />Go to <A href="https://openweathermap.org/api" target="_blank" rel="noopener nofollow noreferrer">https://openweathermap.org/api</A>.<BR />Sign up and then create a new API key from the API keys tab.<BR />Alternatively, an example request would look like this:<BR /><A href="https://api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid=e21c9996e2801f22e2bd0f7d11d1b367" target="_blank" rel="noopener nofollow noreferrer">https://api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid=e21c9996e2801f22e2bd0f7d11d1b367</A></P><P><span class="lia-unicode-emoji" title=":light_bulb:">💡</span>This API key: e21c9996e2801f22e2bd0f7d11d1b367 can be used for testing. If it doesn't work, you can create a free one.</P><P>When we send a request with the link, it returns a JSON list like this:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ilkertkn25_2-1754457981499.png" style="width: 577px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/297012i036F9E9D32DFB518/image-dimensions/577x704?v=v2" width="577" height="704" role="button" title="ilkertkn25_2-1754457981499.png" alt="ilkertkn25_2-1754457981499.png" /></span></P><P> </P><P><STRONG>🧱 2. Creating a UI5 Project in SAP Business Application Studio</STRONG></P><P><BR />A. Start a new Fiori application<BR />SAP Business Application Studio > “Create Project from Template”<BR />Template: SAP Fiori Application<BR />Data Source: “None”<BR />Project Name: weather-app<BR />B. Project folder structure:<BR />weather-app/<BR />├── webapp/<BR />│ ├── controller/<BR />│ │ └── View1.controller.js<BR />│ ├── model/<BR />│ │ └── cityData.json<BR />│ ├── util/<BR />│ │ └── formatter.js<BR />│ ├── view/<BR />│ │ └── View1.view.xml<BR />│ └── index.html<BR />└── manifest.json</P><P> </P><P><STRONG><span class="lia-unicode-emoji" title=":globe_showing_europe_africa:">🌍</span>3. Defining City Data as JSON</STRONG></P><P><BR />webapp/model/cityData.json:</P><pre class="lia-code-sample language-markup"><code>{
"cities": [
{ "name": "İstanbul", "lat": 41.0082, "lon": 28.9784 },
{ "name": "Ankara", "lat": 39.9208, "lon": 32.8541 },
{ "name": "İzmir", "lat": 38.4192, "lon": 27.1287 },
{ "name": "Bursa", "lat": 40.195, "lon": 29.06 },
{ "name": "Antalya", "lat": 36.8969, "lon": 30.7133 },
{ "name": "Trabzon", "lat": 41.0014, "lon": 39.7178 },
{ "name": "Gaziantep", "lat": 37.0662, "lon": 37.3833 },
{ "name": "Samsun", "lat": 41.2867, "lon": 36.33 },
{ "name": "Kayseri", "lat": 38.7348, "lon": 35.4676 },
{ "name": "Eskişehir", "lat": 39.7667, "lon": 30.5256 }
]
}</code></pre><P> </P><P><STRONG><span class="lia-unicode-emoji" title=":direct_hit:">🎯</span>4. View1.view.xml — UI Development</STRONG></P><P> </P><pre class="lia-code-sample language-markup"><code><mvc:View
controllerName="weatherapp.controller.View1"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m"
displayBlock="true">
<VBox id="IDVBoxMain" class="sapUiMediumMargin sapUiResponsiveContentPadding" alignItems="Center">
<!-- Şehir Seçimi -->
<HBox id="IDHBoxCitySelect" class="sapUiSmallMarginBottom" alignItems="Center">
<Label id="IDLabelCity" text="Şehir Seçin:" labelFor="IDComboCitySelect" class="sapUiSmallMarginEnd"/>
<ComboBox id="IDComboCitySelect"
items="{/cities}"
selectionChange=".onCityChange"
placeholder="Şehir seçin"
width="220px"
editable="true">
<items>
<core:Item id="IDComboItemCity" key="{lat},{lon}" text="{name}" />
</items>
</ComboBox>
</HBox>
<!-- Anlık Hava Durumu Tablosu -->
<Table id="IDTableCurrentWeather"
items="{weatherModel>/Forecast}"
inset="false"
width="100%"
class="sapUiResponsiveTable"
growing="true"
growingScrollToLoad="true">
<headerToolbar>
<Toolbar id="IDToolbarWeather">
<Title id="IDTitleWeather" text="📡 Hava Durumu Tahmini" level="H2"/>
</Toolbar>
</headerToolbar>
<columns>
<Column id="IDColDate"><Text id="IDColTextDate" text="📅 Tarih Saat"/></Column>
<Column id="IDColTemp"><Text id="IDColTextTemp" text="🌡 Sıcaklık (°C)"/></Column>
<Column id="IDColFeels"><Text id="IDColTextFeels" text="🤒 Hissedilen (°C)"/></Column>
<Column id="IDColDesc"><Text id="IDColTextDesc" text="🌤 Hava Durumu"/></Column>
<Column id="IDColHumidity"><Text id="IDColTextHumidity" text="💧 Nem (%)"/></Column>
<Column id="IDColWind"><Text id="IDColTextWind" text="🌬 Rüzgar (m/s)"/></Column>
</columns>
<items>
<ColumnListItem id="IDRowWeatherItem" type="Active">
<cells>
<Text id="IDCellDate" text="{weatherModel>dt_txt}" class="sapMTextStrong"/>
<ObjectNumber id="IDCellTemp"
number="{path: 'weatherModel>temp', formatter: '.formatter.formatKelvinToCelsius'}"
unit="°C"
state="Success"/>
<ObjectNumber id="IDCellFeels"
number="{path: 'weatherModel>feels_like', formatter: '.formatter.formatKelvinToCelsius'}"
unit="°C"
state="Warning"/>
<Text id="IDCellDesc" text="{weatherModel>weather_desc}"/>
<Text id="IDCellHumidity" text="{weatherModel>humidity}"/>
<Text id="IDCellWind" text="{weatherModel>wind_speed}"/>
</cells>
</ColumnListItem>
</items>
</Table>
</VBox>
</mvc:View>
</code></pre><P> </P><P><STRONG><span class="lia-unicode-emoji" title=":video_game:">🎮</span>5. View1.controller.js — API Consumption</STRONG></P><P> </P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"weatherapp/util/formatter"
], function (Controller, JSONModel, formatter) {
"use strict";
return Controller.extend("weatherapp.controller.View1", {
formatter: formatter,
onInit: function () {
const oCityModel = new JSONModel();
oCityModel.loadData("model/cityData.json");
this.getView().setModel(oCityModel);
oCityModel.attachRequestCompleted(() => {
const aCities = oCityModel.getData();
if (aCities?.length > 0) {
const firstCity = aCities[0];
this.loadCurrentWeather(firstCity.lat, firstCity.lon);
}
});
},
onCityChange: function (oEvent) {
const sKey = oEvent.getParameter("selectedItem").getKey();
const [lat, lon] = sKey.split(",");
this.loadCurrentWeather(lat, lon);
},
loadCurrentWeather: async function (lat, lon) {
const apiKey = "e21c9996e2801f22e2bd0f7d11d1b367";
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&lang=tr`;
try {
const response = await fetch(url);
const data = await response.json();
const row = {
dt_txt: new Date(data.dt * 1000).toLocaleString("tr-TR"),
temp: data.main.temp,
feels_like: data.main.feels_like,
humidity: data.main.humidity,
wind_speed: data.wind.speed,
weather_desc: data.weather?.[0]?.description || ""
};
const oWeatherModel = new JSONModel({ Forecast: [row] });
this.getView().setModel(oWeatherModel, "weatherModel");
} catch (err) {
console.error("Anlık hava durumu alınamadı:", err);
}
}
});
});</code></pre><P> </P><P><STRONG><span class="lia-unicode-emoji" title=":page_facing_up:">📄</span>6. formatter.js</STRONG></P><P> </P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([], function () {
"use strict";
return {
formatKelvinToCelsius: function (kelvin) {
const k = parseFloat(kelvin);
return isNaN(k) ? "N/A" : (k - 273.15).toFixed(1);
}
};
});</code></pre><P>Now, with this UI5-based Fiori application developed on SAP BTP, users can instantly view real-time weather data for their selected city. By developing projects like this using the OpenWeatherMap API, you can integrate external data sources into your system.</P><P>For more content, you can also find me on Medium (username: tekin.ilker24).</P><P> </P>2025-08-11T12:01:26.753000+02:00https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-members/build-a-web-app-and-connect-to-s-4hana-with-sap-integration-suite/ba-p/14179607Build A Web App and Connect to S/4HANA with SAP Integration Suite2025-08-13T12:45:21.973000+02:00Former Member<P>A big part of the magic behind AI, advanced analytics, and {insert tech buzzword here} is the humble API!</P><P>I remember the excitement about service-oriented architecture in the late 1990s and early 2000s. Back when most organisations had 'fat' ERPs with extensive customisation, the idea that we could split things up into different apps and connect in a standardised way was refreshing.</P><P>I recently noticed a <A href="https://community.sap.com/t5/sap-codejam/sap-codejam-connecting-systems-and-services-using-sap-integration-suite/ec-p/14110686#M848" target="_self">SAP CodeJam</A> on the SAP community events calendar that involved connecting systems to S/4HANA using SAP Integration Suite.</P><P>I thought it might be fun to build a web app and see if I could successfully connect it to S/4HANA.</P><P>A basic understanding of frontend to enterprise backend via cloud architecture is useful for everyone; business experts, technology experts, and people experts</P><P>The article is broken into three parts: an introduction, a step-by-step explanation for generalists, and my build/test notes for anyone working on something similar. The third section includes details on all the test tools, and configuration settings.</P><P>A couple of quick disclaimers:</P><UL><LI>I'm not an integration expert:<UL><LI>I don't look at integration suite vs. other solutions</LI><LI>I don't cover best practices, typical challenges, good use cases</LI></UL></LI><LI>My solution here is likely not optimal:<UL><LI>It's just a vanilla HTML, CSS, JS frontend</LI></UL></LI></UL><P>---</P><H1 id="toc-hId-1608011118">Part 1: introduction</H1><H2 id="toc-hId-1540580332">From web app to S/4HANA</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300221i6AE07F957A2E6A24/image-size/large?v=v2&px=999" role="button" title="integration-1.png" alt="integration-1.png" /></span></P><P>The plan:</P><UL><LI>The frontend is a web page to search for data from within S/4HANA</LI><LI>The web server handles communication between the frontend and SAP Cloud</LI><LI>SAP Integration Suite will route and format the message for S/4HANA</LI><LI>S/4HANA is the source of data.</LI></UL><P>Tools/technology:</P><UL><LI>Utilise the free trial account for SAP BTP and Integration Suite</LI><LI>Build the frontend and web app ourselves</LI><LI>We can't access S/4HANA. However, the CodeJam provides a S/4HANA mock server that mimics the behaviour of an API within S/4HANA.</LI><LI>If we use a mock system, we will need to run it locally. So, adjusting the architecture.</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300223iF83EF9D1F958295D/image-size/large?v=v2&px=999" role="button" title="integration-2.png" alt="integration-2.png" /></span></P><P>This adds SAP Cloud Connector which allows an "on-premise" application to connect with SAP Cloud.</P><H2 id="toc-hId-1344066827">The front end</H2><P>The completed app offers a summary view and a detailed view. Here's a short <A href="https://youtu.be/wNgAwEfLyX0" target="_self" rel="nofollow noopener noreferrer">screen recording</A></P><H3 id="toc-hId-1276636041">Summary view</H3><P>This is a screenshot from the web browser (firefox).</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-3.png" style="width: 986px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300227i15B2BBB969C15471/image-size/large?v=v2&px=999" role="button" title="integration-3.png" alt="integration-3.png" /></span></P><P>The top part of the page has a search form that allows you to search for a business partner. The bottom part of the page shows the results with a selection of key fields in a card style layout.</P><P>The mock system we are using allows for four different search possibilities:</P><UL><LI>Search for a single business partner by number</LI><LI>Search for all business partners</LI><LI>Search for a single business partner by number, including address details</LI><LI>Search for all business partners, including address details.</LI></UL><H3 id="toc-hId-1080122536">Detail view</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-4.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300230iFE51F5F8AD6D02A5/image-size/large?v=v2&px=999" role="button" title="integration-4.png" alt="integration-4.png" /></span></P><P>The detail view shows the results in a table. This table has a horizontal scroll bar, which can be adjusted to view all the fields. The table includes 'raw' results, so there are some 'technical' entries like `[object Object]` and some blanks, which I think is fine for this mock up stage.</P><H3 id="toc-hId-883609031">Responsive view</H3><P>For tablets and mobile, the card view resizes with the browser window.</P><P></P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-5.png" style="width: 364px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300231i01B740227C1E13DC/image-size/large?v=v2&px=999" role="button" title="Integration-5.png" alt="Integration-5.png" /></span></P><P>I'll come back to how this front end was built after running through the integration flow.</P><H2 id="toc-hId-558012807">S/4HANA</H2><P>The value of this flow is being able to design and build a frontend to access real-time, trusted business data from S/4HANA in a standardised way. In a real-world example, our frontend could be an employee portal or supplier portal.</P><P>S/4HANA is:</P><UL><LI>SAP's enterprise software for large organisations. It handles processes such as purchasing, manufacturing, sales, shipping, finance, etc.</LI><LI>An evolution from their earlier ERP products (R/1, R/2, R/3, ECC).</LI><LI>A complex platform comprising thousands of programs, tables, and customisations used by many large enterprises.</LI></UL><P>S/4HANA already comes with a web frontend called Fiori, which includes thousands of apps. However, in this example imagine we are building something for a casual user that does not require the full capability of Fiori. Or, just consider it's for fun.</P><P>Further reading on S/4HANA:</P><P>[SAP help - S/4HANA](<A href="https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE?locale=en-US" target="_blank" rel="noopener noreferrer">https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE?locale=en-US</A>)</P><H2 id="toc-hId-361499302">Business partner</H2><P>The mock server simulates one of the business partner APIs for S/4HANA.</P><P>A business partner is a reference or master data record that represents a third party that an organisation works with. This includes customers, suppliers, and employees.</P><P>Business partner master data is organised by key fields such as "category" and "role".</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-6.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300232iDF2DB3D934F8EECC/image-size/large?v=v2&px=999" role="button" title="Integration-6.png" alt="Integration-6.png" /></span></P><P>All business partners have general data such as name, address, etc., then they have role-specific data, which may include, but are not limited to:</P><UL><LI>Purchasing data</LI><LI>Sales data</LI><LI>Accounting data</LI><LI>And so on.</LI></UL><P>To understand how business partner data is used, consider a typical ERP process like order-to-cash:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-7.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300233i882EADCDD4912385/image-size/large?v=v2&px=999" role="button" title="Integration-7.png" alt="Integration-7.png" /></span></P><P>This is a summary of the order to cash process. During sales, deliveries, and billing, information from the business partner master record is utilised.</P><P>The business partner master stores long-term stable information about the customer. It's used for both reference and validation during transaction entry.</P><P>This ensures there is consistency across transactions over time in terms of how they reference business partners. This is critical for reporting. Consider comparability, aggregation, etc.</P><P>Further reading on business partners:</P><P><A href="https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE/74b0b157c81944ffaac6ebc07245b9dc/45653b5856de0846e10000000a441470.html?locale=en-US&version=LATEST" target="_self" rel="noopener noreferrer">Help - Business Partner</A></P><H2 id="toc-hId-164985797">S/4HANA Architecture</H2><P>The mock server simulates an S/4HANA API. Let's look inside S/4HANA.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-8.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300236i8B0B2CC5994D164A/image-size/large?v=v2&px=999" role="button" title="Integration-8.png" alt="Integration-8.png" /></span></P><P>Starting from the top right, S/4HANA has two primary ways for users to interact. The traditional SAP graphical user interface (GUI) and the modern Fiori web-based user interface.</P><P>I've drawn APIs to the left of these. The APIs allow applications to interact with S/4HANA.</P><P>Consider the data model in S/4HANA in two separate parts. The first is the traditional HANA database. This is where master data and transactional data are stored. On top of this is the virtual data model. This consists of core data services views. This is a way to define different sets of data to meet the needs of APIs and Fiori Apps.</P><P>In this example, we are using a business partner data API. Behind the scenes, the API sources data from CDS views, which in turn connect to the HANA DB tables.</P><P>---</P><H1 id="toc-hId--160610427">Part 2: step by step walkthrough (for everyone)</H1><P>In this section, I'll summarise the process and technology involved at each step.</P><H2 id="toc-hId-119213144">Point 1: Web communication</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-map-1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300239i27A3BC33FE94D8D7/image-size/large?v=v2&px=999" role="button" title="integration-map-1.png" alt="integration-map-1.png" /></span></P><P>Building an integration flow between web connected applications relies on protocols and standards for web communication. Let's run through the main concepts.</P><H3 id="toc-hId--370703368">Client/server</H3><P>The terms client and server are used to describe the requester and receiver. For example, the web browser on a computer is a client, and google search is a server.</P><P>Internet communications use the HTTP protocol.</P><P></P><P>Hypertext Transfer Protocol (HTTP) is a standard protocol for communication between clients and web servers. Web pages are written in Hypertext Markup Language (HTML).</P><P>The term Uniform Resource Locator (URL) is used to describe an address.</P><H3 id="toc-hId--567216873">The structure of a URL</H3><P>URLs have five key parts:</P><UL><LI>Protocol: `http://`</LI><LI>Domain: `<A href="http://www.example.com" target="_blank" rel="noopener nofollow noreferrer">www.example.com</A>`</LI><LI>Path: `/pages/`</LI><LI>Query string: `?id=1&cat=test`</LI><LI>Fragment: `#article` (an internal page reference, often not present)</LI></UL><P>When it comes to APIs, the query string provides the ability to specify parameters for search and filter. In this case, the query string could include a business partner number.</P><H3 id="toc-hId--763730378">HTTPS</H3><P>HTTPS uses the HTTP protocol, but it adds a secure transport layer. HTTPS means the HTTP message is encrypted before transmission.</P><P>The only part that isn't encrypted is the domain name.</P><H3 id="toc-hId--960243883">Internet protocol (IP) address</H3><P>While URLs are designed to be human-readable. An IP is a numerical label like "192.0.2.1" that identifies a computer or network.</P><P>URLs are used for navigation. IPs are used for routing and communication. They identify a specific device on a network (laptop, server, etc.).</P><P>An IP address can be used in place of a domain name with HTTP and HTTPS</P><P>"<A href="http://192.0.2.1" target="_blank" rel="noopener nofollow noreferrer">http://192.0.2.1</A>"</P><P>While an IP address represents a computer. The term "port" is used to specific a specific input/output location.</P><P>Ports are identified using 4 digits.</P><P>"http://{server}:{port}"<BR />"<A href="http://192.0.2.1:1000" target="_blank" rel="noopener nofollow noreferrer">http://192.0.2.1:1000</A>"</P><P>A server is often referred to by 'host'</P><P>"http://{host}:{port}"</P><P>You can access ports on your own computer by using its IP or "localhost"</P><P>"<A href="http://localhost:1000" target="_blank" rel="noopener nofollow noreferrer">http://localhost:1000</A>"</P><H3 id="toc-hId--1156757388">From domain to IP</H3><P>The web browser uses a domain lookup service to translate a URL into an IP address.</P><P>"<A href="http://www.example.com" target="_blank" rel="noopener nofollow noreferrer">http://www.example.com</A>" becomes "<A href="http://192.0.2.1" target="_blank" rel="noopener nofollow noreferrer">http://192.0.2.1</A>"</P><P>This is called the Domain Name System (DNS). Popular look up services include: Cloudflare, Google DNS, and OpenDNS.</P><H3 id="toc-hId--1353270893">Messages</H3><P>The communications themselves can be thought of as messages. They contain a header and a body.</P><P>The header includes:</P><UL><LI>The URL</LI><LI>The method, most commonly GET and POST</LI><LI>GET sends a request without a body</LI><LI>POST sends a request with a body</LI><LI>Additional information on the content type and authorisation</LI></UL><P>The body includes detailed content. For example:</P><UL><LI>If you fill in a form on a web page, it would include the form data</LI><LI>If a server returns a web page, it would include the web page.</LI></UL><H3 id="toc-hId--1549784398">Server Responses</H3><P>When a server receives a request, it responds with a status code and a body. Status codes include '200' representing "ok" and '404' representing 'Not Found'.</P><P>(404 has definitely reached meme levels of fame!).</P><P>The body that's returned depends on the status and the server's purpose.</P><H3 id="toc-hId--1746297903">Real life examples</H3><P>Consider visiting the BBC website from a web browser, a simple GET request would return the home page.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-10-1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300240i98D1E11DB146673D/image-size/large?v=v2&px=999" role="button" title="Integration-10-1.png" alt="Integration-10-1.png" /></span></P><P>On the other hand, consider logging into the BBC website. In this case, the browser sends the login name and password. Therefore, a POST request is used, and the request includes a body.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-10-2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300241i9C9D28EC27350605/image-size/large?v=v2&px=999" role="button" title="Integration-10-2.png" alt="Integration-10-2.png" /></span></P><H3 id="toc-hId--1942811408">Web connectivity and SAP</H3><P>Most SAP applications can use HTTPS communication. This is possibly one of the simplest ways we could define "Cloud" strategy.</P><UL><LI>S/4HANA Cloud Public and S/4HANA Cloud Private can both utilise HTTPS</LI><LI>SAP BTP which includes SAP Integration Suite can utilise HTTPS</LI></UL><P>Outside the SAP Cloud, we have systems like S/4HANA On-Premise. This is usually at an SAP customer's data centre or their 3rd party hosting service provider's data centre. On premise systems are usually not directly connected to the public internet. This is where SAP provide Cloud Connector to create a secure tunnel between on-premise and SAP Cloud.</P><H3 id="toc-hId--1971141222">HTTP data transfer standards</H3><P>There are further standards as to how data is transferred using HTTP.</P><P>There are multiple standards for data transfer with HTTP. One of the earlier and more common standards is REST (Representational State Transfer).</P><P>Many SAP APIs utilise OData (Open data transfer protocol).</P><H2 id="toc-hId--1874251720">Point 2: S/4HANA business partner API mock server</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300244i0C0E251EDEC4B8A1/image-size/large?v=v2&px=999" role="button" title="Integration-map-2.png" alt="Integration-map-2.png" /></span></P><P><STRONG>Purpose:</STRONG> Mimic the business partner API of an S/4HANA system.</P><P><STRONG>What is it:</STRONG> A simple JavaScript server that can be run locally.</P><P>The mock server provided by the SAP community provides a simple way to simulate the design and test of an S/4HANA API.</P><UL><LI>The mock server mimics the business partner (A2X) API</LI><LI>This is one of the S/4HANA APIs (programmed inside S/4HANA)</LI><LI>In the case of the mock server, it's a JavaScript server</LI><LI>The mock server has limited functionality, it supports:<UL><LI>Sample data for a few business partners</LI><LI>Retrieve all business partners</LI><LI>Retrieve a single business partner</LI><LI>Include additional address data in the response.</LI></UL></LI></UL><P>Installing and running the mock server is simple. The instructions are in part 3. When we run it our computer a local address is returned.</P><P>On my computer, it runs on "<A href="http://localhost:3005/" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/</A>"</P><P>This is the address for the Business Partner API. Entering this address in the web browser gives the following response:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-10-3.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300247i85F8DBD3A3C0907C/image-size/large?v=v2&px=999" role="button" title="Integration-10-3.png" alt="Integration-10-3.png" /></span></P><P>The first item refers to the business partner API. This is the first point in the exercise where we can see the path for the Business Partner API:</P><P>"/sap/opu/odata/sap/API_BUSINESS_PARTNER"</P><P>The API path is just appended to the host, so:</P><P>"<A href="http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER</A>"</P><P>Clicking on the link in the browser shows additional information about the API. Note that the only services listed are A_BusinessPartner and A_BusinessPartnerAddress.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-10-4.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300248iD5D9B28D88C78E83/image-size/large?v=v2&px=999" role="button" title="Integration-10-4.png" alt="Integration-10-4.png" /></span></P><P>When building an integration flow, the mix of host names, port names, and paths can quickly become confusing. It's useful to track these as we go.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-10-5.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300249i78F4EB26BF7E6792/image-size/large?v=v2&px=999" role="button" title="Integration-10-5.png" alt="Integration-10-5.png" /></span></P><P>Further reading on the mock server:</P><P>- <A href="https://github.com/SAP-samples/connecting-systems-services-integration-suite-codejam" target="_self" rel="nofollow noopener noreferrer">The CodeJam repo</A><BR />- <A href="https://github.com/SAP-archive/cloud-s4-sdk-book/tree/mock-server" target="_self" rel="nofollow noopener noreferrer">GitHub</A><BR />- <A href="https://learning.sap.com/learning-journeys/develop-advanced-extensions-with-sap-cloud-sdk/exercise-setting-up-the-mock-server_c734679d-9ce9-4905-82c3-ed13603a671d" target="_self" rel="noopener noreferrer">SAP Learning</A></P><H2 id="toc-hId--2070765225">Point 3: Application programming interface (API</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-3.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300253i7FD22496BC81C5DF/image-size/large?v=v2&px=999" role="button" title="Integration-map-3.png" alt="Integration-map-3.png" /></span></P><P><STRONG>Purpose:</STRONG> Provide a standard way to define and operate services for an application that can be consumed by other applications.</P><P><STRONG>What is it:</STRONG> SAP have a large catalogue of standard APIs that come with S/4HANA.</P><H3 id="toc-hId-1734285559">The Business partner API</H3><P>The API that was introduced under the S/4HANA business partner mock server is called 'business partner (A2X)'. It is a SAP standard API that uses the OData V2 standard.</P><P>While HTTP is the communication protocol. OData is an open standard related to the data.</P><P>When viewing the API details in the web browser, the display was JSON. This is JavaScript Object Notation, which is used in Odata. Point 5. in the flow will show more detail on this API.</P><P>Further reading on APIs and Odata:</P><P>- <A href="https://spec.openapis.org/oas/latest.html" target="_self" rel="nofollow noopener noreferrer">Open API spec</A><BR />- <A href="https://www.odata.org/" target="_self" rel="nofollow noopener noreferrer">OData</A></P><H2 id="toc-hId-1831175061">Point 4: Business technology platform (BTP)</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-4.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300256iC627FB8F7F6F5B78/image-size/large?v=v2&px=999" role="button" title="Integration-map-4.png" alt="Integration-map-4.png" /></span></P><P>Skipping Cloud Connector for now, brings us to BTP. Details from BTP are needed to set up Cloud Connector.</P><P><STRONG>Purpose:</STRONG> Enable customers to manage and build on SAP applications.</P><P><STRONG>What is it:</STRONG> A set of tools encompassing various capabilities and environments.</P><P>SAP offer a free trial for BTP, which can be used to build and test integration flows. Instructions on how to register and set up BTP are included in part 3.</P><P>The BTP cockpit is where we can search for and set up different services.</P><P>It supports multiple infrastructures and runtimes so you can manage/build various types of applications from traditional SAP ABAP to web apps.</P><UL><LI>Supports multiple infrastructures/runtimes & languages, including:<UL><LI>Cloud Foundry: develop new apps/services, multiple languages, runtimes</LI><LI>ABAP: extend ABAP based products (S/4HANA)</LI><LI>Kyma: Kubernetes to develop/run cloud-native apps</LI><LI>Neo: HTML5, Java, and HANA extended apps</LI></UL></LI></UL><P>BTP has multiple regions and infrastructure providers</P><UL><LI>Regional deployment<UL><LI>Provided by SAP or Infrastructure-as-a-Provider (IaaS)</LI><LI>AWS, Azure, Google Cloud, Alibaba Cloud</LI></UL></LI><LI>The key features of BTP include managing and building:<UL><LI>Compose business processes</LI><LI>Application development and automation</LI><LI>Build and extend SAP applications</LI><LI>Integrate data</LI><LI>Analytics</LI><LI>Intelligent technologies</LI></UL></LI></UL><P>SAP Integration Suite utilises the Cloud Foundry environment. After we set up Business Technology Suite and SAP Intelligent Suite, a Cloud Foundry API endpoint will be provided in BTP.</P><P>In my case, this is "<A href="https://api.cf.ap21.hana.ondemand.com" target="_blank" rel="noopener nofollow noreferrer">https://api.cf.ap21.hana.ondemand.com</A>"</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-11.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300258i04DE1404995C9368/image-size/large?v=v2&px=999" role="button" title="integration-11.png" alt="integration-11.png" /></span></P><H2 id="toc-hId-1634661556">Point 5. Business Accelerator Hub</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-5.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300259iB0C161A99F8D04C2/image-size/large?v=v2&px=999" role="button" title="Integration-map-5.png" alt="Integration-map-5.png" /></span></P><P><STRONG>Purpose:</STRONG> Provides a central source of information on SAP's APIs</P><P><STRONG>What is it:</STRONG> A web page with API details. Highly integrated with BTP.</P><P>Business Accelerator hub is a web resource from SAP. I've drawn it inside BTP as it closely relates to BTP content. It's a central repository for APIs from SAP & selected partners.</P><P><A href="https://api.sap.com/" target="_self" rel="noopener noreferrer">api.sap.com</A></P><P>Main features</P><UL><LI>Discover, explore, and test APIs</LI><LI>Consume integration and workflow content</LI></UL><P>The Business Partner (A2X) API that is tested here can be viewed on Business Accelerator Hub.</P><OL><LI>Login to business accelerator hub</LI><LI>Search 'business partner (A2X)'</LI><LI>Click on the entry in the results</LI></OL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-12.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300265iA01898E1E22775A1/image-size/large?v=v2&px=999" role="button" title="integration-12.png" alt="integration-12.png" /></span></P><P>Some features of business accelerator hub:</P><UL><LI>Try out the APIs (sandbox environment)<UL><LI>Useful to view a sample of the response</LI></UL></LI><LI>View the API capabilities:<UL><LI>'API Reference' tab, scroll down to 'Business Partner' and click on it</LI><LI>This shows the list of capabilities of the API</LI></UL></LI><LI>View the API specification<UL><LI>'Overview' tab, scroll down to 'API Specification' and click on it</LI><LI>Download OpenAPI JSON</LI><LI>View in web browser, text editor to see extensive details</LI></UL></LI></UL><P>The API hub is a useful resource in terms of discovering and designing potential API use.</P><P>From the mock server specification, we know it's limited to only a few capabilities. We can find the path names for each of these on API hub:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-12-1.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300267iB9991B6EA91D1FC9/image-size/large?v=v2&px=999" role="button" title="Integration-12-1.png" alt="Integration-12-1.png" /></span></P><P>This confirms the mock server only has a small fraction of the full business partner (A2X) capabilities. This makes sense given how extensive business partner data is in SAP. It's noteworthy that the mock server only supports 'read' activities. We can't test creating or changing a business partner.</P><P>The details of each of these requests can be viewed by clicking into them.</P><P>While there are three request paths. The address path can be added to the "all business partners" or "single business partner", so there are four possibilities:</P><UL><LI>All business partners<UL><LI>"/A_BusinessPartner"</LI></UL></LI><LI>All business partners with address<UL><LI>"/A_BusinessPartner/to_BusinessPartnerAddress"</LI></UL></LI><LI>Single business partner<UL><LI>"/A_BusinessPartner('{BusinessPartner}')"</LI></UL></LI><LI>Single business partner with address<UL><LI>"/A_BusinessPartner('{BusinessPartner}')/to_BusinessPartnerAddress"</LI></UL></LI></UL><P>These paths describe services of the API and are appended to the base URL.For example:</P><P>"<A href="http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('{BusinessPartner}')/to_BusinessPartnerAddress" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('{BusinessPartner}')/to_BusinessPartnerAddress</A>"</P><P>The correct terminology for these URLs:</P><UL><LI>Base URL/host: <A href="http://localhost:3005" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005</A></LI><LI>Base path: /sap/opu/odata/sap/API_BUSINESS_PARTNER</LI><LI>Entity set: /A_BusinessPartner</LI><LI>Key Access: ('1234567')</LI><LI>Navigation property: /to_BusinessPartnerAddress</LI></UL><P>"('{business partner}')" in the example is a placeholder for a business partner number.</P><P>Updating the flow diagram with these details:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-12-2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300270i7171352709F09CC3/image-size/large?v=v2&px=999" role="button" title="Integration-12-2.png" alt="Integration-12-2.png" /></span></P><H2 id="toc-hId-1438148051">Point 6: SAP Integration Suite</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-6.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300271i8FBBDE02F7A3CF64/image-size/large?v=v2&px=999" role="button" title="Integration-map-6.png" alt="Integration-map-6.png" /></span></P><P><STRONG>Purpose:</STRONG> Design and manage communications between applications.</P><P><STRONG>What is it:</STRONG> A service of SAP BTP.</P><P>SAP Integration Suite is one of the services available in Business Technology Platform. Therefore, a prerequisite is to register for the BTP free trial.</P><P>SAP Integration Suite can then be found under 'Services Marketplace'.</P><P>SAP Intelligent suite can be used for Cloud, on-premise, and hybrid scenarios. It includes pre-built, best-practice integration packs</P><P>Technically, it's a Java based app, and utilises the Apache Camel framework.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-13.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300274iC625747A413A6229/image-size/large?v=v2&px=999" role="button" title="Integration-13.png" alt="Integration-13.png" /></span></P><P> </P><P>The steps to install and set up are covered in part 3. After the initial set up you can navigate to the application.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-14.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300276i32A7F841634A21EB/image-size/large?v=v2&px=999" role="button" title="Integration-14.png" alt="Integration-14.png" /></span></P><P> </P><P>For this demo/test, the two key menus within Integration Suite are:</P><UL><LI>Design > Integrations and APIs</LI><LI>Monitor > Integrations and APIs</LI></UL><P>The design area allows us to create an integration flow which involves:</P><UL><LI>Specifying source or 'sender' system</LI><LI>Specifying target or 'receiver' system</LI><LI>Adding flow steps</LI><LI>Modify message header</LI><LI>Modify message contents</LI><LI>Route steps between sender and receiver.</LI></UL><P>Within design, there is a graphical editor to build the integration flow.</P><H3 id="toc-hId-948231539"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-15.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300279iBAFE6B3B4B1AE952/image-size/large?v=v2&px=999" role="button" title="Integration-15.png" alt="Integration-15.png" /></span></H3><P> </P><H3 id="toc-hId-751718034">Business Partner Integration Flow</H3><P>Creating the integration flow involves setting the sender details and designing the required transformations to meet the receiver (API) requirements.</P><P>As we work through this keep in mind the API expects one of four paths depending on the search scenario:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-15-A.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300281i7F82A0BD6569F827/image-size/large?v=v2&px=999" role="button" title="Integration-15-A.png" alt="Integration-15-A.png" /></span></P><H3 id="toc-hId-555204529">The sender</H3><P>The sender represents the address that SAP Integration Suite will listen on. This is the address we send a message to from our upstream app. In this case a web app.</P><P>This address is built up in three parts:</P><OL><LI>A base which is provided when we deploy the integration flow</LI><LI>An 'Address' that we specify in the integration flow</LI><LI>Further path details from the web app.</LI></OL><P>The base of the endpoint is something along the lines of:</P><P>https://{trial-account-specific-details}-rt.cfapps.ap21.hana.ondemand.com/http/</P><P>For the address name, this demo/test uses the path `/request-business-partners/*` The "`*`" at the end allow us to send requests with additional details that can be utilised in the flow logic.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-15-2.png" style="width: 813px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300287iEFB652F16F584247/image-size/large?v=v2&px=999" role="button" title="Integration-15-2.png" alt="Integration-15-2.png" /></span></P><P>The web app will send four different types of message to match the four API scenarios, for the demo/test I will use:</P><UL><LI>"/api/bp/single"</LI><LI>"/api/bp/all"</LI><LI>"/api/bp/single/add"</LI><LI>"/api/bp/single/all"</LI></UL><P>The web app will also include the BP number in the message body.</P><P>We don't need to specify these in the Integration Flow as the `*` will allow them all to pass as long as they are preceded by "request-business-partners/"</P><P>Adding this information to the mapping table.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-15-B.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300290i5293141B171F7138/image-size/large?v=v2&px=999" role="button" title="Integration-15-B.png" alt="Integration-15-B.png" /></span></P><H3 id="toc-hId-526874715">Routing and Transformations</H3><P>The integration flow routes and transforms the received messages to meet the API requirements at the receiver. This involves:</P><UL><LI>Routing of messages from receiver to sender based on their content</LI><LI>1:1 relationship for each of the four scenarios</LI><LI>Transform the URLs</LI><LI>A part of the transformation is extraction of the business partner number from the received message and the placement of it into the API format URL.</LI></UL><H3 id="toc-hId-330361210">The receiver</H3><P>The receiver is set up to match the S/4HANA business partner mock server.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-15-3.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300292i4EB268C6A7AECB21/image-size/large?v=v2&px=999" role="button" title="Integration-15-3.png" alt="Integration-15-3.png" /></span></P><P>More detail on the settings of each step are in part 3.</P><P>At this point, the integration flow is:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-15-4.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300293i3A47FABC1963733D/image-size/large?v=v2&px=999" role="button" title="Integration-15-4.png" alt="Integration-15-4.png" /></span></P><P> </P><P>As an alternative, the web app could have been programmed to send messages that already fit the API requirements. However, in some scenarios sender systems may be inflexible or difficult to develop on, making these transformation capabilities in Integration Suite important.</P><P>Further reading on SAP Integration Suite:</P><P>- <A href="https://help.sap.com/docs/integration-suite/sap-integration-suite/what-is-sap-integration-suite?locale=en-US" target="_self" rel="noopener noreferrer">Help - What is integration suite</A><BR />- <A href="https://camel.apache.org/" target="_self" rel="nofollow noopener noreferrer">Apache Camel</A></P><H2 id="toc-hId-427250712">Point 7. Cloud Connector</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-7.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300294iE46B3CEEA68DFE85/image-size/large?v=v2&px=999" role="button" title="Integration-map-7.png" alt="Integration-map-7.png" /></span></P><P><STRONG>Purpose:</STRONG> Allow SAP BTP to communicate to On-Premise SAP.</P><P><STRONG>What is it:</STRONG> An application that can provide a secure connection between SAP Cloud and On-Premise applications.</P><P>In the previous part, we defined the address details of the S/4HANA business partner mock server as:</P><UL><LI>Base URL/host: <A href="http://localhost:3005" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005</A></LI><LI>Base path: /sap/opu/odata/sap/API_BUSINESS_PARTNER</LI><LI>Entity set: /A_BusinessPartner</LI><LI>Key Access: ('1234567')</LI><LI>Navigation property: /to_BusinessPartnerAddress</LI></UL><P>If you paid attention to the screenshot of the receiver configuration in Intelligent Suite, you will note that it was set to</P><P>`<A href="http://s4-mock:3006/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('${property.employee_id" target="_blank" rel="noopener nofollow noreferrer">http://s4-mock:3006/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('${property.employee_id</A>}')`</P><P>The domain was "s4-mock:3006" not "localhost:3005".</P><P>This is because we can't connect SAP Cloud directly to an on-premise system. The S/4HANA business partner mock server is a JavaScript server that runs locally on desktop/laptop and is hence considered 'on-premise' or outside the SAP Cloud.</P><P>SAP provides "SAP Cloud Connector" to connect on-premise applications to the SAP Cloud.</P><P>It's a JavaScript application that can be installed and run locally. Part of the set-up involves entering authentication details from BTP.</P><P>After it's set-up, Cloud Connector will accept messages from Integration Suite and forward them to the S/4HANA business partner mock server.</P><P>The detailed set-up is covered in part 3.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-16.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300295i06526E809FBCA319/image-size/large?v=v2&px=999" role="button" title="Integration-16.png" alt="Integration-16.png" /></span></P><P>The screenshot above shows the "Cloud to On-Premise" mapping. A virtual host "s4-mock:3006" is mapped to the S4/HANA business partner mock server running locally on "localhost:3005".</P><P>Updating the integration flow:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-16-2.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300296i574EC1489A20E013/image-size/large?v=v2&px=999" role="button" title="Integration-16-2.png" alt="Integration-16-2.png" /></span></P><H2 id="toc-hId-230737207">Point 8: Web app</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-8.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300297i052F2A545DBA8042/image-size/large?v=v2&px=999" role="button" title="Integration-map-8.png" alt="Integration-map-8.png" /></span></P><P>The web app is an application that uses JavaScript as a programming language. Web browsers have JavaScript engines and can run JavaScript code.</P><P>There are two parts to the web app. The frontend and the backend.</P><P>Think of JavaScript in two categories. frontend JavaScript and server (backend) JavaScript.</P><H3 id="toc-hId--259179305">Frontend JavaScript</H3><UL><LI>Runs in the web browser, utilising the browsers JavaScript engine</LI><LI>Is oriented towards manipulating web documents (HTML documents), for example:</LI><LI>Retrieve fields from HTML (e.g. sign up form)</LI><LI>Update HTML (e.g. show results, dynamically add a new page)</LI><LI>The JavaScript engine in the browser has limitations.</LI></UL><H3 id="toc-hId--455692810">Server JavaScript</H3><UL><LI>Installed on a server (can also be run on a desktop/laptop)</LI><LI>A popular engine is Node.js</LI><LI>Is oriented towards messaging, connectivity, security, authentication</LI><LI>Has a lot less limitations than the web browser.</LI></UL><P>We could try to send a request from the frontend to SAP Integration Suite, but because it comes from a browser, it will likely result in errors.</P><P>I did try sending a message to Integration Suite from the browser, but received various CORS errors. CORS, or Cross-Origin Resource Sharing, is a browser security feature that controls whether a web page on one domain can access resources from a different domain.</P><P>Therefore, the frontend will send a request to the backend, which will then prepare the message and send it as a request to SAP Integration Suite.</P><P>Let's look at the frontend first, then the backend.</P><H2 id="toc-hId--358803308">Point 8.1 Web app: frontend</H2><P><STRONG>Purpose:</STRONG> Search for and display business partner details on a web page.</P><P><STRONG>What is it:</STRONG> A simple web app based on HTML, CSS and JavaScript.</P><P>The frontend can be built with plain HTML, CSS and JavaScript.</P><UL><LI>HTML: Used to define the content of the web page</LI><LI>CSS: Used to apply styles to the web page (layout, colours, font, etc.)</LI><LI>Javascript&colon; Use for programming logic, for example:<UL><LI>Get input field values from HTML</LI><LI>Fetch data from the server</LI><LI>Restructure data for display</LI></UL></LI></UL><P>HTML, CSS, and JavaScript are written in their own files. They are typically in the same folder.</P><P>```<BR />frontend/<BR />├── index.html<BR />├── styles.css<BR />└── script.js<BR />```</P><P>The HTML file includes references to the 'styles.css' and 'script.js' documents. These can all be written in simple text editors, but applications like 'visual studio code' help with syntax highlighting and formatting.</P><P>For demo/test these files can simply be kept on a computers hard drive. Or they could be hosted on a static web server like Netlify or GitHub pages.</P><H2 id="toc-hId--555316813">Point 8.2: Web app - HTML</H2><P>Web pages are written with HTML, they are hierarchically structured documents where 'tags' are used to denote different types of element which contain content.</P><P>As a simple illustration, the following would create a web page with a title, a text input field, a submit button and a space for results.</P><pre class="lia-code-sample language-markup"><code><header>
<p>This is the page title</p>
<body>
<article>
<form>
<label>Enter business partner number
<input type="text" />
</label>
<button type="submit">Submit</button>
</form>
<div id="js-results">
// Results go here
</div>
</body></code></pre><P>This would display:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-17.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300300i9BF0AF4B0E0552B9/image-size/large?v=v2&px=999" role="button" title="Integration-17.png" alt="Integration-17.png" /></span></P><UL><LI>A simple element such as paragraph is denoted by<UL><LI>`<p>enter paragraph</p>`</LI></UL></LI><LI>A more complex element, an input field is denoted by<UL><LI>`<input type="text" />`</LI><LI>In this case, `type` is an attribute set to `text` for text field</LI></UL></LI></UL><H3 id="toc-hId--1045233325">Getting HTML to talk to CSS and JavaScript**</H3><P>There are two attributes that allow them to work together:</P><UL><LI>"id": for example id="bp-input" (where bp-input is a variable name)</LI><LI>"class": for example class="bp-input" (where bp-input is a variable name)</LI></UL><P>These attributes can be added to HTML elements to allow us to access those elements with CSS and JavaScript. The difference between the two is a single "id" value is unique and should only be used once in an HTML document, while a class can be applied to multiple HTML elements.</P><P>The body of the web app frontend is:</P><pre class="lia-code-sample language-markup"><code><body>
<header class="header">
<div class="header-title">
<img class="logo" src="assets/team.png">
<p class="title">Employee portal: business partner search</p>
</div>
<nav class="nav">
<a href="/index.html">Home</a>
</nav>
</header>
<article class="bp-article flow">
<h2>Search</h2>
<div class="divider"></div>
<form id="bp-form" class="bp-search">
<label for="bp-inp-number">Business partner number:</label>
<p class="text-small">(Enter 7 digit number or leave blank to return all)</p>
<input id="bp-inp-number" class="bp-inp-number" name="bp" type="text" />
<p id="bp-error" class="bp-error"></p>
<p class="options">Options:</p>
<div>
<input id="bp-inp-address" value="add" type="checkbox" />
<label class="text-small" for="bp-inp-address" name="bp-input-address">Include address details</label>
</div>
<div>
<input id="bp-inp-tab" value="tab" type="checkbox" />
<label class="text-small" for="bp-inp-tabulate" name="bp-inp-tabulate">Show results in table</label>
</div>
<button id="js-inp-sub" type="submit">Submit</button>
</form>
<h2>Results</h2>
<div class="divider"></div>
<div id="js-bp-results" class="bp-results">
</div>
</article>
</body></code></pre><P>It's not very complex. Most of the complexity is in the CSS styling and the JavaScript programming to return the results.</P><P>This segregation of content (HTML), styles (CSS), and programming logic (JS) makes working with frontend well structured.</P><P>The web app initial HTML includes:</P><UL><LI>A header bar with the logo, page name and home link</LI><LI>A search section with search field options<UL><LI>BP number</LI><LI>Checkbox to get address</LI><LI>Checkbox to show results in detail view</LI></UL></LI></UL><P>This is how this looks without styling.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-18.png" style="width: 824px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300306i6242F0F51B915194/image-size/large?v=v2&px=999" role="button" title="integration-18.png" alt="integration-18.png" /></span></P><P> </P><P>The version with styling was shown at the start of the article.</P><P>Here is the <A href="https://alexroan.com/assets/documents/integration/frontend-html" target="_self" rel="nofollow noopener noreferrer">HTML file</A></P><H2 id="toc-hId--948343823">Point 8.3: Web app - CSS</H2><P>Cascading style sheets (CSS) are used to apply styles to HTML documents. Consider an HTML document with three lines of text:</P><pre class="lia-code-sample language-markup"><code><p id="line-one">This is text line one</p>
<p class="other-lines">This is text line two</p>
<p class="other-lines">This is text line three</p></code></pre><P>These can be styled with CSS as follows:</P><pre class="lia-code-sample language-css"><code>#line-one {
color: red;
font-size: 1.2rem;
}
.other-lines {
color: blue;
text-decoration: underline;
}</code></pre><P>This would show:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-19.png" style="width: 472px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300316iF0420D2F7368485B/image-size/large?v=v2&px=999" role="button" title="Integration-19.png" alt="Integration-19.png" /></span></P><P>The complete CSS for the demo/test web app is lengthy. Around 200 lines. Here is a snippet to get an idea of what it looks like:</P><P> </P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="integration-20.png" style="width: 822px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300317i8B8CC471FF9421E6/image-size/large?v=v2&px=999" role="button" title="integration-20.png" alt="integration-20.png" /></span></P><P>CSS is easy to pick up, but challenging to master!</P><P>Looking at the class "bp-search". This applies to the area of the HTML document where the search fields are collected. The CSS here does things like orient those search fields in a column "flex-direction:column" and apply a border and a shadow.</P><P>This is how our page looks with styling.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-21.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300318i0AF0854FE34C5F34/image-size/large?v=v2&px=999" role="button" title="Integration-21.png" alt="Integration-21.png" /></span></P><P> Here is the full <A href="https://alexroan.com/asssets/documents/integration/frontend-css.css" target="_self" rel="nofollow noopener noreferrer">CSS file</A></P><H2 id="toc-hId--976673637">Point 8.4: Web app - JavaScript</H2><P>Frontend JavaScript is able to retrieve, edit and add elements to the HTML document. Writing the JavaScript is possibly the most challenging part of this demo/test, so I'll just summarise what the code does:</P><UL><LI>Listen for a click of the 'submit' button</LI><LI>Get the value of the form input elements<UL><LI>The Business partner number</LI><LI>The status of the 'include address details' checkbox</LI><LI>The status of the 'show results in table' checkbox</LI></UL></LI><LI>Check the business partner value is valid<UL><LI>It has to be blank or a 7-digit number</LI></UL></LI><LI>Create a variable object called 'request' to track the request type</LI><LI>The variable includes:<UL><LI>request URL</LI><LI>request method</LI><LI>request body</LI><LI>(The ability to track multiple values in an Object is a key JS feature)</LI></UL></LI><LI>Based on the input fields, identify the request type & update the 'request' object.<UL><LI>The combinations are:<UL><LI>If bp number is blank and get address isn't checked</LI><LI>If bp number is blank and get address is checked</LI><LI>If bp number is entered and get address isn't checked</LI><LI>If bp number is entered and get address is checked.</LI></UL></LI></UL></LI></UL><P>At this point, the request object will store a set of values based on the input selections. The values will be one of the four options listed in the earlier tables.</P><P>The JavaScript now has what it needs to send a request to SAP Integration Suite. The rest of the JavaScript handles various things:</P><UL><LI>Use the JavaScript method 'fetch()' to send requests to the server</LI><LI>Handle security and authorisation</LI><LI>Getting a token if needed</LI><LI>Sending a token with requests</LI><LI>Handling errors</LI><LI>If successful, capturing the returned data</LI><LI>Working through the returned data and updating the HTML</LI><LI>Creating cards for the summary view</LI><LI>Creating a table for the detailed view</LI></UL><P>Here is the <A href="https://alexroan.com/assets/documents/integration/frontend-javascript.js" target="_self" rel="nofollow noopener noreferrer">JavaScript.</A></P><H2 id="toc-hId--1173187142">Point 9. Web app: backend server</H2><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-map-9.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300319i957338C0D7C637CE/image-size/large?v=v2&px=999" role="button" title="Integration-map-9.png" alt="Integration-map-9.png" /></span></P><P><STRONG>Purpose:</STRONG> Allow a web frontend to communicate with SAP BTP.</P><P><STRONG>What is it:</STRONG> A JavaScript web backend server for message formatting and routing.</P><P>The backend server is locally hosted on our computer for this test/demo, but in reality would be on a web server somewhere.</P><P>It's written in express, which is a framework on Node.js. It's quite different from frontend JavaScript.</P><P>The logic of the backend is:</P><UL><LI>Listen for communication from the frontend</LI><LI>If a message is received:<UL><LI>Do some manipulation of the message related to authorisations</LI><LI>Using fetch() try sending a request to SAP Integration Suite</LI><LI>More authorisation/security handling</LI><LI>If successful, return the response back to the browser</LI><LI>If unsuccessful, log and return the error.</LI></UL></LI></UL><P>Here is the backend <A href="https://alexroan.com/assets/documents/integration/backend-javascript.js" target="_self" rel="nofollow noopener noreferrer">JavaScript code</A>. This needs to be set up as part of a Node.js server.</P><P>The JavaScript server is a bit more complex than the frontend. The server folder contains:</P><P>```<BR />web-app-server/<BR />├── node_modules/ # created by npm install<BR />├── package-lock.json # created/updated by npm install<BR />├── package.json # you write this (or generate with `npm init`)<BR />└── server.js # your server code<BR />```</P><UL><LI>server.js contains the actual JavaScript code for the server.</LI><LI>package.json defines project settings, dependencies, and scripts.</LI><LI>node_modules/ and package-lock.json are automatically generated when dependencies are installed using npm install.</LI></UL><P>The port can be specified in 'server.js'. I choose port 5000.</P><P>This means the server will run on '<A href="http://localhost:5000" target="_blank" rel="noopener nofollow noreferrer">http://localhost:5000</A>'.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-24.png" style="width: 650px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300322i6AC5E5AE7259389E/image-size/large?v=v2&px=999" role="button" title="Integration-24.png" alt="Integration-24.png" /></span></P><P>After writing package.json and server.js, the following steps are required in terminal to initialise the server, install express, and then start the server.</P><UL><LI>cd web-app-server</LI><LI>npm init -y</LI><LI>npm install express</LI><LI>node server.js</LI></UL><P>We can now update the flow diagram with the details for the frontend.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Integration-25.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/300323iD986EC04D7C226DD/image-size/large?v=v2&px=999" role="button" title="Integration-25.png" alt="Integration-25.png" /></span></P><H2 id="toc-hId--1369700647">One flow, many messages</H2><P>Looking at the diagram, a click on the search button triggers a message that passes through four stages:</P><OL><LI>From the frontend (browser) to the backend web server</LI><LI>From the web server to SAP Integration Suite</LI><LI>From Integration Suite to Cloud Connector</LI><LI>From Cloud Connector to the S/4HANA system</LI></OL><P>JavaScript doesn’t normally “wait” for things to happen — it’s designed to keep running while other operations (like network requests) complete in the background.</P><P>However, the fetch() function is asynchronous, which means it starts a request and immediately returns a kind of “promise” — a placeholder that will eventually be resolved when the response comes back (or if it fails).</P><P>When testing this flow, there are multiple layers where errors can occur:</P><UL><LI>The browser console (frontend logs)</LI><LI>The backend server console (Node.js logs)</LI><LI>Integration Suite’s monitoring tools</LI></UL><P>The message can fail at any step, and it sometimes takes a bit of detective work to figure out where it failed and why.</P><P>If everything works, and S/4HANA returns a successful response (status code 200), that response flows automatically back through the same chain — each layer staying in a “waiting” state until the result is passed back to the frontend and displayed to the user.</P><P>Don't worry if it's not 100% clear, it took me a while to figure this out.</P><H2 id="toc-hId--1566214152">Conclusions to the walkthrough</H2><P>It's really fun to build your own frontend and connect it to a real enterprise grade system.</P><P>A few important considerations:</P><UL><LI>Precision is needed with the integration configuration:<UL><LI>Host names, types, routing, transformation are all sensitive to mistakes</LI></UL></LI><LI>The JavaScript is a little complex, but it is all well established</LI><LI>A JavaScript and Node.js course and some googling could enable anyone to create this.</LI><LI>Using the S/4HANA business partner mock server is a quick and fun way to test out a potential integration with S/4HANA. However, it is just a mock server with limited features and a build and test with a real S/4HANA system would be required.</LI></UL><P>However, it's easily achievable with a little study and practice and it opens the possibility to develop a wide range of things.</P><P>The APIs brings together:</P><UL><LI>Real-time fast access to a wide range of business data thanks to S/4HANA.<BR />Extreme flexibility on the frontend side thanks to modern HTML, CSS and JS.</LI></UL><P>This integration could have been much simpler by just having the frontend deliver a URL in the format required for the API. We don't really need the complexity of Integration Suite for this.</P><P>---</P><H1 id="toc-hId--1469324650">Part 3: building the integration flow (for IT people)</H1><P>In this section I'll share my rough notes from the process of building this front end and integration flow. This is a copy and paste of my original notes in markdown so I apologise for the lack of formatting. I do list all the required tools which may be helpful for people trying this out.</P><P>Before starting, I'd recommend working through the CodeJam: </P><UL><LI><A href="https://community.sap.com/t5/sap-codejam/sap-codejam-connecting-systems-and-services-using-sap-integration-suite/ec-p/14110686#M848" target="_self">SAP CodeJam</A></LI><LI>The instructions are on a <A href="https://github.com/SAP-samples/connecting-systems-services-integration-suite-codejam" target="_self" rel="nofollow noopener noreferrer">GitHub repository.</A></LI></UL><H2 id="toc-hId--1959241162">Information sources & tools</H2><H3 id="toc-hId-1845809622">SAP Accounts: BTP and Integration Suite</H3><P>A trial account for business technology platform is required.</P><P>[BTP trial](<A href="https://developers.sap.com/tutorials/hcp-create-trial-account.html" target="_blank" rel="noopener noreferrer">https://developers.sap.com/tutorials/hcp-create-trial-account.html</A>)</P><P>And a a trial for Integration Suite</P><P>[Integration Suite trial](<A href="https://developers.sap.com/tutorials/cp-starter-isuite-onboard-subscribe.html#f55ec71c-2853-4b83-8092-4e3031f8d6e6" target="_blank" rel="noopener noreferrer">https://developers.sap.com/tutorials/cp-starter-isuite-onboard-subscribe.html#f55ec71c-2853-4b83-8092-4e3031f8d6e6</A>)</P><P>See the pre-requisites [pre-requisites](<A href="https://github.com/SAP-samples/connecting-systems-services-integration-suite-codejam/blob/main/prerequisites.md" target="_blank" rel="noopener nofollow noreferrer">https://github.com/SAP-samples/connecting-systems-services-integration-suite-codejam/blob/main/prerequisites.md</A>) document in the CodeJam repository.</P><H3 id="toc-hId-1649296117">Containerisation & Docker</H3><P>When running the S/4HANA business partner mock server locally, one option is to install the necessary JavaScript runtime environment and run it manually. Another option is to run it inside a container.</P><P>Containers are a key concept in Cloud architecture.</P><P>A container packages an app and all it's dependencies together so that it can run independently of the underlying computer (server, laptop, etc.).</P><P>This is a key concept for Cloud as it allows applications to run on different hardware and operating systems with minimal set up effort.</P><P>Docker is a platform to build and manage containers.</P><P>Docker and container features:</P><P>- Package an app and all it's dependencies<BR />- A container is like a lightweight virtual machine<BR />- Key terms<BR />- image: blueprint (.zip) containing app, dependencies, and OS<BR />- container: running instance of an image<BR />- dockerfile: instructions to build image<BR />- volume: how to persist data outside the container<BR />- port mapping: expose internal port to machine (e.g. 8080 to 3001).</P><P>I'll come back to this in the section on running the BP mock server.</P><H3 id="toc-hId-1452782612">Data basics</H3><P>The following data standards/formats are used in this exercise:</P><P><STRONG>JSON (JavaScript Object Notation)</STRONG></P><P>- A lightweight, human-readable format for storing and sharing structured data<BR />- Looks like nested key-value pairs (like a shopping list with categories)<BR />- Commonly used in web apps and APIs for sending data between systems.</P><P>For example:</P><P>```JSON<BR />{<BR />"employee_id": "1234567",<BR />"employee_name": "Alexander"<BR />}<BR />```</P><P><STRONG>XPATH</STRONG></P><P>- A query language used to navigate and extract data from XML or HTML documents<BR />- Lets you point to specific elements using a path-like syntax<BR />- Example: find the third paragraph inside a section<BR />- Used in tools like web scrapers and automation scripts.</P><P>For example:</P><P>```XPATH<BR />//title[contains(text(), 'Programming')]<BR />```</P><P><STRONG>XML (eXtensible Markup Language)</STRONG></P><P>- A flexible, tag-based format for representing structured data<BR />- Similar to HTML in appearance<BR />- But used for data storage and exchange, not page display.</P><P>For example:</P><P>```XML<BR /><book id="bk01"><BR /><author>Roan, Alexander</author><BR /><title>Front end to S/4HANA</title><BR />```</P><P><STRONG>HTML (HyperText Markup Language)</STRONG></P><P>- The standard language for building web pages and displaying content in browsers<BR />- Uses tags to define elements like headings, paragraphs, links, and images<BR />- Focused on structure and layout, not data exchange.</P><H3 id="toc-hId-1256269107">Terminal</H3><P>I worked through this demo/test on Mac so I used Terminal, which is the Mac default command line interface (CLI).</P><P>The CLI is necessary for activities such as setting up and starting servers or working with docker containers.</P><P>Terminal basics</P><P>- Open a folder `cd <folder name>` (change directory)<BR />- `cd` on it's own will go to the home directory<BR />- (Note that `~` represents home directory in terminal)<BR />- `cd ..` will go up a folder<BR />- List folders `ls` (list files in the current directory)<BR />- Open a file `open <file name>` (open a file)<BR />- Quit sub-screen and return to terminal `q`<BR />- Stop a running process hold control and c<BR />- Clear terminal `clear`</P><P>To run JavaScript servers, JavaScript runtime is required. It's easier to install and manage things like this using a package manager in Terminal. Homebrew is a popular package manager for Mac.</P><P>Homebrew</P><P>- A package manager for Mac<BR />- To install homebrew homebrew:<BR />- Launch terminal (launchpad > other > terminal)<BR />- Visit [Homebrew](<A href="https://brew.sh/" target="_blank" rel="noopener nofollow noreferrer">https://brew.sh/</A>) in your web browser<BR />- Copy the installation command<BR />- Paste it into terminal press enter.</P><H3 id="toc-hId-1227939293">Java/JavaScript</H3><P>To complete the demo/test a few different JavaScript things are needed.</P><P>Node.js</P><P>- This is a JavaScript that can be installed locally to create and run web-servers and web applications<BR />- Install using Homebrew<BR />- In terminal, enter: `brew install node`<BR />- Test the installation of Node.js<BR />- In terminal, enter: `node -v`, it should return the node version number.</P><P>NPM</P><P>- NPM is the node package manager<BR />- It's installed with Node.js<BR />- It's used to run a server<BR />- Install it in any directory a Node.js server sits in<BR />- To check the installation of NPM<BR />- In terminal, enter: `npm -v`, it should return the npm version number</P><P>Java development kit (JDK)</P><P>- Cloud Connector is a more complex application and requires JDK<BR />- More notes in the Cloud Connector section.</P><H3 id="toc-hId-1031425788">API client (Bruno/Postman)</H3><P>The CodeJam utilised [Bruno](<A href="https://www.usebruno.com/" target="_blank" rel="noopener nofollow noreferrer">https://www.usebruno.com/</A>) for API testing.</P><P>For the CodeJam a folder of pre-configured settings for Bruno is provided. However I'd suggest to start experimenting without the pre-configuration to build a solid understanding of the basics.</P><P>I'll include more notes in later sections.</P><H2 id="toc-hId-1128315290">Building and testing an integration flow</H2><P> </P><H3 id="toc-hId-638398778">Set up the S/4HANA business partner mock server</H3><P>Start by setting up the S/4HANA business partner mock server</P><P>- Download the mock server from [GitHub](<A href="https://github.com/SAP-archive/cloud-s4-sdk-book/tree/mock-server" target="_blank" rel="noopener nofollow noreferrer">https://github.com/SAP-archive/cloud-s4-sdk-book/tree/mock-server</A>)<BR />- Scroll down to the readme<BR />- Either download the archive linked under 'How to run this server'<BR />- Or if using GitHub clone the repository and checked the branch 'mock-server'<BR />- Move it to a convenient folder of your choice<BR />- I set it a `users/<username>/projects/integration/cloud-s4-sdk-book`</P><P> </P><H3 id="toc-hId-441885273">Run the server: option 1: use NPM</H3><P>Node.js and the node package manager (NPM) can be used to run the server directly on a computer.</P><P>- Open terminal<BR />- Navigate to `users/<username>/projects/integration/cloud-s4-sdk-book`<BR />- (or wherever you saved the folder)<BR />- Enter: `npm install` (install node package manager in the folder)<BR />- Enter: `npm start` (start the server)<BR />- This should return something like:</P><P>```shell<BR />> bupa-mock-odata@1.0.0 start<BR />> node server.js<BR />Mock server started on port 3000 after 1 ms, running - stop with CTRL+C (or CMD+C)...<BR />```</P><P>Terminal tells us which port the server is running on. Port "3000" is accesible in the browser or an API client via "<A href="http://localhost:3000" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3000</A>".</P><P>To stop the server in terminal use `ctrl+c`.</P><P>For the curious, you can look at the files that make up the mock server in the above folder. Check out:</P><P>- server.js<BR />- This includes the JavaScript code for the server<BR />- The code references other files such as app.js<BR />- package.json<BR />- This is like a configuration file for a node.js server<BR />- business partner > business-partner-data.json<BR />- This contains the demo test business partner data.</P><P>Theoretically you could use this Node.js server as a template to simulate other SAP Odata APIs with some adjustments to these files.</P><P> </P><H3 id="toc-hId-245371768">Run the server: option 2: use Docker</H3><P>The mock server can also be run as a Docker container. This is a little more convenient as after the first run we can stop and start it from the Docker desktop app.</P><P>Note the server already has a Dockerfile, so it's already set up to run as a container.</P><P>If we run something inside a docker container we need to interact with it via ports on the container. The application is really running contained inside a container. When we run a docker container we provide a mapping between a local port on the computer and the container port. We can then access the docker application via this mapping.</P><P>To run as a Docker container:</P><P>- Launch the docker app<BR />- Open terminal<BR />- Enter `docker run -p 3005:8080 bp-mock-server`<BR />- 3005 is the local port<BR />- 8080 is the docker container port<BR />- Local port can be any free port on your computer. I choose 3005<BR />- The container port is 8080<BR />- View the status of the container in the Docker app<BR />- Use the browser to check `http//localhost:3005`</P><P>Note if there wasn't already a dockerfile we would need to create one and build the app before running it.</P><P>**A simple docker demo**</P><P>This was my first time using docker, so I experimented by creating a simple "Hello, World!" style server from scratch. Here it is if you want to try:</P><P>- Create a JS file "index.js"<BR />- Add `console.log("hello from docker");`<BR />- This just prints text to the console (Terminal)<BR />- Create a package file "package.json"<BR />- Add the following JSON to "package.json"</P><P>```json<BR />{<BR />"name": "hello-docker",<BR />"version": "1.0.0",<BR />"main": "index.js",<BR />"scripts": {<BR />"start": "node index.js"<BR />}<BR />```</P><P>- Create a dockerfile "dockerfile"<BR />- Add the following to "dockerfile"</P><P>```Dockerfile<BR />FROM node:18</P><P>WORKDIR /usr/src/app</P><P>COPY package*.json ./<BR />RUN npm install</P><P>COPY . .</P><P>CMD ["npm", "start"]<BR />```</P><P>You can see Docker uses NPM, in the same way we would with a manual run, but it's installing and running NPM inside the container, not on the computer.</P><P>To build and run:</P><P>- Build docker container `docker build -t hello-docker .`<BR />- Run docker container `docker run hello-docker`</P><P> </P><H2 id="toc-hId-342261270">Testing with the web browser</H2><P>The simplest way to test the API is running locally is to put the local address in the web browser.</P><P>- For NPM it was "http//localhost:3000"<BR />- For docker image it was "http//localhost:3005"</P><P>The main domain should return the API details including the links such as:</P><P>"<A href="http://localhost:3000/sap/opu/odata/sap/API_BUSINESS_PARTNER" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3000/sap/opu/odata/sap/API_BUSINESS_PARTNER</A>"<BR />"<A href="http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER</A>"</P><P>To access the service to return the general data of all business partners we add A_BusinessPartner</P><P>"<A href="http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner</A>"</P><P>In the browser, this should return a JSON document containing the list of business partners.</P><P>We can pick a business partner number from the list and use it with the path to select a specific business partner:</P><P>"<A href="http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('1003764" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('1003764</A>')"</P><P> </P><H2 id="toc-hId-145747765">Testing with an API client (Bruno)</H2><P>Rather than just using the web browser to check the API an API client can be used, this has a few benefits:</P><P>- We can build the URLs through a selection of 'input fields'<BR />- We can save different requests for easy and quick re-testing<BR />- We can pass data in the request body</P><P>To test with Bruno:</P><P>- Launch Bruno<BR />- Use the '...' menu to create a collection<BR />- Name it 'bp-mock'<BR />- Specify a location. I used "users/{username}/projects/integration"</P><P>Create a request for all business partners</P><P>- Use the '...' menu next to bp-mock and select 'new request'<BR />- Enter request name 'All business partners'<BR />- Under URL select 'GET' and enter the URL that returns all business partners<BR />- `<A href="http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner" target="_blank" rel="noopener nofollow noreferrer">http://localhost:3005/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner</A>`</P><P>Run a request</P><P>- Look to the right of the 'GET' line on the main page and click the '->' to run<BR />- The right panel will show the JSON response<BR />- The same response as shown earlier in the web browser.</P><P>Create a request for a single business partner (1003765)</P><P>- Use the '...' menu next to bp-mock and select 'new request'<BR />- Enter request name 'Specific business partners'<BR />- Enter the same URL details as above.<BR />- In the 'params' tab click '+ param' and enter<BR />- Name: `&filter`<BR />- Path: 'BusinessPartner eq '1003766'<BR />- Run the request. A single business partner should be returned.</P><P>Note as the params are entered the URL dynamically updates.</P><P><STRONG>Basics on OData API URLs</STRONG></P><P>- The base for the API is "/API_Business_Partner"<BR />- A service of the API is then appended "/A_BusinessPartner"<BR />- Queries can then by added, OData queries include:<BR />- Filtering: `/A_BusinessPartner?$filter=Name eq 'Max'`<BR />- Selecting fields: `/A_BusinessPartner?$select=Name,City`<BR />- Pagination: `/A_BusinessPartner?$top=5&$skip=10`<BR />- Accessing nested data: `/A_BusinessPartner?$expand=Address`<BR />- When working with OData:<BR />- Field names are case sensitive<BR />- String values in single quotes</P><P>Keep in mind the S/4HANA mock business partner server only includes limited functionality. The above filters and selects won't work.</P><H3 id="toc-hId--344168747">Java SDK for SAP Cloud Connector</H3><P>The next step is to set up SAP Cloud Connector</P><P>Recall Cloud Connector will provide a secure tunnel allowing SAP Cloud to talk to the S/4HANA business partner mock server.</P><P>Cloud Connector requires a full Java Development Kit (JDK).</P><P>- You can use "javac -version" in terminal to check if you already have JDK<BR />- There is a SAP Help page for [Cloud Connector](<A href="https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/cloud-connector?locale=en-US" target="_blank" rel="noopener noreferrer">https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/cloud-connector?locale=en-US</A>)<BR />- Check the [prerequisites](<A href="https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/prerequisites?locale=en-US#jdks" target="_blank" rel="noopener noreferrer">https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/prerequisites?locale=en-US#jdks</A>) section, it lists the JDK options<BR />- I choose SAP machine 21 JDK<BR />- You can download this from [GitHub](<A href="https://sap.github.io/SapMachine/" target="_blank" rel="noopener nofollow noreferrer">https://sap.github.io/SapMachine/</A>)<BR />- I have a dev folder for items like this in my home folder<BR />- "Users/{username}/dev"<BR />- Use Homebrew to install<BR />- Open terminal and enter "brew install openjdk@21"</P><P>It's likely you may run into version, compatibility, authorisation issues. These are all very common and a web search should help.</P><H3 id="toc-hId--540682252">Install Cloud Connect</H3><P>Next install Cloud Connector.</P><P>Cloud connector is listed under the SAP development tools page under [Cloud](<A href="https://tools.hana.ondemand.com/#cloud" target="_blank" rel="noopener nofollow noreferrer">https://tools.hana.ondemand.com/#cloud</A>).</P><P>- Download the cloud connector file for your OS<BR />- My Mac is Apple Silicon so I chose 'sapcc-2.18.1.2-macosx-aarch64.tar.gz'<BR />- Unzip and move it to a folder of your choice<BR />- Navigate inside the downloaded folder in terminal<BR />- Check contents with 'ls', you should see a 'go.sh' file<BR />- Enter: './go.sh' this will run Cloud Connector<BR />- Cloud connector should now be running, note the address in the terminal log<BR />- Login with default account: 'Administrator' and password: 'manage'<BR />- Change password<BR />- Keep a note of the account and password.</P><P>I received authorisation issues on the first attempt to run it:</P><P>- Click through them, then goto apple > settings > privacy&security<BR />- Scroll down to security and click 'allow anyway'<BR />- Try: './go.sh' again.</P><H3 id="toc-hId--569012066">Install and set up SAP Integration Suite</H3><P>To continue from here SAP Integration has to be installed and active as per the earlier instructions.</P><H3 id="toc-hId--765525571">Connect SAP Cloud Connector to SAP Integration Suite</H3><P>As Cloud Connector bridges between SAP Cloud and the S/4HANA business partner mock server we need to set it up to connect to SAP Cloud. We get the security/authentication data to do this from our SAP BTP trial account.</P><P>- After logging into Cloud Connector click '+Add Subaccount'<BR />- Hit 'next' to skip the HTTPS settings<BR />- Select 'Configure using authentication data'<BR />- Select 'Add subaccount authentication data from file '<BR />- In your browser got to your SAP BTP trial homepage<BR />- Click on your subaccount<BR />- On the left menu expand Connectivity and select Cloud Connectors<BR />- Click on 'download authentication data'<BR />- Return to Cloud Connector<BR />- Click browse and select the downloaded file 'authentication.data'<BR />- Click next<BR />- Leave location ID blank<BR />- (This would be relevant if there were multiple Cloud Connectors)<BR />- Click finish.</P><P>Double check the settings in the subaccount overview:</P><P>- BTP trial region = Cloud Connector region<BR />- BTP Subaccount ID = Cloud Connector Subaccount<BR />- The region host in Cloud Connector = Cloud Foundry API Endpoint in BTP.</P><H3 id="toc-hId--962039076">Cloud Connector to Mock BP Server</H3><P>There's no security on S/4HANA business partner mock server so it is simply a matter of adding the address.</P><P>- In Cloud Connector, on the left sidebar click 'Cloud to On-Premise'<BR />- To the right of 'Mapping Virtual to Internal Systems' click `+`<BR />- Select back-end, enter: "Non-SAP system", click 'next'<BR />- Select protocol: "HTTP", click 'next'<BR />- For internal host enter "localhost"<BR />- For internal port enter: "3005"<BR />- For virtual host: "s4-mock"<BR />- For virtual Port: "3006"<BR />- Uncheck allow principal propagation<BR />- Click through to finish.</P><P>Replace the internal port name with the one your mock server is running on locally. You are free to choose the virtual host and port.</P><P>It's critical to select "Non-SAP system" and HTTP, not HTTPS.</P><P>A new entry will appear under 'Mapping Virtual to Internal Systems'</P><P>- Make sure your mock bp server is still running on the host and IP you entered<BR />- From the icons on the right, click on 'check availability..'<BR />- You should see status 'reachable'</P><P>At this stage `<A href="https://localhost:3005" target="_blank" rel="noopener nofollow noreferrer">https://localhost:3005</A>` is now mapped to `<A href="https://s4-mock:3005" target="_blank" rel="noopener nofollow noreferrer">https://s4-mock:3005</A>` in the SAP Cloud.</P><P>You can also check in integration suite to see if Cloud Connector is connected.</P><P>- In the left hand menu select connectivity > cloud connectors.</P><P>Errors at this stage are likely related to</P><P>- Mistakes in the host name, port name, or system type<BR />- The mock server or cloud connector is not running.</P><H3 id="toc-hId--1158552581">Design integration flow</H3><H4 id="toc-hId--1648469093">Create an integration flow</H4><P>- Login to BTP trial home<BR />- From the left hand menu expand Services and select Instances and subscriptions<BR />- Under 'Subscriptions' click on 'Integration Suite'<BR />- On the left menu select expand 'Design' and select 'Integrations and APIs'<BR />- Choose 'Create' on the top right to create a new package.<BR />- Give it a name<BR />- Shift to the 'Artifacts' tab<BR />- Select 'Add' and choose 'Integration Flow' from the list<BR />- Give it a name<BR />- Click add<BR />- Click on the newly created integration flow</P><P>The integration flow screen is read-only by default, click edit.</P><P><STRONG>Set up the sender</STRONG></P><P>Define an 'address' for the SAP Integration Suite endpoint.</P><P>- Click on sender<BR />- Click connector arrow<BR />- Drag to start event<BR />- In adapter type pop-up select HTTPS<BR />- To configure the adapter click on the drawn line (if not selected)<BR />- The settings are in the bottom panel, drag it up to expand it<BR />- Navigate to 'connection' tab, enter the following:<BR />- Address: `/request-business-partners`<BR />- Authorisation: `User Role`<BR />- User Role: `ESBMessaging.send`<BR />- CSRF Protected: `Unchecked` (Cross-site request forgery).</P><P><STRONG>Add flow elements</STRONG></P><P>The CodeJam has excellent instructions for walking through different flow steps as per their exercises.</P><P>I will summarise a few elements I used in my design.</P><P><STRONG>Router</STRONG></P><P></P><P>- The router allows you to split the flow based on a condition.<BR />- This example splits the flow into 4 based on the incoming URL<BR />- For example where the incoming URL ends in "single/add"<BR />- Re-call our integration flow address was "request-business-partners"<BR />- In this case a message arrives to "request-business-partners/single/add"<BR />- Intelligent Suite assigns the last part to the variable CamelHttpPath<BR />- Which is part of the message header hence: header.CamelHttpPath.</P><P>The route path we are looking at in this example is the one that returns a single business partner with address data.</P><P><STRONG>Content modifier - case 1</STRONG></P><P>Case 1:</P><P>- The content modifier allows us to modify the message header or body.<BR />- In the above screenshot a content modifier is added directly after the routing.<BR />- This deletes the CamelHttpPath, in this case "single/add"<BR />- After routing we no longer need this part of the URL in the message header.</P><P><STRONG>JSON to XML converter</STRONG></P><P>- This converts the JSON in the message body to XML.<BR />- In the case of searching for a single BP the message body includes JSON:</P><P>```JSON<BR />{<BR />"employee_id": "1234567"<BR />}<BR />```</P><P>- This will be converted to XML</P><P>```XML<BR /><root><BR /><employee_id>1234567</employee_id><BR /></root><BR />```</P><P><STRONG>Content modifier - case 2</STRONG></P><P></P><P>- In this case the content modifier gets "employee_id" from the message body<BR />- And assigns it to a new variable<BR />- XPath can be used to access the XML value<BR />- "/root/employee_id"<BR />- The variable name is set as employee_id<BR />- The data type is set as a Java string.<BR /><BR /><STRONG>Request Reply</STRONG></P><P>Request reply let's us send a request to a server.</P><P>- Click on the Set employee_id<BR />- Click add flow step on the canvas<BR />- Select 'Request Reply' under call > external call<BR />- Click on 'Request Reply'<BR />- Click on 'connector' and drag to the receiver<BR />- Select adapter type 'HTTP'<BR />- Under 'HTTP' in the connector properties, select 'Connection'<BR />- Enter the address of the cloud connector:<BR />- The path for a single business partner with address data involves updating:<BR />- Address: "<A href="http://s4-mock:3006/sap/opu/odata/sap/API_Business_Partner/A_BusinessPartner('${property.employee_id" target="_blank" rel="noopener nofollow noreferrer">http://s4-mock:3006/sap/opu/odata/sap/API_Business_Partner/A_BusinessPartner('${property.employee_id</A>}')"<BR />- Query: "$expand=to_BusinessPartnerAddress"<BR />- Proxy Type: `On-premise`<BR />- Method: `GET`<BR />- Authentication: `None`<BR />- Save<BR />- Deploy</P><P>To check deployment status go to Monitor > Integration and APIs. On this page the endpoint to access the service is shown:</P><P>"https://{your trial}-cpitrial03-rt.cfapps.ap21.hana.ondemand.com/http/request-business-partners"</P><H4 id="toc-hId--1844982598">Test Cloud Integration with API client</H4><P>At this point we can test consuming the API through SAP Integration Suite.</P><P>Unlike testing the local mock server, we need to deal with authentication and security. The way this works is:</P><P>- We pass a "client id" and "secret" to a "token URL"<BR />- BTP passes back a "token" which is valid for a certain period of time<BR />- This "token" has to be attached to any requests to the API in Intelligent Suite.</P><P><STRONG>Accessing security details</STRONG></P><P>- Navigate to your BTP trial account<BR />- Expand services and click on 'instances and subscriptions'<BR />- Scroll down to instances and look for your integration flow instance<BR />- Integration Suite uses Cloud Foundry so the runtime will be cloud foundry<BR />- It will likely be named 'default_it-rt_integration-flow'<BR />- Scroll down to service keys and click on the service key, note the values for:<BR />- "clientid"<BR />- "clientsecret"<BR />- "url"<BR />- "tokenurl"</P><P>For local testing we can hardcode these values in our test tools, but be careful not to upload or share these anywhere.</P><P>In production, never hardcode secrets or tokens. Use environment variables or a secure credential store.</P><P>**Request a token with Bruno**</P><P>In Bruno create a new request:</P><P>- Name: `TOKEN`<BR />- Method: `POST`<BR />- URL: enter the "tokenurl" from above<BR />- Navigate to the Params tab:<BR />- Select 'Add Param'<BR />- Enter name: `grant_type` path: `client_credentials`<BR />- Navigate to Auth<BR />- Switch 'Inherit' to 'Basic Auth' and enter:<BR />- Username: `client_id`<BR />- Password: `client_secret`<BR />- Save</P><P>Send the request. This should return a JSON document with a long value in "access_token". There should also be a expiry time e.g. 4199 seconds.</P><P>When sending a request, if the token is not valid Integration Suite will return a 401 error code. This means we need to request a new token.</P><P>Within Bruno we can save this token value to a variable. This saves us from copying and pasting it into other requests.</P><P>- Goto Environments > Configure > Create Environment<BR />- Name: 'integration-flow'<BR />- Click '+ Add Variable'<BR />- Enter name: 'access_token'<BR />- For value, leave it blank<BR />- Save and close</P><P>Navigate to 'scripts' under the TOKEN request. Under Post Request enter:</P><P>```JS<BR />if (res.status == 200) {<BR />const token = res.body.access_token;<BR />bru.setEnvVar("access_token",token);<BR />}<BR />```</P><P>- If the request receives a response (status 200)<BR />- Get the access_token value from the response<BR />- Assign to environment variable "access_token".</P><P>Save and run the TOKEN request.<BR />Goto the environment and click 'configure'. You should see the access_token variable updated with the value from the response.</P><P>**Test the API with a request with for a single BP**</P><P>- In Bruno create a new request<BR />- Name: "BP via integration suite"<BR />- URL: "https://{your-trial}.it-cpitrial03-rt.cfapps.ap21.hana.ondemand.com/http/request-business-partners"<BR />- Replace the above with your actual endpoint from Intelligent Suite.<BR />- Navigate to the 'Auth' tab<BR />- Click on 'Inherit' and change to 'Bearer Token'<BR />- In Token enter: `{{access_token}}`<BR />- This eferences an environment variable in Bruno<BR />- Add the request body<BR />- The JSON with our employee ID</P><P>```JSON<BR />{<BR />"employee_id": "1003764"<BR />}<BR />```</P><H2 id="toc-hId--1454690089">Building and testing a frontend</H2><P>At this point a request to SAP Integration Suite should be successfully routed and transformed to the S/4HANA business partner mock server.</P><P>The next part would be building and testing the web app. However, there is too much to cover in building and testing the frontend to cover in this post. I may produce a video on this if anyone is interested.</P><H2 id="toc-hId--1651203594">Final thoughts</H2><P>This technology stack is definitely a bit overkill for a simple 'search' portal, but it is fairly easy to put together as long as you are careful when specifying paths, hosts and port names. </P><P>If you'd like to discuss further please feel free to connect on <A href="https://www.linkedin.com/in/alexanderroan/" target="_self" rel="nofollow noopener noreferrer">LinkedIn - Alexander Roan</A></P>2025-08-13T12:45:21.973000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/ba-p/14200825Give Your AI Agent Some Tools: Introducing the UI5 MCP Server2025-09-03T09:45:00.056000+02:00merlin_beutlbergerhttps://community.sap.com/t5/user/viewprofilepage/user-id/281023<P><SPAN>We are happy to announce the release of the UI5 MCP server – an open-source </SPAN><A href="https://modelcontextprotocol.io/" target="_blank" rel="noopener nofollow noreferrer"><SPAN>Model Context Protocol (MCP)</SPAN></A><SPAN> server that provides AI agents with comprehensive UI5 knowledge. By combining best-practice guidelines, project-aware context information, templates for creating new projects, and access to the rich set of UI5 CLI tools, the UI5 MCP server transforms AI agents into UI5 development experts.</SPAN><SPAN> </SPAN></P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="UI5 MCP Server - wide 1.jpg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/307873i79F209D9B4997D4B/image-size/large?v=v2&px=999" role="button" title="UI5 MCP Server - wide 1.jpg" alt="UI5 MCP Server - wide 1.jpg" /></span></SPAN></P><P><SPAN>In parallel to this release, our colleagues from CAP and SAP Fiori elements have also introduced dedicated MCP servers:</SPAN><SPAN> </SPAN></P><UL><LI><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/boost-your-cap-development-with-ai-introducing-the-mcp-server-for-cap/ba-p/14202849" target="_self">Boost your CAP Development with AI: Introducing the MCP Server for CAP</A></LI><LI><SPAN><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-tools-update-first-release-of-the-sap-fiori-mcp-server-for/ba-p/14204694" target="_blank">SAP Fiori Tools Update – First Release of the SAP Fiori MCP Server for Agentic AI Workflows</A></SPAN></LI></UL><P><SPAN> </SPAN></P><H2 id="toc-hId-1758992204"><SPAN>Why Use the UI5 MCP Server?</SPAN><SPAN> </SPAN></H2><UL><LI><SPAN>Large Language Models (LLMs) sometimes lack information on the latest best practices and APIs recommended for UI5 development.</SPAN><SPAN> </SPAN></LI></UL><UL><LI><SPAN>They might recommend the use of UI5 APIs that do not exist in the version used by your project, or of APIs that have been marked as deprecated.</SPAN><SPAN> </SPAN></LI></UL><UL><LI><SPAN>By facilitating </SPAN><A href="https://github.com/UI5/linter" target="_blank" rel="noopener nofollow noreferrer"><SPAN>UI5 linter</SPAN></A><SPAN>, your coding agent can validate the changes it has made, ensuring they follow current best practices.</SPAN><SPAN> </SPAN></LI><LI><SPAN>Scaffolding can help agents kickstart new projects faster and with established patterns, saving token costs.</SPAN></LI></UL><P><SPAN> </SPAN></P><H2 id="toc-hId-1562478699"><SPAN>Setup</SPAN><SPAN> </SPAN></H2><P><SPAN>Installing the UI5 MCP server is easy. Head over to the project page and follow the instructions for your code editor (your MCP client): </SPAN><A href="https://github.com/UI5/mcp-server#setup" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/UI5/mcp-server#setup</SPAN></A><SPAN>.</SPAN><SPAN> </SPAN></P><P><SPAN>Some IDEs (like VS Code) allow you to add MCP servers directly from npm. In that case, choose this option and provide the package name "@ui5/mcp-server" as shown here:</SPAN><SPAN> </SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FlorianVogt_0-1756799183054.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/307682i6477E254B470C501/image-size/large?v=v2&px=999" role="button" title="FlorianVogt_0-1756799183054.png" alt="FlorianVogt_0-1756799183054.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FlorianVogt_1-1756799183055.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/307683i4DD0693AC0AD9047/image-size/large?v=v2&px=999" role="button" title="FlorianVogt_1-1756799183055.png" alt="FlorianVogt_1-1756799183055.png" /></span></P><P><SPAN>Once installed, the AI agent in your IDE will automatically gain access to the UI5 MCP server and can start using the tools at its own discretion. Depending on your IDE and settings, you may need to approve the first tool calls.</SPAN><SPAN> </SPAN></P><P> </P><H2 id="toc-hId-1365965194"><SPAN>Try It!</SPAN><SPAN> </SPAN></H2><P><SPAN>The UI5 MCP server works well for everyday tasks in UI5 projects. It can help you refactor your code and add new features.</SPAN><SPAN> </SPAN></P><P><STRONG><SPAN>In your UI5 projects, try out some prompts like the following:</SPAN></STRONG><SPAN> </SPAN></P><UL><LI><SPAN>"Explain the difference between the sap.ui.require and sap.ui.define APIs"</SPAN><SPAN> </SPAN></LI></UL><UL><LI><SPAN>"Should I use sap.ui.table.Table or sap.m.List? What is the difference?"</SPAN><SPAN> </SPAN></LI></UL><UL><LI><SPAN>"How can I style an sap.m.Button to appear as a "reject" action?"</SPAN><SPAN> </SPAN></LI></UL><P><STRONG><SPAN>Ask questions about your code:</SPAN></STRONG><SPAN> </SPAN></P><UL><LI><SPAN>"Does this UI5 view apply best practices?"</SPAN><SPAN> </SPAN></LI></UL><UL><LI><SPAN>"Should I update the UI5 version of this project?"</SPAN><SPAN> </SPAN></LI></UL><UL><LI><SPAN>"Help me migrate any deprecated UI5 API usage in this UI5 controller"</SPAN><SPAN> </SPAN></LI></UL><P> </P><H2 id="toc-hId-1169451689"><SPAN>Tools</SPAN><SPAN> </SPAN></H2><P><SPAN>This release of the UI5 MCP server includes the following six tools, which can be accessed by your AI agent:</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId-1102020903"><SPAN>Best Practice Guidance for Your Agent: get_guidelines</SPAN><SPAN> </SPAN></H3><P><SPAN>Provides curated UI5 development guidelines specifically formatted for AI consumption, ensuring that it follows fundamental concepts and that the generated code adheres to current best practices.</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId-905507398"><SPAN>Project Scaffolding: create_ui5_app</SPAN><SPAN> </SPAN></H3><P><SPAN>Creates a foundation to start new UI5 projects. The tool comes with templates for both JavaScript and TypeScript UI5 applications. They can be generated as standalone apps or integrated into an existing CAP application. </SPAN><SPAN> </SPAN></P><P><SPAN>The templates feature pre-configured OData model setups and follow current best practices.</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId-708993893"><SPAN>Search the UI5 API Reference: get_api_reference</SPAN><SPAN> </SPAN></H3><P><SPAN>Allows the AI agent to search the </SPAN><A href="https://ui5.sap.com/#/api" target="_blank" rel="noopener noreferrer"><SPAN>UI5 API reference</SPAN></A><SPAN> and learn about API descriptions, signatures, and deprecation information. You may have encountered AI agents hallucinating about UI5 APIs that do not exist. This tool will effectively overcome such problems.</SPAN><SPAN> </SPAN></P><P><SPAN>The UI5 MCP server ensures that the version of the API reference matches with the UI5 version configured in your project, so you always get relevant information.</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId-512480388"><SPAN>Let the Agent Fix Your Code: run_ui5_linter</SPAN><SPAN> </SPAN></H3><P><SPAN>This tool exposes functionality of </SPAN><A href="https://github.com/UI5/linter" target="_blank" rel="noopener nofollow noreferrer"><SPAN>UI5 linter</SPAN></A><SPAN>, allowing the AI agent to scan a UI5 project for problems such as the use of deprecated UI5 APIs, and to apply any </SPAN><A href="https://github.com/UI5/linter?tab=readme-ov-file#--fix" target="_blank" rel="noopener nofollow noreferrer"><SPAN>fixes provided by UI5 linter</SPAN></A><SPAN>.</SPAN><SPAN> </SPAN></P><P><SPAN>Beyond these basic linting capabilities, the tool also provides rich contextual information, including rule descriptions, extracts of relevant API references, and even entire guides for the manual migration of certain deprecated APIs that can’t be fixed automatically by UI5 linter. These guides offer step-by-step instructions that guide the agent through complex refactoring, for example when replacing a deprecated, synchronous UI5 API with its modern, asynchronous successor.</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId-315966883"><SPAN>Project Insights: get_project_info</SPAN><SPAN> </SPAN></H3><P><SPAN>Provides selected metadata for your UI5 project. This includes the relevant configuration, the framework version and libraries used, and current information regarding the version’s support status. All this allows the AI agents to quicky gain important insights about the project.</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId-119453378"><SPAN>UI5 Version Overview: get_version_info</SPAN><SPAN> </SPAN></H3><P><SPAN>Delivers real-time information about UI5 framework versions, including the support status and upgrade paths.</SPAN><SPAN> </SPAN></P><P> </P><H3 id="toc-hId--152291496"><FONT color="#FF6600"><STRONG>2026 Update: New Tools for UI Integration Cards</STRONG></FONT></H3><P data-unlink="true"><SPAN>In its latest version, the UI5 MCP server now also supports the development of <A href="https://ui5.sap.com/test-resources/sap/ui/integration/demokit/cardExplorer/index.html" target="_self" rel="noopener noreferrer">UI Integration Cards</A>. The new tools include:</SPAN></P><UL><LI><SPAN><STRONG>get_integration_cards_guidelines:</STRONG> Provides dedicated guidelines for the development of UI Integration Cards</SPAN></LI><LI><STRONG>create_integration_card: </STRONG>Creates new UI Integration Cards based on parameters</LI><LI><SPAN><STRONG>run_manifest_validation:</STRONG> Validates the manifest.json, not only for UI Integration Cards, but also of regular UI5 applications and libraries!</SPAN></LI></UL><P><SPAN>For more information on the new tools, see the blog post: <A class="" href="https://community.sap.com/t5/technology-blog-posts-by-sap/ui5-mcp-server-got-extended-for-ui-integration-cards-generation/ba-p/14312670" target="_blank">UI5 MCP Server Got Extended for UI Integration Cards Generation</A></SPAN></P><H2 id="toc-hId--55401994"><SPAN>Dive Deeper</SPAN><SPAN> </SPAN></H2><P><SPAN>If you’re interested in learning more about the inner workings of the UI5 MCP server, check out the code at </SPAN><A href="https://github.com/UI5/mcp-server" target="_blank" rel="noopener nofollow noreferrer"><SPAN>github.com/UI5/mcp-server</SPAN></A><SPAN> and read the </SPAN><A href="https://github.com/UI5/mcp-server/blob/main/docs/architecture.md" target="_blank" rel="noopener nofollow noreferrer"><SPAN>architecture document</SPAN></A><SPAN>.</SPAN><SPAN> </SPAN></P><P><SPAN>Don’t hesitate to reach out if you should run into any trouble using this new tool. Simply create an </SPAN><A href="https://github.com/UI5/mcp-server/issues/new/choose" target="_blank" rel="noopener nofollow noreferrer"><SPAN>issue on GitHub</SPAN></A><SPAN>, and we’ll be happy to help. You can also propose new features through this method.</SPAN><SPAN> </SPAN></P><P><SPAN>As always, we’re looking forward to your feedback!</SPAN><SPAN> </SPAN></P>2025-09-03T09:45:00.056000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-adding-field-validation-based-on-other-field-values-using/ba-p/14259241SAP Fiori: Adding Field Validation Based on Other Field Values Using Controller Extensions OData V22025-11-03T14:13:25.458000+01:00Sahil2508https://community.sap.com/t5/user/viewprofilepage/user-id/1527875<P><SPAN>In many project use cases, we often encounter situations where </SPAN><STRONG>the validity of one field depends on the value of another</STRONG><SPAN>. For example, when a flag is updated or a key date is modified, the user must provide additional comments before saving the record.</SPAN></P><P><SPAN><BR /></SPAN>In this blog post, I’ll walk you through how I implemented such <STRONG>conditional validations</STRONG> in a Fiori Elements Object Page using <STRONG>Controller Extensions</STRONG>.<BR /><BR /></P><H3 id="toc-hId-1892954939"><STRONG>Scenario</STRONG></H3><P>I had a requirement where:</P><OL><LI><P>If the <STRONG>Last Working Date (LWD)</STRONG> was changed, then the <STRONG>Additional Comments (ARD Comments)</STRONG> field must not be empty.</P></LI><LI><P>If certain flags or dropdown values were set to specific conditions (like “Y” or a specific code), corresponding comment fields were required before allowing the user to save.</P></LI></OL><P>Instead of modifying the generated metadata or annotations, I handled these validations using the <STRONG>Controller Extension’s <CODE>beforeSaveExtension</CODE> hook</STRONG>, which is triggered before the save action.<BR /><BR /></P><H3 id="toc-hId-1696441434"><SPAN>Implementation</SPAN></H3><P><SPAN>1. Adding controller extension :- Add the controller extension file in app/webapp/ext/controller as ObjectPageExt.controller.js</SPAN></P><P><SPAN>2. Add the controller file in manifest.json</SPAN></P><pre class="lia-code-sample language-json"><code>"sap.ui5": {
"flexEnabled": true,
// other fields
"resources": {
"css": []
},
"routing": {
"config": {},
"routes": [],
"targets": {}
},
"extends": {
"extensions": {
"sap.ui.controllerExtensions": {
"sap.suite.ui.generic.template.ObjectPage.view.Details": {
"controllerName": "app.ext.controller.ObjectPageExt"
}
}
}
}
},</code></pre><P>3. <CODE>ObjectPageExt.controller.js</CODE></P><pre class="lia-code-sample language-json"><code>sap.ui.define(["sap/ui/core/mvc/ControllerExtension", "sap/m/MessageBox"], function (ControllerExtension, MessageBox) {
"use strict";
return ControllerExtension.extend("offboardingdashboard.ext.controller.ObjectPageExt", {
override: {
beforeSaveExtension: function () {
const oContext = this.getView().getBindingContext();
if (!oContext) return Promise.resolve();
const oModel = oContext.getModel();
const sPath = oContext.getPath();
const oNewData = oContext.getObject(); // Current draft data
// --- Step 1: Get the original (active) entity data ---
const oOriginalData = oModel.getOriginalProperty
? oModel.getOriginalProperty(sPath)
: null;
// --- Step 2: Detect change in Last Working Date ---
let bLwdChanged = false;
if (oOriginalData && oOriginalData.cust_lastworkingdate && oNewData.cust_lastworkingdate) {
const dNew = new Date(oNewData.cust_lastworkingdate);
const dOld = new Date(oOriginalData.cust_lastworkingdate);
bLwdChanged = dNew.getTime() !== dOld.getTime();
} else if (oOriginalData && oOriginalData.cust_lastworkingdate !== oNewData.cust_lastworkingdate) {
bLwdChanged = true;
}
// --- Step 3: Apply validation rules ---
const aMessages = [];
if (oNewData.cust_rehire === "Y" && (!oNewData.cust_rehirecomments || oNewData.cust_rehirecomments.trim() === "")) {
aMessages.push("• Rehire comments are required when rehire flag is set to 'Yes'.");
}
if (bLwdChanged && (!oNewData.cust_ardcomments || oNewData.cust_ardcomments.trim() === "")) {
aMessages.push("• ARD comments are required when Last Working Date has been changed.");
}
// --- Step 4: Show error message and prevent save ---
return new Promise(function (fnResolve, fnReject) {
if (aMessages.length > 0) {
const sMessage = "Please address the following issues before saving:\n\n" + aMessages.join("\n");
MessageBox.error(sMessage, {
actions: [MessageBox.Action.OK],
onClose: fnReject
});
} else {
fnResolve();
}
});
}
}
});
});</code></pre><H3 id="toc-hId-1499927929"><STRONG>How It Works</STRONG></H3><OL><LI><P>The <FONT color="#000000"><CODE>beforeSaveExtension</CODE></FONT> method is triggered automatically before the <STRONG>Save</STRONG> action on the Object Page.</P></LI><LI><P>We first read the <STRONG>current draft data</STRONG> and the <STRONG>original active entity</STRONG>.</P></LI><LI><P>We compare fields like <CODE>cust_lastworkingdate</CODE> to detect changes.</P></LI><LI><P>Based on certain conditions (like flag = ‘Y’ ), we push validation messages into an array.</P></LI><LI><P>If there are validation errors, a <STRONG>MessageBox</STRONG> appears with all issues listed, and the save action is <STRONG>prevented</STRONG>.<BR /><BR /></P></LI></OL><H3 id="toc-hId-1303414424">UI Screenshots</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Sahil2508_0-1762170076532.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/335346iB9CDF5D193F95B37/image-size/medium?v=v2&px=400" role="button" title="Sahil2508_0-1762170076532.png" alt="Sahil2508_0-1762170076532.png" /></span> <BR /><BR />When both the conditions are met :<BR />-> last working day is changed and rehire flag is set to 'Y'</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Sahil2508_1-1762170282958.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/335347i4CE007290692BAD5/image-size/medium?v=v2&px=400" role="button" title="Sahil2508_1-1762170282958.png" alt="Sahil2508_1-1762170282958.png" /></span></P><P>-> only last working day is changed<BR /><BR /></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Sahil2508_2-1762175135420.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/335386i2DD743A98D7E1C6A/image-size/medium?v=v2&px=400" role="button" title="Sahil2508_2-1762175135420.png" alt="Sahil2508_2-1762175135420.png" /></span></P><P>-> only rehire flag is set to 'Y'<BR /><BR /></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Sahil2508_4-1762175308970.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/335390i3D71C81A5905338C/image-size/medium?v=v2&px=400" role="button" title="Sahil2508_4-1762175308970.png" alt="Sahil2508_4-1762175308970.png" /></span></P><P> </P><H3 id="toc-hId-1106900919"><STRONG>Key Takeaways</STRONG></H3><UL><LI><P>Always perform <STRONG>value-dependent validations</STRONG> in the <CODE>beforeSaveExtension</CODE> hook for extensibility.</P></LI><LI><P>Use <CODE>getOriginalProperty</CODE> to safely compare values between draft and active entities.</P></LI><LI><P>Consolidate all validation messages for a <STRONG>better user experience</STRONG> rather than showing multiple alerts.</P></LI></UL>2025-11-03T14:13:25.458000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/how-to-reduce-prompt-token-costs-using-toon-save-money/ba-p/14267265How to Reduce Prompt Token Costs Using Toon = Save Money2025-11-12T17:34:51.069000+01:00Yoganandahttps://community.sap.com/t5/user/viewprofilepage/user-id/75<P>As you already might know prompt tokens are the backbone of communication with large language models (LLMs). However, as usage scales, token costs can quickly become a significant expense. If you’re using <STRONG>Toon</STRONG>—a tool designed for optimizing prompt workflows—you can dramatically cut down on these costs without sacrificing performance.</P><H3 id="toc-hId-1893818944"><STRONG>Why Token Costs Matter</STRONG></H3><P>Every interaction with an LLM consumes tokens. These tokens represent:</P><UL><LI><STRONG>Input tokens</STRONG>: The text you send to the model.</LI><LI><STRONG>Output tokens</STRONG>: The text generated by the model.</LI></UL><P>The more tokens you use, the higher your bill. For businesses running thousands of prompts daily, even small inefficiencies can lead to big costs.</P><P><FONT color="#FF00FF"><STRONG>Example:</STRONG></FONT></P><UL><LI>Original prompt: <EM>“Please summarize the following text in a clear and concise manner, highlighting the key points and provide a RACI Matrix for S/4 HANA”</EM></LI><LI>Compressed prompt: <EM>“Summarize key points.”</EM></LI></UL><H3 id="toc-hId-1697305439"><FONT color="#800080"><STRONG>What is TOON?</STRONG></FONT></H3><P>TOON is a <STRONG>compact, human-readable serialization format</STRONG> designed for passing structured data to LLMs with <STRONG>significantly reduced token usage</STRONG>. It acts as a <STRONG>lossless, drop-in representation of JSON</STRONG>, optimized for token efficiency.</P><H3 id="toc-hId-1500791934"><FONT color="#800080"><STRONG>Why Use TOON?</STRONG></FONT></H3><UL><LI><STRONG>Token-efficient</STRONG>: Saves <STRONG>30–60% tokens</STRONG> compared to formatted JSON for large uniform arrays.</LI><LI><STRONG>LLM-friendly</STRONG>: Explicit lengths and fields improve parsing and validation.</LI><LI><STRONG>Minimal syntax</STRONG>: Removes redundant punctuation (braces, quotes).</LI><LI><STRONG>Tabular arrays</STRONG>: Declare keys once, stream data as rows.</LI><LI><STRONG>Optional key folding</STRONG>: Collapses nested chains into dotted paths for fewer tokens.</LI></UL><H3 id="toc-hId-1304278429"><STRONG>Benchmarks</STRONG></H3><UL><LI>TOON uses <STRONG>39.6% fewer tokens</STRONG> than JSON while improving retrieval accuracy (73.9% vs 69.7%).</LI><LI>For uniform tabular data, TOON is slightly larger than CSV (+6%) but far smaller than JSON (-58%).</LI></UL><H3 id="toc-hId-1107764924"><STRONG>When NOT to Use TOON</STRONG></H3><UL><LI>Deeply nested or non-uniform structures → JSON may be better.</LI><LI>Pure tabular data → CSV is smaller.</LI><LI>Latency-critical apps → Benchmark first; compact JSON might be faster.</LI></UL><P><STRONG>How to Use TOON</STRONG></P><pre class="lia-code-sample language-abap"><code>NPM Lib
npm install -format/toon
Python
https://github.com/xaviviro/python-toon</code></pre><P> <A href="https://github.com/toon-format/toon" target="_blank" rel="noopener nofollow noreferrer">https://github.com/toon-format/toon</A> </P><P><A href="https://github.com/toon-format/spec" target="_blank" rel="noopener nofollow noreferrer">https://github.com/toon-format/spec</A> </P><H3 id="toc-hId-911251419"><STRONG>Why TOON Matters while your working for different SAP Product LoBs</STRONG></H3><OL><LI><P><STRONG>Token Cost Reduction</STRONG></P><UL><LI>SAP S/4 HANA systems handle large datasets (e.g., thousands of line items in a purchase order).</LI><LI>JSON representation of these datasets is expensive in token terms.</LI><LI>TOON compresses this data by <STRONG>30–60%</STRONG>, reducing LLM API costs significantly.</LI></UL></LI><LI><P><STRONG>Performance Gains</STRONG></P><UL><LI>Smaller prompts mean <STRONG>lower latency</STRONG> and <STRONG>faster response times</STRONG>.</LI><LI>This is critical for real-time SAP applications like <STRONG>Joule for procurement</STRONG> or <STRONG>HR assistants</STRONG>.</LI></UL></LI><LI><P><STRONG>Improved Accuracy</STRONG></P><UL><LI>TOON’s explicit structure (e.g., tabular arrays with declared keys) helps LLMs parse data better.</LI><LI>This reduces hallucinations in SAP workflows like <STRONG>financial reconciliation</STRONG> or <STRONG>compliance checks</STRONG>.</LI></UL></LI></OL><P><STRONG>High Level Flow</STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Yogananda_0-1762963441825.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/339677iCF14D8DA461534BD/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Yogananda_0-1762963441825.png" alt="Yogananda_0-1762963441825.png" /></span></P><H2 id="toc-hId-585655195"><STRONG>Integration Approach</STRONG></H2><UL><LI><STRONG>Middleware Layer</STRONG>: Convert SAP OData/JSON responses to TOON before sending to LLM.</LI><LI><STRONG>SAP BTP Extension</STRONG>: Implement TOON conversion in <STRONG>CAP, Cloud Foundry apps</STRONG> or <STRONG>Kyma runtime or SAP Databricks, AI Core Models you have selected</STRONG>.</LI><LI><STRONG>Prompt Wrapping</STRONG>: Always wrap TOON in fenced code blocks for LLM clarity</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="G5qMVMlacAAosFZ.png" style="width: 800px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/340395iDB351B41F1A46C83/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="G5qMVMlacAAosFZ.png" alt="G5qMVMlacAAosFZ.png" /></span></P><H2 id="toc-hId-389141690"><STRONG>Best Practices</STRONG></H2><UL><LI>Use <STRONG>tab-delimited rows</STRONG> for extra token savings.</LI><LI>Benchmark TOON vs JSON for your specific SAP dataset.</LI><LI>Cache static context to avoid repeated token costs.</LI></UL><H2 id="toc-hId-192628185"><STRONG>Conclusion</STRONG></H2><P>TOON offers a simple yet powerful way to reduce LLM costs in your SAP landscape environments. By compressing structured data without losing meaning, SAP teams can achieve:</P><UL><LI><STRONG>Up to 60% cost savings</STRONG></LI><LI><STRONG>Faster response times</STRONG></LI><LI><STRONG>Improved AI accuracy</STRONG></LI></UL><P>As SAP continues its AI journey, adopting TOON can make intelligent automation more scalable and cost-effective.</P>2025-11-12T17:34:51.069000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/understanding-regex-in-javascript-a-beginner-friendly-guide/ba-p/14279708Understanding Regex in JavaScript - A Beginner-Friendly Guide2025-11-28T11:44:51.979000+01:00Myvizhipriya_Thangaraj_2810https://community.sap.com/t5/user/viewprofilepage/user-id/1477011<H3 id="toc-hId-1894806669">Quick Introduction:</H3><P>When working with JavaScript, we will often find ourselves needing to search, validate or modify text. That’s where <STRONG>Regex</STRONG> comes in.</P><P>In this blog, we will break down what regex is, why it’s useful, and how to start using it with simple examples.</P><H3 id="toc-hId-1698293164"> </H3><H3 id="toc-hId-1501779659"><STRONG>What is Regex?</STRONG></H3><P>Regex is a <STRONG>powerful</STRONG> & <STRONG>smart search tool</STRONG> that lets you find patterns in text almost like giving your code a mini detective to look for clues inside a string.</P><P><STRONG>Regex</STRONG> (short for <STRONG>Regular Expression</STRONG>) is a special pattern used to <STRONG>search</STRONG>, <STRONG>match</STRONG> and <STRONG>replace</STRONG> text.</P><P>In other words, regex is a <STRONG>pattern-matching language</STRONG> used in many programming languages, including JavaScript to:</P><UL><LI><STRONG>Search</STRONG> for text</LI><LI><STRONG>Match</STRONG> specific patterns</LI><LI><STRONG>Replace</STRONG> parts of a string</LI></UL><P>For example, we might need to check if a sentence contains a word, extract all numbers from a paragraph or validate an email address. Regex makes these tasks <STRONG>fast, efficient and reliable</STRONG>.</P><P> </P><H3 id="toc-hId-1305266154"><STRONG>Why do use Regex in JavaScript?</STRONG></H3><P>Because normal search is limited. With regex, we can do so much. Regex lets you write dynamic, flexible patterns to handle more advanced text operations. Here’s what you can do with it:</P><OL><LI><STRONG>Validate patterns</STRONG><P>Modify text based on matching patterns.<BR /><EM><STRONG>Example</STRONG>: Remove extra spaces or replace characters.</EM></P></LI><LI><P><STRONG>Extraction Text</STRONG></P><P>Check if a string follows a specific format.<BR /><EM><STRONG>Example</STRONG>: Is this a valid email address?</EM></P></LI><LI><P><STRONG>Replace text</STRONG></P><P><EM>Pull out numbers, words or custom text patterns.<BR /><STRONG>Example</STRONG>: Find all digits in a message.</EM></P></LI></OL><P> </P><H3 id="toc-hId-1108752649"><STRONG>Simple and Practical Examples</STRONG></H3><P>Let’s look at how we can use regex in JavaScript with just a few lines of code.</P><UL><LI><STRONG><STRONG>Check if a word exists</STRONG></STRONG></LI></UL><pre class="lia-code-sample language-javascript"><code>const sText = "I love JavaScript";
const bResult = /JavaScript/.test(text);
console.log(bResult); // true</code></pre><UL><LI><STRONG><STRONG>Find all numbers in a string</STRONG></STRONG></LI></UL><pre class="lia-code-sample language-javascript"><code>const sText = "My score is 95 out of 100.";
const sNumbers = sText.match(/\d+/g);
console.log(sNumbers); // ["95", "100"]</code></pre><UL><LI><STRONG><STRONG>Replace spaces with dashes</STRONG></STRONG></LI></UL><pre class="lia-code-sample language-javascript"><code>const sResult = "hello world js".replace(/\s/g, "-");
console.log(sResult); // "hello-world-js"</code></pre><P> </P><H3 id="toc-hId-912239144"><STRONG>How regex patterns are structured & how it works</STRONG></H3><P>Regex patterns in JavaScript are written between <STRONG>forward slashes</STRONG>:</P><P>/pattern/</P><P>Here are some common symbols:</P><TABLE width="381px"><TBODY><TR><TD width="69.4844px"><P><STRONG>Symbol</STRONG></P></TD><TD width="310.516px"><P><STRONG>Meaning</STRONG></P></TD></TR><TR><TD width="69.4844px"><P>\d</P></TD><TD width="310.516px"><P>digit (0–9)</P></TD></TR><TR><TD width="69.4844px"><P>\s</P></TD><TD width="310.516px"><P>whitespace (space, tab, etc.)</P></TD></TR><TR><TD width="69.4844px"><P>\w</P></TD><TD width="310.516px"><P>word character (letters, numbers, _)</P></TD></TR><TR><TD width="69.4844px"><P>+</P></TD><TD width="310.516px"><P>one or more</P></TD></TR><TR><TD width="69.4844px"><P>*</P></TD><TD width="310.516px"><P>zero or more</P></TD></TR></TBODY></TABLE><P>Based on our requirement, we can combine these symbols to create almost any pattern we need.</P><P><STRONG>In simple words, Regex is a smart text-searching tool in JavaScript. </STRONG>It helps us work with text faster, cleaner, and more efficiently, especially when dealing with patterns. Whether you're searching, matching or validating text, regex makes the process powerful, flexible and easy.</P><P>Thank you for taking the time to read this blog! I hope this helps you. If you found this helpful, please feel free to share your thoughts, feedback or questions in the comments. Let's keep learning and growing together!</P><P>Stay tuned for more in-depth knowledge on regex tool in future blogs. Happy coding!</P><P>Dear experts, I’m new to blogging, please feel free to correct me if any information is inaccurate.</P>2025-11-28T11:44:51.979000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/harnessing-reporting-potential-by-thinking-outside-of-the-box-part-1-3-sac/ba-p/14277884Harnessing Reporting Potential, by Thinking outside of the Box – Part 1/3 SAC Jump & Run Blog series2025-12-04T10:44:32.333000+01:00Adrian_Khttps://community.sap.com/t5/user/viewprofilepage/user-id/2090638<H1 id="toc-hId-1636582854">Harnessing Reporting Potential, by Thinking outside of the Box - <SPAN>Part 1/3 SAC Jump & Run Blog series</SPAN></H1><H3 id="toc-hId-1698234787">Solving Reporting needs in SAP Analytics Cloud by passing limitations with workaround</H3><P>After years of experience within the area of SAC reporting, I cannot count how often I heard colleagues, business partners, or customers complain about SAC limitations which eventually hinder the reporting potential. Like every software, the limitations in SAC stem from focus in a certain area which will result in the underdevelopment of another area. While this statement is partly true, it has shown that almost every obstacle can be overcome by thinking outside the box. Instead of being frustrated about the straight path which is blocked, users find creative tricks and workarounds and share them with the community. To prove my point, I have created a Jump & Run demo in SAC! Although such games are not part of the tool’s scope, the implementation was still easy, if one is willing to make some compromises.</P><H2 id="toc-hId-1372638563">Part 1 – the Game</H2><P>Let me introduce you to my demo, which I will use to explain how I was able to use the potential of SAC to my advantage and discuss certain features which helped me to do so:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Figure 1: Overview SAC Jump & Run" style="width: 945px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/345304i028170DAB42A0D5E/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Adrian_K_6-1764147405938.png" alt="Figure 1: Overview SAC Jump & Run" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Figure 1: Overview SAC Jump & Run</span></span></P><P>The demo I have created is a playable, though not very complex, jump & run game. It allows the player to control their character (1) by using buttons (2). These buttons trigger the character to automatically run in two different directions, jump, or stay still.<BR />Throughout the game, a moving enemy (3) creates constant tension, as it can cost the player valuable lives (4). The player is expected to use platforms in order to collect coins (5). Once all coins are collected, the level is completed by reaching the portal (6).</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Figure 2: Jump & Run Gameplay" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/345307i44EF334C4ABD4E30/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="image003.gif" alt="Figure 2: Jump & Run Gameplay" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Figure 2: Jump & Run Gameplay</span></span></P><P>The most important features, which were not only helpful in creating this game, but also in fulfilling reporting requirements, are the following:</P><UL><LI>Panels</LI><LI>Scripting, especially Events & Timer</LI><LI>Images & Animations</LI></UL><P>In this blog series, I will discuss all mentioned features and explain why all of them are powerful tools for responsive reporting and, additionally, helpful when creating a jump & run game. The first blog will focus on Panels.</P><H2 id="toc-hId-1176125058">Part 2 - Panels & the responsive (play-)world</H2><H5 id="toc-hId-1366859710"> Why “everything” should be in a panel</H5><H3 id="toc-hId-912180767">What are panels?</H3><P>Panels are containers used to organize and group objects by placing them inside. What makes them such a powerful tool for responsive dashboards is the relative positioning and sizing of these objects. This means that objects are set in a proportional ratio to each other and the panel, which makes the whole group automatically responsive.</P><P>Dashboards are often supposed to work on various devices and screen sizes, such as conference room screens, desktops, tablets, or phones. Responsive stories in combination with nested panels are a crucial factor for achieving the desired adaptability. Hence, they should provide the whole structure of the dashboard. As mentioned before, every part can be put in relation to each other, except the highest layer of panels. They provide the overall structure and require a fixed number of columns or rows in the grid system. In responsive stories this step can be handled rather easily, as the lanes of a story can be configured based on the end-device. The SAC user interface allows to adjust how much of the grid every lane can use. Another useful feature of responsive stories is the automatic transformation of components, such as charts and filters. They will show up in their mobile form, when the dashboard is opened on a mobile device. Even outside of responsive stories, panels are a powerful tool for structuring and grouping.</P><P>Note: Although a panel’s official unit weight is 0.1 according to “Best Practices for Performance Optimization in Story Design”, they do not impact story performance.</P><H3 id="toc-hId-715667262">How Panels were used in the Jump & Run</H3><P>Before diving into the application of panels in the classic reporting sense, I will explain how they were used in the Jump & Run. Note that the game was only developed for desktop and has not yet been adapted for mobile devices.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Figure 3: Simplyfied panel structure in the SAC Jump & Run" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/345323i759B9D63B2593172/image-size/large?v=v2&px=999" role="button" title="Panel.png" alt="Figure 3: Simplyfied panel structure in the SAC Jump & Run" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Figure 3: Simplyfied panel structure in the SAC Jump & Run</span></span></P><P>Applying the logic described above, the whole content of the game is nested in one panel. This “outer panel” (Panel_Game) is only used to provide structure and does not directly contain any visible elements. Within this panel are three other panels, referred to as “inner panels” (Panel_PlayerInformation, Panel_Playfield, Panel_Ground) which provide structure for the game and include visible content. One panel represents the header, containing information about the player status and the controls. The middle panel includes the playable world in which the player can move, and one panel is used for the ground. All inner panels are responsive and adapt to changes in the outer panel.</P><P>Inside these panels are panels which are supposed to fulfil a specific task. As shown by the fields outlined in red, one panel provides images of the collected coins and remaining lives, while the other includes the control panel which allows to move the player. Similar to the other panels, these are responsive as well and only require rework when the screen size falls below a certain value. In addition, it makes rearranging specific parts, such as the controls, fairly easy. As all necessary parts are grouped within the panel, they can be rearranged with one "click" without altering the proportions.</P><H3 id="toc-hId-519153757">Panels in responsive Reporting</H3><P>Checking the latest updates on the phone while waiting for the next flight to catch, diving into details from the desktop, or discussing current trends with others by accessing the data on a tablet. These and many others are typical examples of use from dashboards. By using responsive stories, lanes, and panels, modern dashboards can be created which can cover these applications.</P><P>Achieving this level of responsiveness can be challenging and requires creative conceptualisation. One key component is what I call “Tiles” in this blog. They are a specific way of using panels to structure responsive layouts. While they can differ in size, form, and nested objects, they all follow a common structure. This allows them to be replaces easily with other tiles.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Figure 4: Structure Tile" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/345324i264AC468672BCA6B/image-size/medium?v=v2&px=400" role="button" title="Tile.png" alt="Figure 4: Structure Tile" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Figure 4: Structure Tile</span></span> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Figure 5: Example Tile (Data from BestRunJuice Sample)" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/345312i0C452B1C8BD18512/image-size/medium?v=v2&px=400" role="button" title="image007.png" alt="Figure 5: Example Tile (Data from BestRunJuice Sample)" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Figure 5: Example Tile (Data from BestRunJuice Sample)</span></span></P><P>The basic structure was already looked at in the paragraph above, however, I will now shed light on how to structure them for effective reporting. As known already, an invisible outer panel provides the overall positioning and sizing in the grid and lanes. The inner panel adds a visible structure, such as background colour and borders. Nested in there are panels which contain specific elements. In the example provided, I used a title/text, created an area for numeric charts, which again is split in two separate parts with their own subpanels, and left space for a big chart with visualisations. Using this approach results in a fully responsive tile which could look similar to the last figure I provided (Figure 5). It is important to note that all panel and chart positions and size properties are expressed as a percentage of their parent elements, except for the font-size of the title.</P><H2 id="toc-hId-193557533">Outlook</H2><P>In the next part of this blog, I will discuss the scripting behind the game, the utilization of global variables, functions, events and especially timer to make the panels and images playable.</P>2025-12-04T10:44:32.333000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/building-secure-sapui5-applications-best-practices-for-developers/ba-p/14267269Building Secure SAPUI5 Applications: Best Practices for Developers2025-12-10T08:16:09.335000+01:00Manisha_19https://community.sap.com/t5/user/viewprofilepage/user-id/1695623<H3 id="toc-hId-1893818948"><STRONG>Introduction</STRONG></H3><P>In today’s enterprise landscape, security is fundamental, not optional. As the user-facing gateway to SAP systems, SAPUI5 apps must be built with security in mind from the start.</P><P>This blog shares practical, developer-focused techniques to help you secure your SAPUI5 applications from input validation and Cross-Site Scripting (XSS) prevention to using ESLint and safe communication practices. By adopting secure coding habits early, you can ensure your UI remains clean, efficient, and resilient against modern threats.</P><H3 id="toc-hId-1697305443"><STRONG>Why Security Matters in SAPUI5</STRONG></H3><P>The UI layer is where users interact, input data, and access sensitive information.<BR />Even a small oversight like an unsanitized binding or debug log can lead to security exposure or data misuse.</P><P>By following secure UI5 coding standards, developers can:</P><UL><LI>Maintain data integrity and confidentiality</LI><LI>Prevent unauthorized access</LI><LI>Build user trust through reliable and safe design</LI></UL><H3 id="toc-hId-1500791938"><STRONG>Secure Coding Practices for SAPUI5 Developers</STRONG></H3><P><STRONG>1. Validate and Sanitize User Input</STRONG></P><P>Always validate what users enter before sending it to your backend.</P><UL><LI>Use data types in input fields for built-in validation:</LI></UL><pre class="lia-code-sample language-markup"><code><Input value="{path: 'age', type: 'sap.ui.model.type.Integer'}" /></code></pre><UL><LI>Add custom formatters or validators for domain-specific logic.</LI><LI>Reject invalid inputs early, don’t rely on the backend to catch them.</LI></UL><P><STRONG> 2. </STRONG><STRONG>Prevent Cross-Site Scripting (XSS)</STRONG></P><P>XSS attacks occur when untrusted input is rendered as executable code in the browser.<BR />To prevent it in UI5:</P><UL><LI>Use <STRONG>Text</STRONG> control instead of <STRONG>FormattedText</STRONG> unless the content is sanitized.</LI><LI>Escape dynamic strings using encodeHTML().</LI><LI>Avoid direct DOM manipulation like innerHTML or jQuery .html().</LI><LI>Never include untrusted HTML in views or models.</LI></UL><P><STRONG> 3. </STRONG><STRONG>Protect Sensitive Information</STRONG></P><UL><LI>Do <STRONG>not</STRONG> store tokens, passwords, or system URLs in <STRONG>localStorage</STRONG> or <STRONG>sessionStorage</STRONG>.</LI><LI>Avoid exposing business logic or identifiers in console logs.</LI><LI>Remove debug logs before deployment as they often reveal internal system details.</LI></UL><P><STRONG> 4. </STRONG><STRONG>Use ESLint for Secure and Consistent Code </STRONG></P><P>Static code analysis helps detect vulnerabilities and enforces coding standards.</P><P>Install and configure ESLint with SAP’s recommended rules:</P><pre class="lia-code-sample language-bash"><code>npm install eslint eslint-config-ui5 --save-dev</code></pre><P>Create a .eslintrc.json file:</P><pre class="lia-code-sample language-json"><code>{
"extends": ["eslint-config-ui5"]
}</code></pre><P><STRONG>Benefits:</STRONG></P><UL><LI>Detects unsafe DOM access or unused imports</LI><LI>Prevents insecure practices like eval statements</LI><LI>Promotes consistent, readable, and maintainable code</LI></UL><P>Run ESLint regularly as part of your build or CI pipeline:</P><pre class="lia-code-sample language-bash"><code>eslint .</code></pre><P><STRONG> 5. </STRONG><STRONG>Secure Communication with Backend</STRONG></P><P>Even though backend services handle authentication, the UI must communicate securely.</P><UL><LI>Always use <STRONG>HTTPS</STRONG> for service endpoints.</LI><LI>Avoid hardcoding URLs use destination configurations or environment variables.</LI><LI>Enable <STRONG>Cross-Site Request Forgery</STRONG> <STRONG>(CSRF) protection</STRONG> in your ODataModel configuration:</LI></UL><pre class="lia-code-sample language-javascript"><code>let oModel = new sap.ui.model.odata.v2.ODataModel("/odata/service", {
tokenHandling: true
});</code></pre><H3 id="toc-hId-1304278433"><STRONG>Developer Mindset: Security as a Habit</STRONG></H3><P>Security isn’t a one-time setup, it’s an ongoing practice.<BR />Adopt a security-first mindset by:</P><UL><LI>Reviewing code with security in mind</LI><LI>Regularly updating libraries and dependencies</LI><LI>Integrating linting and vulnerability scans in CI/CD</LI></UL><P>When security becomes part of your coding routine, your applications naturally grow more resilient and trustworthy.</P><H3 id="toc-hId-1107764928"><STRONG>Conclusion</STRONG></H3><P>Building secure SAPUI5 applications isn’t just about backend policies, it starts right in your controllers, bindings, and XML views.<BR />By validating inputs, preventing XSS, protecting sensitive data, and using tools like ESLint, you strengthen your application’s defense without compromising usability or performance.</P>2025-12-10T08:16:09.335000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/extending-an-sap-sample-sankey-custom-widget-in-sap-analytics-cloud-to/ba-p/14295743Extending an SAP Sample Sankey Custom Widget in SAP Analytics Cloud to Deliver Top-N Hierarchical In2025-12-23T08:37:05.630000+01:00GovindaRaoBanothu45https://community.sap.com/t5/user/viewprofilepage/user-id/828979<P><STRONG>Introduction</STRONG></P><P>SAP Analytics Cloud (SAC) does not provide a native, built-in Sankey chart as part of its standard visualization catalog. As a result, Sankey-style visualizations in SAC are typically implemented using custom widgets.</P><P>In mid-2024, while working on an enterprise analytics engagement, I encountered a requirement to visualize:</P><UL><LI>Top N parent entities (for example, service providers)</LI><LI>And, for each parent, the Top M child entities independently ranked per parent (for example, services offered)</LI></UL><P>All of this needed to be displayed within a single Sankey visualization, enabling users to immediately understand concentration and distribution patterns without navigating multiple charts.</P><P>To address this requirement, I explored SAC’s Custom Widget framework and extended an existing SAP-provided sample Sankey custom widget into a production-ready solution.</P><P>I have intentionally included the development and deployment steps in detail so that consultants or developers who are completely new to Sankey custom widgets in SAP Analytics Cloud can deploy and validate the solution end-to-end by following this blog.</P><P><STRONG>SAP Sample Sankey Custom Widget as the Starting Point</STRONG></P><P>Since SAP Analytics Cloud does not include a native Sankey chart, I referred to a sample Sankey custom widget implementation shared by SAP through the SAP Community. This SAP-provided sample demonstrates how a Sankey diagram can be rendered using the SAC Custom Widget framework and served as a technical starting point for my work.</P><P>The SAP sample was very helpful as a technical reference, as it illustrated:</P><UL><LI>Custom widget structure and lifecycle</LI><LI>Binding SAC model data to a widget</LI><LI>Rendering a Sankey diagram using JavaScript</LI></UL><P>During mid-2024, while actively working on this requirement, I engaged in the comments section of that SAP Community blog (<A href="https://community.sap.com/t5/technology-blog-posts-by-sap/sap-analytics-cloud-custom-widget-amp-widget-add-ons-samples-preview/bc-p/13917451#M176481" target="_blank">SAP Analytics Cloud: Custom Widget & Widget Add-Ons Samples Preview</A>) under my SAP Community user<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/828979">@GovindaRaoBanothu45</a> , discussing feasibility and implementation boundaries.</P><P>While the sample clarified the basic rendering approach, it also highlighted functional gaps when applied to enterprise-scale analytical requirements.</P><P><STRONG>Why the SAP Sample Sankey Widget Was Not Sufficient</STRONG></P><P>The SAP-provided Sankey custom widget was intentionally designed as a generic sample. Its behavior was to render all nodes and all links returned by the bound dataset.</P><P>However, the sample widget did not support the enterprise analytical requirement I was addressing, specifically:</P><UL><LI>No Top-N filtering at the parent level</LI><LI>No independent ranking of child nodes per parent</LI><LI>No data trimming prior to Sankey rendering</LI><LI>Limited usability for large, real-world datasets</LI></UL><P>As a result, while the SAP sample demonstrated <EM>how</EM> to render a Sankey chart, it did not meet the analytical or usability needs of an enterprise dashboard.</P><P>This gap required extending and enhancing the SAP sample Sankey custom widget with additional, original logic to reshape the dataset <EM>before</EM> rendering.</P><P><STRONG>High-Level Architecture of the Extended Sankey Widget</STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_0-1766472320540.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354864iB98389D5B5D57B5E/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_0-1766472320540.png" alt="GovindaRaoBanothu45_0-1766472320540.png" /></span></P><P>The Sankey rendering mechanism remains aligned with the SAP sample, while the data-transformation layer represents the core extension.</P><P><STRONG>Development Environment and Prerequisites</STRONG></P><P><STRONG>Prerequisites</STRONG></P><P>To get started, ensure that you have:</P><UL><LI>Access to SAP Analytics Cloud with Optimized Design Experience enabled</LI><LI>Node.js installed on your local machine</LI><LI>Visual Studio Code (VS Code) installed</LI><LI>Basic understanding of JavaScript and JSON</LI></UL><P><STRONG>Development Environment Setup</STRONG></P><P><STRONG>Install Node.js</STRONG></P><OL><LI>Download Node.js from: <A href="https://nodejs.org" target="_blank" rel="noopener nofollow noreferrer">https://nodejs.org</A></LI><LI>Verify installation:</LI></OL><pre class="lia-code-sample language-bash"><code>node -v
npm -v</code></pre><P><STRONG>Install Visual Studio Code</STRONG></P><OL><LI>Download VS Code from: <A href="https://code.visualstudio.com" target="_blank" rel="noopener nofollow noreferrer">https://code.visualstudio.com</A></LI><LI>Use VS Code as the primary editor for developing and maintaining the custom widget files.</LI></OL><P><STRONG>Set Up the Project Folder</STRONG></P><OL><LI>Create a project folder (for example, SankeySAPcode).</LI><LI>Open the folder in Visual Studio Code.</LI><LI>Create the following files:</LI></OL><UL><LI>main.js – core widget logic and Sankey rendering</LI><LI>index.json – metadata and resource configuration</LI><LI>hash256.js – Node.js script for integrity key generation</LI></UL><P><STRONG>Sankey Custom Widget – Standard Project Structure</STRONG></P><P>For the standard Sankey implementation, I created a dedicated project folder (for example, SankeySAPcode or any relevant project name). This folder contains all the supporting and required files needed to build and deploy the Sankey custom widget in SAP Analytics Cloud.</P><P>The project folder includes:</P><UL><LI><STRONG>main.js</STRONG><BR />The web component JavaScript file that contains the core widget logic and Sankey rendering code (mandatory).</LI><LI><STRONG>index.json</STRONG><BR />The metadata file that defines the custom widget, its properties, and resource references (mandatory).</LI><LI><STRONG>sankeyChartStyling.js</STRONG><BR />An optional styling component used to expose styling options in the widget’s styling panel (optional).</LI><LI><STRONG>build/ folder</STRONG><BR />Contains the helper script hash256.js, which is used to generate SHA-256 integrity keys for the JavaScript files.</LI></UL><P>Note: Styling files (Styling.js, sankeyChartStyling.js, or xxxxStyling.js) are optional and may not be required for all custom widgets.<BR />However, the web component JavaScript file (main.js or equivalent) and the metadata file (index.json) are mandatory for every SAP Analytics Cloud custom widget. The build folder is included purely for maintainability and ease of understanding.<BR />Technically, the hash256.js file can also be placed directly in the main project folder. Including it in a separate subfolder is optional and does not impact deployment, as long as the final ZIP file complies with SAP Analytics Cloud’s upload requirements.</P><P><STRONG>Creating Integrity Keys and Deploying the Code</STRONG></P><P>SAP Analytics Cloud supports SHA-256 integrity validation to ensure uploaded JavaScript resources are not modified.</P><P>Generating Integrity Keys</P><OL><LI>Open a Node.js command prompt</LI><LI>Navigate to the folder containing hash256.js</LI><LI>Execute the script</LI></OL><P>Example output:</P><UL><LI>main.js: sha256-6134FFLCQYpH7x8gx29H1xJyRbitpf/guj5UGKhJ25Q=</LI><LI>sankeyChartStyling.js: sha256-AR0uDO+9h8nu9EyJotSinZ2APDWD5Ja2JmdFcZCH1Xo=</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_1-1766473046724.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354869iD9B0D3FE54390042/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_1-1766473046724.png" alt="GovindaRaoBanothu45_1-1766473046724.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_3-1766473094599.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354871i0F8581EE857FE2A4/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_3-1766473094599.png" alt="GovindaRaoBanothu45_3-1766473094599.png" /></span></P><P>Note: If additional JavaScript resource files are included, their corresponding sha256- integrity keys must also be added to the index.json file under the resources section.</P><P>Similarly, the file names must be updated in hash256.js before generating integrity keys for those files.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_4-1766473154728.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354872i4241C26FD2F3EA34/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_4-1766473154728.png" alt="GovindaRaoBanothu45_4-1766473154728.png" /></span></P><P><STRONG>Core Enhancement: Top-N Parent and Top-M Child Logic</STRONG></P><P>The following snippets are representative excerpts from the working implementation.<BR />Full scripts are intentionally not published, as this widget was delivered as part of an enterprise engagement.</P><P>In the widget, the SAC data binding is first normalized into an array (data) containing parent node, child node, and measure value fields.</P><P><STRONG>Step 1: Aggregate Measure by Parent</STRONG></P><pre class="lia-code-sample language-javascript"><code>const parentTotals = {};
data.forEach(row => {
parentTotals[row.parentNode] =
(parentTotals[row.parentNode] || 0) + Number(row.measureValue);
});</code></pre><P><STRONG>Step 2: Rank and Retain Top-N Parents</STRONG></P><pre class="lia-code-sample language-javascript"><code>const topParents = Object.entries(parentTotals)
.sort((a, b) => b[1] - a[1])
.slice(0, TOP_N_PARENT_NODES)
.map(entry => entry[0]);</code></pre><P><STRONG>Step 3: Rank and Retain Top-M Children per Parent</STRONG></P><pre class="lia-code-sample language-javascript"><code>const filteredLinks = [];
topParents.forEach(parent => {
const children = data
.filter(row => row.parentNode === parent)
.sort((a, b) => b.measureValue - a.measureValue)
.slice(0, TOP_N_CHILD_NODES);
children.forEach(child => {
filteredLinks.push({
source: parent,
target: child.childNode,
value: child.measureValue
});
});
});</code></pre><P><STRONG>Step 4: Rebuild Nodes</STRONG></P><pre class="lia-code-sample language-javascript"><code>const nodes = [...new Set(
filteredLinks.flatMap(l => [l.source, l.target])
)].map(name => ({ name }));</code></pre><P><STRONG>Step 5: Render Sankey</STRONG></P><pre class="lia-code-sample language-javascript"><code>sankey.nodes(nodes).links(filteredLinks).layout(32);</code></pre><P>The rendering follows the SAP sample approach; the innovation lies in the data-transformation logic.</P><P>Once all required files and script updates are ready, let’s deploy the custom widget into SAP Analytics Cloud.</P><P><STRONG>Deploy the Custom Widget</STRONG></P><OL><LI><STRONG>Create a ZIP File:</STRONG></LI><UL><LI>Include main.js, index.json, and optional styling files in a .zip file.</LI></UL><LI><STRONG>Upload to SAP Analytics Cloud:</STRONG></LI><UL><LI>Navigate to the Custom Widgets section in SAC.</LI><LI>Upload the .json and .zip files.</LI></UL><LI><STRONG>Test the Widget:</STRONG></LI><UL><LI>Add the custom widget to a story and configure data sources.</LI></UL></OL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_0-1766473672743.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354874i8770FFF8D82F9DA5/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_0-1766473672743.png" alt="GovindaRaoBanothu45_0-1766473672743.png" /></span></P><P>Select the JSON metadata file from your local system.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_1-1766473698360.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354875iE8F0E11B631A9B32/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_1-1766473698360.png" alt="GovindaRaoBanothu45_1-1766473698360.png" /></span></P><P>Next, select the resource file that is .zip file of main.js, index.json and styling.js or any other file related to this chart.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_2-1766473739981.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354877i9BBD79ACE4333A5B/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_2-1766473739981.png" alt="GovindaRaoBanothu45_2-1766473739981.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_4-1766473769830.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354879i984F01FBE40E58B0/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_4-1766473769830.png" alt="GovindaRaoBanothu45_4-1766473769830.png" /></span></P><P>Now you can the widget in "Custom Widgets" section.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_6-1766473919986.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354883iCAD16F58A3D77A65/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_6-1766473919986.png" alt="GovindaRaoBanothu45_6-1766473919986.png" /></span></P><P>Observe the warning messages below with and/without integrity checks in the list of custom widgets</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_7-1766473956257.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354885i18D8C583F6CE9CB6/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_7-1766473956257.png" alt="GovindaRaoBanothu45_7-1766473956257.png" /></span></P><P>We have done custom widget deployment and will use the same in stories. Let's see how to access this in story 2.0.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_8-1766474180045.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354887i1BF061C51DEACA23/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_8-1766474180045.png" alt="GovindaRaoBanothu45_8-1766474180045.png" /></span></P><P> </P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_9-1766474201684.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354888i0529535AD4970822/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_9-1766474201684.png" alt="GovindaRaoBanothu45_9-1766474201684.png" /></span></P><P>From the builder panel, select your data model, set measure, set a hierarchical dimension and edit the default colors of the different depths from the Styling panel.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_11-1766474268088.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354890i421DE32EE6A35A39/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_11-1766474268088.png" alt="GovindaRaoBanothu45_11-1766474268088.png" /></span></P><P>Below screenshot show the usage of the Styling.JS file.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_12-1766474306576.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354891iAF3800B9184BDFC9/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_12-1766474306576.png" alt="GovindaRaoBanothu45_12-1766474306576.png" /></span></P><P>In case the java script files get modified / tampered you get the below error while creating chart using custom widget in SAC story.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_13-1766474335478.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354892iFD0969DA25078BE4/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_13-1766474335478.png" alt="GovindaRaoBanothu45_13-1766474335478.png" /></span></P><P><STRONG>Validated Sankey Variants </STRONG>- Let's validate 3 different scenarios</P><UL><LI><STRONG>SAP sample Sankey (baseline):</STRONG></LI></UL><P> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_14-1766474406431.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354893i6397957D27A15C71/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_14-1766474406431.png" alt="GovindaRaoBanothu45_14-1766474406431.png" /></span></P><UL><LI><STRONG>Top 1 parent + Top 5 child nodes:</STRONG><UL><LI>This is the custom code and the main.js file is completely different than standard code with the key components are the below conditions below.</LI></UL></LI></UL><P> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_15-1766474594362.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354895i399A695663888595/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_15-1766474594362.png" alt="GovindaRaoBanothu45_15-1766474594362.png" /></span></P><P> Result of the above changes,</P><P> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_18-1766474714474.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354898i57490BBD6C099353/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_18-1766474714474.png" alt="GovindaRaoBanothu45_18-1766474714474.png" /></span></P><UL><LI><STRONG>Top 3 parents + Top 5 children:</STRONG></LI></UL><P> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_19-1766474813254.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354899iADA5FE798D87BB3A/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_19-1766474813254.png" alt="GovindaRaoBanothu45_19-1766474813254.png" /></span></P><P> Result of the above changes,</P><P> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GovindaRaoBanothu45_20-1766474876341.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354900i126042C7474BF241/image-size/medium?v=v2&px=400" role="button" title="GovindaRaoBanothu45_20-1766474876341.png" alt="GovindaRaoBanothu45_20-1766474876341.png" /></span></P><P>This incremental approach helped validate correctness before applying the logic to larger Top-N configurations.</P><P><STRONG>Acknowledgment</STRONG></P><P>I would like to thank <a href="https://community.sap.com/t5/user/viewprofilepage/user-id/719361">@marouferchichi</a> for actively engaging in the SAP Community blog comments and helping clarify the capabilities and boundaries of the sample Sankey custom widget.</P><P>The final implementation described here represents my independent extension and delivery of that sample to meet enterprise-level analytical requirements.</P><P><STRONG>Conclusion</STRONG></P><P>This blog demonstrates how a SAP sample Sankey custom widget can be extended into a production-grade Top-N hierarchical Sankey visualization using SAP Analytics Cloud’s Custom Widget framework.</P><P>For similar enterprise scenarios, this approach provides a reusable and scalable pattern.</P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P>2025-12-23T08:37:05.630000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464SAP UI5 - QUnit-Testing2026-01-11T23:36:30.594000+01:00dreicherthttps://community.sap.com/t5/user/viewprofilepage/user-id/835133<P><ul =""><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-1638366408">1. Setup</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-1570935622">1.1 Installing the required libraries</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-1177908612">1.2 Folder structure</a></li><li style="list-style-type:disc; margin-left:30px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--143834573">1.2.1 AllTests.js</a></li><li style="list-style-type:disc; margin-left:30px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--340348078">1.2.2 unitTests.qunit.html</a></li><li style="list-style-type:disc; margin-left:30px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--536861583">1.2.3 unitTests.qunit.js</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--146569074">2. Implementing unit tests</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--636485586">2.1 Basic structure of a test module</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--832999091">2.2 Simple Test using sinon.stub and sinon.spy</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--1029512596">2.3 Testing functions with Return Values or Promises</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--1226026101">2.4 Testing error cases and exceptions</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--1422539606">2.5 Larger example with more branches</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-1172365833">3. Running tests and coverage</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-850633012">3.1 Running tests</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-457606002">3.2 Test results in the QUnit interface</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId-261092497">3.3 Enabling code coverage</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--328448018">3.4 Detailed view of individual files</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--524961523">3.5 Coverage reports</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-qunit-testing/ba-p/14295464#toc-hId--428072021">4. Conclusion</a></li></ul></P><P>In this post, I share practical experience with QUnit tests from my UI5 projects.<BR />This post is aimed at developers who are already familiar with SAP UI5 and just getting started with QUnit. It was originally intended as an onboarding guide, but I noticed that information on this topic is quite scattered.<BR />The examples in this post are kept close to real project code and can also be used as a reference when implementing similar test cases.</P><P> </P><H1 id="toc-hId-1638366408">1. Setup</H1><H2 id="toc-hId-1570935622">1.1 Installing the required libraries</H2><P>To implement and execute QUnit tests in a UI5 application, just a few libraries are needed.<BR />We just add them to the dependencies section of the application's package.json.</P><P>Depending on the project, either a specific version or simply <EM>latest</EM> can be used.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="2bf0122c-3d55-455e-9ff0-7f420cfa5216.png" style="width: 467px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354787i71265CF561ED7782/image-size/large?v=v2&px=999" role="button" title="2bf0122c-3d55-455e-9ff0-7f420cfa5216.png" alt="2bf0122c-3d55-455e-9ff0-7f420cfa5216.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P>For later test coverage analysis (<EM>see section 3. Running Tests & Coverage</EM>), an additional library should be included:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="fc07ecd0-4e3d-40a4-acc5-db89aaeec475.png" style="width: 371px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354793iD74A597BA6C9FAD2/image-size/large?v=v2&px=999" role="button" title="fc07ecd0-4e3d-40a4-acc5-db89aaeec475.png" alt="fc07ecd0-4e3d-40a4-acc5-db89aaeec475.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P>This library also needs to be added to the ui5.yaml under "server>customMiddleware>".</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="0c5b668b-f789-4c0f-b720-2c3759930ba2.png" style="width: 321px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354794iADF3774B45A507D2/image-size/medium?v=v2&px=400" role="button" title="0c5b668b-f789-4c0f-b720-2c3759930ba2.png" alt="0c5b668b-f789-4c0f-b720-2c3759930ba2.png" /></span></P><P> </P><P> </P><P>After adding the required libraries, they can be installed using:</P><pre class="lia-code-sample language-bash"><code>npm install</code></pre><H2 id="toc-hId-1374422117"> </H2><H2 id="toc-hId-1177908612">1.2 Folder structure</H2><P>The folder structure for the unit tests is quite simple and is set up as follows:</P><pre class="lia-code-sample language-bash"><code>- webapp
- test
- unit
AllTests.js
unitTests.qunit.html
unitTests.qunit.js</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="d4c4ece3-f37d-4551-937b-eb15392cf5d0.png" style="width: 309px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354795i9077DB597E82660B/image-size/large?v=v2&px=999" role="button" title="d4c4ece3-f37d-4551-937b-eb15392cf5d0.png" alt="d4c4ece3-f37d-4551-937b-eb15392cf5d0.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P>The actual test files should ideally follow a structure similar to the application itself.<BR />This not a rule -> <STRONG>just for readability</STRONG><BR />For example, if unit tests are required for <EM>BaseController.js</EM>, a corresponding folder <EM>controller</EM> and a file <EM>BaseController.js</EM> should be created inside the test directory:</P><pre class="lia-code-sample language-bash"><code>- webapp
- controller
BaseController.js
- test
- unit
- controller
BaseController.js</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="21df0360-5d37-40a8-9ee4-560ab966e762.png" style="width: 309px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/354796i94B02ED89C3398E4/image-size/large?v=v2&px=999" role="button" title="21df0360-5d37-40a8-9ee4-560ab966e762.png" alt="21df0360-5d37-40a8-9ee4-560ab966e762.png" /></span></P><P> </P><H2 id="toc-hId-981395107"> </H2><H2 id="toc-hId-784881602"> </H2><H2 id="toc-hId-588368097"> </H2><H2 id="toc-hId-391854592"> </H2><H2 id="toc-hId-195341087"> </H2><H2 id="toc-hId--1172418"> </H2><H3 id="toc-hId--143834573">1.2.1 AllTests.js</H3><P>All QUnit tests to be executed are included in the define section of this file.<BR />It is a central place where we can enable or disable tests for execution.</P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([
"./controller/BaseController",
// "./controller/OtherController",
// "./controller/OtherController2",
// "./services/SampleService",
// "./util/SampleFormatter",
], function () {
"use strict";
});</code></pre><P>(Tip)</P><P>When working on unit tests for a specific controller an running them frequently, tests for other controllers can be commented out here, so they are not executed each time.</P><P> </P><H3 id="toc-hId--340348078">1.2.2 unitTests.qunit.html</H3><P>Short and simple: we start our tests using this file.<BR />All required libraries for test execution are included here.<BR /><BR /></P><pre class="lia-code-sample language-markup"><code><!DOCTYPE html>
<html>
<head>
<title>Unit tests for Template</title>
<meta charset="utf-8">
<script id="sap-ui-bootstrap"
src="../../../../resources/sap-ui-core.js"
data-sap-ui-resourceroots='{
"app.unittester": "../"
}'
data-sap-ui-async="true"
data-sap-ui-preload="async">
</script>
<link rel="stylesheet" type="text/css" href="../../../../resources/sap/ui/thirdparty/qunit-2.css">
<script src="../../../../resources/sap/ui/thirdparty/qunit-2.js"></script>
<script src="../../../../resources/sap/ui/qunit/qunit-junit.js"></script>
<script src="../../../../resources/sap/ui/thirdparty/sinon.js"></script>
<script src="../../../../resources/sap/ui/thirdparty/sinon-qunit.js"></script>
<script src="../../../../resources/sap/ui/qunit/qunit-coverage-istanbul.js"
data-sap-ui-cover-only="app/unittester/"
data-sap-ui-cover-never="app/unittester/test, app/unittester/localServices, app/unittester/services">
</script>
<script src="../../../../resources/sap/ui/thirdparty/sinon.js"></script>
<script src="../../../../resources/sap/ui/thirdparty/sinon-qunit.js"></script>
<script src="unitTests.qunit.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html></code></pre><P>Just a few notes on the most important scripts that are included here (because we will use them later).</P><P><STRONG>QUnit</STRONG></P><pre class="lia-code-sample language-markup"><code><link rel="stylesheet" type="text/css" href="../../../../resources/sap/ui/thirdparty/qunit-2.css">
<script src="../../../../resources/sap/ui/thirdparty/qunit-2.js"></script>
<script src="../../../../resources/sap/ui/qunit/qunit-junit.js"></script></code></pre><P>(Roughly summarized) The actual QUnit framework is included here.</P><P><STRONG>sinon and sinon-qunit</STRONG></P><pre class="lia-code-sample language-markup"><code><script src="../../../../resources/sap/ui/thirdparty/sinon.js"></script>
<script src="../../../../resources/sap/ui/thirdparty/sinon-qunit.js"></script></code></pre><P>These libraries enable spying, stubbing and mocking dependencies.</P><P><STRONG>qunit-coverage-istanbul.js (Code Coverage Integration)</STRONG></P><pre class="lia-code-sample language-markup"><code><script src="../../../../resources/sap/ui/qunit/qunit-coverage-istanbul.js"
data-sap-ui-cover-only="app/unittester/"
data-sap-ui-cover-never="[
app/unittester/test,
app/unittester/localServices,
app/unittester/services
]">
</script></code></pre><P>This is required later for code coverage.</P><P>(Note) In this post, we use Istanbul for code coverage.<BR />Of course, other options such as Karma can also be used if desired.</P><P>Important attributes:</P><UL><LI><STRONG>data-sap-ui-cover-only</STRONG><BR />files to be included in the coverage</LI><LI><STRONG>data-sap-ui-cover-never</STRONG><BR />files and directories to be excluded from coverage</LI></UL><P> </P><H3 id="toc-hId--536861583">1.2.3 unitTests.qunit.js</H3><P>This is the central entry point for the unit tests.<BR />Here we only include the tests from AllTests.js in the define section and start the test run.</P><pre class="lia-code-sample language-javascript"><code>QUnit.config.autostart = false;
sap.ui.getCore().attachInit(function () {
"use strict";
sap.ui.require([
"app/unittester/test/unit/AllTests"
], function () {
QUnit.start();
});
});</code></pre><H1 id="toc-hId--146569074">2. Implementing unit tests</H1><P>In this example, we will look at some unit tests for the BaseController as well as the basic structure.<BR />In addition, we will see how sinon can be used to handle dependencies.<BR /><SPAN><BR /></SPAN></P><H2 id="toc-hId--636485586">2.1 Basic structure of a test module</H2><P>Each module has its own tests and provides lifecycle hooks such as beforeEach and afterEach.<BR />The modules can be structured freely, depending on the requirements.</P><P>We start with a test module for BaseController.js and implement the module in the test file.<BR />(webapp>test>unit>controller>BaseController)</P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([
"app/unittester/controller/BaseController",
"sap/ui/thirdparty/sinon",
"sap/ui/thirdparty/sinon-qunit",
], function(BaseController) {
"use strict";
/**
* Module
*/
QUnit.module("Module", {
beforeEach: function() {
this.oBaseController = new BaseController();
},
afterEach: function() {
this.oBaseController.destroy();
}
});
});</code></pre><P>First, we include the actual BaseController and the two sinon libraries in the define statement.</P><P>After that, the module itself:<BR />The lifecycle functions of the module are executed before and after each test.<BR />In order to test the functions of the BaseController, we instatiate it before the test and destroy it afterwards.</P><H2 id="toc-hId--832999091">2.2 Simple Test using sinon.stub and sinon.spy</H2><P>In our first test, we will take the <STRONG>getRouter</STRONG> function from our BaseController.</P><pre class="lia-code-sample language-javascript"><code>getRouter: function() {
return this.getOwnerComponent().getRouter();
},</code></pre><P>We only want to test whether the function can be executed.</P><P>However, it has two dependencies:<BR />this.getOwnerComponent and its function getRouter.</P><P>In order to test getRouter without having to instantiate all dependencies, we will "simulate" them with sinon.</P><P>This would look like the following:</P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([
"app/unittester/controller/BaseController",
"sap/ui/thirdparty/sinon",
"sap/ui/thirdparty/sinon-qunit",
], function(BaseController) {
"use strict";
/**
* Module
*/
QUnit.module("Module", {
beforeEach: function() {
this.oBaseController = new BaseController();
},
afterEach: function() {
this.oBaseController.destroy();
}
});
/**
* BaseController - Check getRouter
*/
QUnit.test("BaseController - Check getRouter", function(assert) {
sinon.stub(this.oBaseController, "getOwnerComponent").returns({
getRouter: sinon.stub()
});
const fnSpy = sinon.spy(this.oBaseController, "getRouter");
this.oBaseController.getRouter();
assert.ok(fnSpy.calledOnce, "Check getRouter successful");
});
});</code></pre><P>The function flow:</P><P>In line 24, we create a stub for this.oBaseController (our controller instance) and listen for calls to the getOwnerComponent function.<BR />As soon as this function is executed, a return value is "simulated" that provides another simulated function, getRouter.</P><P>After that, we create a spy for this.oBaseController, that listens for calls to the getRouter function.</P><P>Then comes the actual function call in line 29.</P><P>Finally, we use an assertion where we check (via fnSpy.calledOnce) whether getRouter was executed at least once within the function.</P><P>And that's basically it. Every following test has the same structure (sometimes more or less complex).</P><P><STRONG>Running the test:<BR /></STRONG><SPAN>If we look at the coverage here now (more on this later), we can see that the function was executed exactly once:</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_Ap7HtUUpEr.png" style="width: 420px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356299i98D20494C4CF1148/image-size/large?v=v2&px=999" role="button" title="Citrix.DesktopViewer.App_Ap7HtUUpEr.png" alt="Citrix.DesktopViewer.App_Ap7HtUUpEr.png" /></span></P><P> </P><P> </P><P> </P><H2 id="toc-hId--1029512596">2.3 Testing functions with Return Values or Promises</H2><P>Many controller methods are asynchronous and return a Promise.</P><P>These tests are implemented using <EM>async</EM> and <EM>await</EM>.<BR />Using <EM>await</EM> ensures that the test waits properly for execution to finish.</P><P>Example: <STRONG>onStartEditMode<BR /></STRONG></P><pre class="lia-code-sample language-javascript"><code>onStartEditMode: async function () {
try {
this._fireSetViewBusy(true);
await this.oDraftManager.onEdit();
} catch (oError) {
this.evaluateException(oError);
} finally {
this._fireSetViewBusy(false);
}
},</code></pre><P>The corresponding test:</P><pre class="lia-code-sample language-javascript"><code>QUnit.test("BaseController - Start edit mode", async function (assert) {
sinon.stub(this.oBaseController, "_fireSetViewBusy");
sinon.stub(this.oBaseController, "evaluateException");
this.oBaseController.oDraftManager = {
onEdit: sinon.stub().returns(Promise.resolve(true))
};
const fnSpy = sinon.spy(this.oBaseController, "onStartEditMode");
await this.oBaseController.onStartEditMode();
assert.ok(fnSpy.calledOnce, "onStartEditMode executed successfully");
});</code></pre><H2 id="toc-hId--1226026101">2.4 Testing error cases and exceptions</H2><P>Error cases are just as important as successful scenarios.</P><P>In this example, we provoke an error by not stubbing any dependencies, so the function runs into the error path.<BR />This verifies that the error handling logic is executed correctly.</P><P>Example function onFullScreenPressed:</P><pre class="lia-code-sample language-javascript"><code>onFullScreenPressed: function() {
try {
this._fireSetLayout("MidColumnFullScreen");
this.getOwnerComponent().getModel("util").setProperty(`/ViewControl/Layout`, "MidColumnFullScreen");
return true;
} catch (oError) {
this.evaluateException(oError);
return false;
}
},</code></pre><P>The test:</P><pre class="lia-code-sample language-javascript"><code>QUnit.test("BaseController - Check onFullScreenPressed Exception", async function(assert) {
assert.notOk(await this.oBaseController.onFullScreenPressed(), "Check onFullScreenPressed Exception successful");
});</code></pre><H2 id="toc-hId--1422539606">2.5 Larger example with more branches</H2><P>A slightly larger example is the onCancel function, which is a bit more complex. </P><P>The function checks whether changes exist in the current draft.</P><P>Several paths (branches):<BR />Changes exist: the user is shown a dialog, where they can decide whether to discard the draft<BR />No changes: the draft is discarded<BR />Error case: an error message is shown</P><P>onCancel:</P><pre class="lia-code-sample language-javascript"><code>onCancel: async function(oEvent) {
try {
const oData = this.getView().getBindingContext().getObject();
//are there changes?
if (oData.DraftEntityCreationDateTime.toString() !== oData.DraftEntityLastChangeDateTime.toString()) {
const oSource = oEvent.getSource();
this._openDiscardDraftDialog(oSource);
return true;
} else {
//no changes, so discard the draft
return Promise.resolve(await this.onDiscardDraft());
}
} catch (oError) {
this.evaluateException(oError);
return false;
}
},</code></pre><P>We will now test all paths to achieve full coverage.</P><P>1. onCancel with draft changes</P><pre class="lia-code-sample language-javascript"><code>/**
* BaseController - Check onCancel (with draft changes)
*/
QUnit.test("BaseController - Check onCancel - with draft changes", async function(assert) {
this.oBaseController.onDiscardDraft = sinon.stub().returns(Promise.resolve(true));
sinon.stub(this.oBaseController, "_openDiscardDraftDialog");
sinon.stub(this.oBaseController, "getView").returns({
getBindingContext: sinon.stub().returns({
getObject: sinon.stub().returns({
DraftEntityCreationDateTime: 1,
DraftEntityLastChangeDateTime: 2
})
})
});
const oMockButton = new Button();
const oEvent = {
getSource: function() {
return oMockButton;
}
};
assert.ok(await this.oBaseController.onCancel(oEvent), "Check onCancel (with draft changes) successful");
});</code></pre><P>2. onCancel without changes</P><pre class="lia-code-sample language-javascript"><code>/**
* BaseController - Check onCancel (no draft changes)
*/
QUnit.test("BaseController - Check onCancel - no changes", async function(assert) {
this.oBaseController.onDiscardDraft = sinon.stub().returns(Promise.resolve(true));
sinon.stub(this.oBaseController, "getView").returns({
getBindingContext: sinon.stub().returns({
getObject: sinon.stub().returns({
DraftEntityCreationDateTime: 1,
DraftEntityLastChangeDateTime: 1
})
})
});
assert.ok(await this.oBaseController.onCancel(), "Check onCancel (no draft changes) successful");
});</code></pre><P>3. onCancel error case</P><pre class="lia-code-sample language-javascript"><code>/**
* BaseController - Check onCancel Exception
*/
QUnit.test("BaseController - Check onCancel Exception", async function(assert) {
assert.notOk(await this.oBaseController.onCancel(), "Check onCancel Exception successful");
});</code></pre><P>When we now run the tests, we can see that we have covered all paths (branches):</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_89tf2RbxCT.png" style="width: 836px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359491iFD56956C4B3C4522/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Citrix.DesktopViewer.App_89tf2RbxCT.png" alt="Citrix.DesktopViewer.App_89tf2RbxCT.png" /></span></P><H1 id="toc-hId--1325650104"> </H1><H1 id="toc-hId--1353979918"> </H1><H1 id="toc-hId--1550493423"> </H1><H1 id="toc-hId--1747006928"> </H1><H1 id="toc-hId--1943520433"> </H1><H1 id="toc-hId--2140033938"> </H1><H1 id="toc-hId-1958419853"> </H1><H1 id="toc-hId-1761906348"> </H1><H1 id="toc-hId-1565392843"> </H1><H1 id="toc-hId-1368879338"> </H1><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><H1 id="toc-hId-1172365833">3. Running tests and coverage</H1><P>Unit tests can be executed in different ways.<BR />In practice, running them via an npm script has proven to be the most convenient option.</P><H2 id="toc-hId-850633012">3.1 Running tests</H2><P>Unit tests can be started using a script defined in package.json:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="ead89be6-66ce-46de-ba9e-71ee5911ac1e.png" style="width: 652px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356300iBA999B7D4B6F2362/image-size/large?v=v2&px=999" role="button" title="ead89be6-66ce-46de-ba9e-71ee5911ac1e.png" alt="ead89be6-66ce-46de-ba9e-71ee5911ac1e.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P>The test run is then started using the following command:</P><pre class="lia-code-sample language-bash"><code>npm run unit-tests</code></pre><P>Once the tests are started, the browser opens and all tests (that are not commented out in AllTests.js) are executed automatically.</P><H2 id="toc-hId-654119507"><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="chrome_ESZgy2uDlt.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356302i73B0FA71475C46DE/image-size/large?v=v2&px=999" role="button" title="chrome_ESZgy2uDlt.png" alt="chrome_ESZgy2uDlt.png" /></span></H2><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><H2 id="toc-hId-457606002">3.2 Test results in the QUnit interface</H2><P>After the tests have been executed, an overview is displayed in the browser showing which tests were successful an which were not.<BR />This is also indicated by colors and shown together with an error message.</P><P>Example:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_x1y8OK1r6d.png" style="width: 831px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359498i781293553B3CD7B4/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Citrix.DesktopViewer.App_x1y8OK1r6d.png" alt="Citrix.DesktopViewer.App_x1y8OK1r6d.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P>Failed tests can also be inspected in the browser console.</P><P> </P><H2 id="toc-hId-261092497">3.3 Enabling code coverage</H2><P>Code coverage can be enabled using the checkbox "Enable coverage" in the QUnit toolbar.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="chrome_wCUCF5JgZH.png" style="width: 470px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356303i110ACAF488DA950F/image-size/large?v=v2&px=999" role="button" title="chrome_wCUCF5JgZH.png" alt="chrome_wCUCF5JgZH.png" /></span></P><P> </P><P> </P><P><BR />Once enabled, tests are re-run automatically and an interactive coverage overview is displayed below the test results.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="chrome_8BNiBACVoC.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356304i3EA63012E324B1B2/image-size/large?v=v2&px=999" role="button" title="chrome_8BNiBACVoC.png" alt="chrome_8BNiBACVoC.png" /></span></P><P> </P><P> </P><H2 id="toc-hId-64578992"> </H2><P>In the coverage overview, we can see the tested controllers and the information about the coverage itself.</P><P><STRONG>statements and lines<BR /></STRONG>Shows which lines of code were executed during the tests.<STRONG><BR /><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_0wXwB0uyW2.png" style="width: 502px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359505i8ABDD2D629EDBB9E/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Citrix.DesktopViewer.App_0wXwB0uyW2.png" alt="Citrix.DesktopViewer.App_0wXwB0uyW2.png" /></span></STRONG></P><P> </P><P> </P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_HBOPFpsmIf.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359506i81D36037A147CE78/image-size/medium/is-moderation-mode/true?v=v2&px=400" role="button" title="Citrix.DesktopViewer.App_HBOPFpsmIf.png" alt="Citrix.DesktopViewer.App_HBOPFpsmIf.png" /></span></P><P> </P><P> </P><P> </P><P><STRONG>functions</STRONG><BR />Indicates which functions were called at least once.</P><P><STRONG><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_9BZibtM57b.png" style="width: 422px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359507iF07F249E02A0DC7C/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Citrix.DesktopViewer.App_9BZibtM57b.png" alt="Citrix.DesktopViewer.App_9BZibtM57b.png" /></span></STRONG></P><P> </P><P> </P><P><STRONG>branches</STRONG><BR />Shows whether different execution paths (e.g. if/else, try/catch) were actually tested.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Citrix.DesktopViewer.App_ytqFExFz8B.png" style="width: 419px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/359508i6554488C536D7CD1/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Citrix.DesktopViewer.App_ytqFExFz8B.png" alt="Citrix.DesktopViewer.App_ytqFExFz8B.png" /></span></P><P> </P><P> </P><H2 id="toc-hId--131934513"> </H2><H2 id="toc-hId--328448018">3.4 Detailed view of individual files</H2><P>Clicking a file opens a detailed coverage view.</P><P><BR /><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="b133915d-24cb-42aa-bded-f293d2c098e4.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356305iF8CB479341F44936/image-size/large?v=v2&px=999" role="button" title="b133915d-24cb-42aa-bded-f293d2c098e4.png" alt="b133915d-24cb-42aa-bded-f293d2c098e4.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P><STRONG>green lines</STRONG> were executed<BR /><STRONG>red lines</STRONG> were not covered</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="991f915c-863c-48bf-9760-32060dfd6041.png" style="width: 465px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356306iCF44FBC9E9C5CDD0/image-size/large?v=v2&px=999" role="button" title="991f915c-863c-48bf-9760-32060dfd6041.png" alt="991f915c-863c-48bf-9760-32060dfd6041.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><H2 id="toc-hId--524961523">3.5 Coverage reports</H2><P>In addition to the browser view, a coverage report is generated under:<BR /><STRONG>tmp/coverage-reports/html/index.html</STRONG></P><P>Alternatevly, it can also be found in the IDE's project folder:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="1e4624f5-8474-419b-a54c-40e0d2aad0d7.png" style="width: 268px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/356307iDC7BB59732EA959A/image-size/large?v=v2&px=999" role="button" title="1e4624f5-8474-419b-a54c-40e0d2aad0d7.png" alt="1e4624f5-8474-419b-a54c-40e0d2aad0d7.png" /></span></P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P> </P><P>The report is updated automatically, can be opened in the browser independently.</P><H1 id="toc-hId--428072021">4. Conclusion</H1><P>With QUnit, we mainly focus on isolated controller logic and function behavior. <BR />Therefore keeping functions cleanly encapsulated and dependencies low during development leads to much easier unit tests</P><P>A note on coverage:<BR />I know that coverage percentages are often defined as targets.<BR />I have also seen that the temptation to push these numbers can be quite high by starting to test trivial functions (e.g. getters or setters). This usually backfires sooner or later.<BR />It makes much more sense to focus on testing critical logic, error cases and especially different paths and branches.</P>2026-01-11T23:36:30.594000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472SAP UI5 - Draft Handling in FreeStyle Applications2026-01-14T23:43:12.458000+01:00dreicherthttps://community.sap.com/t5/user/viewprofilepage/user-id/835133<P><ul =""><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-1638455810">1. Creating a DraftManager Class</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-1571025024">1.1 Initialization</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-1374511519">1.2 Draft Functions in the DraftManager</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-1048915295">2. Instantiating the DraftManager in the Controller</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-852401790">3. Using the DraftManager in the Controller</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-784971004">3.1 Start Editing</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-588457499">3.2 Save</a></li><li style="list-style-type:disc; margin-left:15px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-391943994">3.3 Cancel</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId-66347770">4. DraftHandling and MERGE lock errors</a></li><li style="list-style-type:disc; margin-left:0px; margin-bottom:1px;"><a href="https://community.sap.com/t5/technology-blog-posts-by-members/sap-ui5-draft-handling-in-freestyle-applications/ba-p/14298472#toc-hId--130165735">5. Conclusion</a></li></ul></P><P> </P><H1 id="toc-hId-1638455810">1. Creating a DraftManager Class</H1><P>In this post, I would like to share some of my experience with draft handling in FreeStyle applications.<BR />After we decided in one of our projects to implement draft handling and at the same time had to consider our new component-based architecture, we encapsulated SAP’s DraftController (sap.ui.generic.app.transaction.DraftController) in a way that made sense for our use case.</P><P>I describe one possible encapsulation of the DraftController in a service class called "DraftManager", which (depending on the project) can be integrated and used across FreeStyle applications.<BR />This approach worked very well for us, especially since official documentation was quite limited at that time.</P><P><STRONG>Important</STRONG>: In our application we use an OData V2 service (with CDS views) that is already prepared accordingly for draft handling.</P><H2 id="toc-hId-1571025024">1.1 Initialization</H2><P>The DraftManager is a service class used to encapsulate the DraftController.<BR />The following basic structure has proven itself in our projects and can be used as is or adapted and extended as needed.</P><P>When initializing the DraftManager in the controller, we only pass the component instance and the view’s binding context.</P><P>(Example)</P><pre class="lia-code-sample language-javascript"><code>this.oDraftManager = new DraftManager(this.getOwnerComponent(), oContext);</code></pre><P>The binding context is stored internally within the class for further processing and can be accessed or modified if required using the helper functions getActiveContext and setActiveContext.</P><P>Basic structure:</P><pre class="lia-code-sample language-javascript"><code>sap.ui.define([
"sap/ui/generic/app/transaction/DraftController"
], function (DraftController) {
"use strict";
const DraftManager = function (oComponent, oContext) {
this.oModel = oComponent.getModel();
this.oDraftController = new DraftController(this.oModel);
this.oActiveContext = oContext;
this.oDraftContext = null;
};
DraftManager.prototype.setActiveContext = function (oContext) {
this.oActiveContext = oContext;
};
DraftManager.prototype.getActiveContext = function () {
return this.oActiveContext;
};
return DraftManager;
});</code></pre><P> </P><H2 id="toc-hId-1374511519">1.2 Draft Functions in the DraftManager</H2><P>The DraftManager can be extended as needed, but in this post I would like to focus in particular on the main draft handling functions (Activate, Edit, and Discard).</P><P><STRONG>Determine or Create a Draft</STRONG></P><P>The DraftController itself is relatively easy to use.<BR />However, there are operations, such as Edit, that always require multiple steps.<BR />If a draft already exists, a new one cannot be created and an error will occur.</P><P>To avoid having to implement the same check in every controller, this logic is encapsulated.<BR />This way, only a single function needs to be executed.</P><P>In this example code, we first check whether a draft exists for the active view context.<BR />If so, it is returned and the controller can rebind the view accordingly.<BR />If not, a new draft is created.<BR /><BR />If needed, success and error messages can also be handled inside the function (for example, if they should be consistent across several applications). We use our own separate error handling in this case.</P><pre class="lia-code-sample language-javascript"><code>DraftManager.prototype.getOrCreateEditDraft = async function () {
const oExistingDraft = await this.oDraftController.getDraftForActiveEntity(this.oActiveContext);
if (oExistingDraft && !oExistingDraft.context.getObject().IsActiveEntity) {
this.oDraftContext = oExistingDraft.context;
return this.oDraftContext;
}
const oDraft = await this.oDraftController.createEditDraftEntity(this.oActiveContext);
this.oDraftContext = oDraft.context;
return this.oDraftContext;
};</code></pre><P><STRONG>Activate a Draft</STRONG></P><P>Activating a draft is pretty straightforward in this case. Here as well: The function can be extended as needed (error + success handling e.g.)</P><pre class="lia-code-sample language-javascript"><code>DraftManager.prototype.activateDraft = async function () {
await this.oDraftController.activateDraftEntity(this.oDraftContext);
this.oActiveContext = this.oDraftContext;
};</code></pre><P><STRONG>Discard a Draft</STRONG></P><P>Discarding a draft is also easy to implement in this case.</P><pre class="lia-code-sample language-javascript"><code>DraftManager.prototype.discardDraft = async function () {
await this.oDraftController.discardDraft(this.oDraftContext);
this.oDraftContext = null;
};</code></pre><H1 id="toc-hId-1048915295">2. Instantiating the DraftManager in the Controller</H1><P>As mentioned at the beginning, the DraftManager can be instantiated quite easily.</P><P>Example: We have a master/detail application with an onRouteMatched function in the detail controller.<BR />Assume we have a SmartTable on the master page and navigate to the detail page after clicking on an entry.<BR />Here, we pass the corresponding bound id (or guid) of the list item and the HasActiveEntity property.</P><P>Using this properties, we now create a raw context (helper function _loadContext) and start instantiating the DraftManager .</P><pre class="lia-code-sample language-javascript"><code>onRouteMatched: async function (oEvent) {
const sId = oEvent.getParameter("arguments").UUID;
const bActive = oEvent.getParameter("arguments").HasActiveEntity;
try {
const oContext = await this._loadContext("/EntitySet", { "UUID": sId, "IsActiveEntity": bActive });
this.oDraftManager = new DraftManager(this.getOwnerComponent(), oContext);
} catch (oError) {
//error handling
}
}</code></pre><pre class="lia-code-sample language-javascript"><code>_loadContext: async function (sEntity, oContextParameters) {
const oModel = this.getView().getModel();
await oModel.metadataLoaded();
const sKey = oModel.createKey(sEntity, oContextParameters);
const oContext = new sap.ui.model.Context(oModel, sKey);
return Promise.resolve(oContext);
},</code></pre><P>After that, the DraftManager is instantiated and can be used.</P><H1 id="toc-hId-852401790">3. Using the DraftManager in the Controller</H1><P>From this point on things become much more relaxed, as encapsulating the draft logic significantly reduces the number of operations in the controller.</P><P>I will keep the following examples to a minimum. Depending on the requirements, these can of course become more complex.</P><P>For rebinding/refreshing the view, we're using the following helper function:</P><pre class="lia-code-sample language-javascript"><code>_bindView: async function (sId, bIsActiveEntity) {
return new Promise((resolve) => {
this.getView().bindElement({
path: `/EntitySet(UUID=guid'${sId}',IsActiveEntity=${bIsActiveEntity})`,
parameters: {
expand: "DraftAdministrativeData"
},
events: {
change: (oEvent) => {
resolve(oEvent.getSource().getBoundContext());
}
}
});
});
};</code></pre><H2 id="toc-hId-784971004">3.1 Start Editing</H2><P>Editing has now become a fire-and-forget function, as everything is nicely encapsulated.<BR />After each draft operation, the view should be updated (in this case using _bindView).</P><pre class="lia-code-sample language-javascript"><code>onEdit: async function () {
try {
const oDraftContext = await this.oDraftManager.getOrCreateEditDraft();
await this._bindView(
oDraftContext.getProperty("UUID"),
oDraftContext.getProperty("IsActiveEntity")
);
} catch (oError) {
//error handling...
}
}</code></pre><H2 id="toc-hId-588457499">3.2 Save</H2><P>After activating the draft, the view is rebound to the active entity.</P><pre class="lia-code-sample language-javascript"><code>onSave: async function () {
try {
await this.oDraftManager.activateDraft();
await this._bindView(
this.oDraftManager.getActiveContext().getProperty("UUID"),
this.oDraftManager.getActiveContext().getProperty("IsActiveEntity")
);
} catch (oError) {
//error handling...
}
}</code></pre><H2 id="toc-hId-391943994">3.3 Cancel</H2><P>When canceling, the draft is discarded and the view is rebound to the active context.</P><pre class="lia-code-sample language-javascript"><code>onDiscard: async function () {
try {
await this.oDraftManager.discardDraft();
await this._bindView(
this.oDraftManager.getActiveContext().getProperty("UUID"),
this.oDraftManager.getActiveContext().getProperty("IsActiveEntity")
);
} catch (oError) {
//error handling...
}
}</code></pre><H1 id="toc-hId-66347770">4. DraftHandling and MERGE lock errors</H1><P>One important learning from our projects was dealing with MERGE lock errors.<BR />As soon as draft handling is applied to larger views with many form fields and especially rich text editors, you will sooner or later run into locking issues, as we did.</P><P>This happens because the DraftController triggers a MERGE request on every change in the view (which is correct behavior).<BR />However, if <STRONG>too many changes</STRONG> are <STRONG>performed too quickly</STRONG>, multiple MERGE requests can be sent at the same time and end up locking each other.</P><P>This can easily be reproduced with a rich text editor in the view by pressing the Enter key very quickly. The result is a large number of lock errors (The draft itself is still updated correctly, but this can become quite annoying when working with messaging).</P><P>Our solution for this was to work with <STRONG>deferredGroups</STRONG> and <STRONG>changeGroups</STRONG>.</P><P>In order to get this to work with the DraftController and the service class, we use the merge timer provided by SAP.</P><P>First we start by setting the deferredGroup and changeGroup in the onInit function of the view controller.</P><pre class="lia-code-sample language-javascript"><code>this.getOwnerComponent().getModel().setDeferredGroups(["Changes"]);
this.getOwnerComponent().getModel().setChangeGroups({
"*": {
groupId: "Changes",
changeSetId: "Changes",
single: false
}
});
this.getOwnerComponent().getModel().attachPropertyChange(this.onEnqueueSubmitChanges.bind(this, 1));</code></pre><P>After that we define the propertyChange handler function onEnqueueSubmitChanges.</P><pre class="lia-code-sample language-javascript"><code>onEnqueueSubmitChanges: function (nIntervalInSeconds) {
const mParameters = this.getOwnerComponent().getModel("util").getProperty("/ViewControl/batchParameters");
this.oDraftManager.triggerSubmitChanges(nIntervalInSeconds, mParameters);
}</code></pre><P>And finally, we define a new DraftManager function triggerSubmitChanges, which bundles the requests using the merge timer and submits them accordingly.</P><pre class="lia-code-sample language-javascript"><code>oDraftManager.prototype.triggerSubmitChanges = function (nIntervalInSeconds, mParameters) {
const oDraftMergeTimer = this.oDraftController._oDraftMergeTimer;
if (!oDraftMergeTimer.nTimeoutID) {
oDraftMergeTimer.nTimeoutID = setTimeout(() => {
oDraftMergeTimer.nTimeoutID = null;
if (this.oModel.hasPendingChanges()) {
this.oDraftController.triggerSubmitChanges(mParameters)
.catch((oError) => {
console.error("Error during submit changes:", oError);
});
}
}, (nIntervalInSeconds || 0) * 1000);
}
};</code></pre><H1 id="toc-hId--130165735">5. Conclusion</H1><P>I have reduced the examples to the basics, as they can be extended as needed. We had very good experience with this service class, and draft handling in FreeStyle applications in combination with an OData V2 service worked very well.</P><P>The DraftController can of course also be used directly in the controller.<BR />However, as mentioned before, encapsulating it is worthwhile when working with a larger application architecture, especially when testing is relevant in the project.</P>2026-01-14T23:43:12.458000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/code-connect-2026-is-coming-mark-your-calendars/ba-p/14307923Code Connect 2026 is Coming – Mark Your Calendars!2026-01-16T10:17:39.514000+01:00BirgitShttps://community.sap.com/t5/user/viewprofilepage/user-id/41902<P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Code Connect logo" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361588i1404B9EF84CF278E/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="CodeConnectBanner.png" alt="CodeConnectBanner.png" /></span></SPAN></P><P> </P><P><SPAN>We’re excited to announce the return of <A href="https://code-connect.dev/" target="_blank" rel="noopener nofollow noreferrer"><STRONG>Code Connect</STRONG> </A>for its third edition, bringing together three dynamic events - <STRONG>UI5con, reCAP, and HANA Tech Con</STRONG> - under one roof. Mark your calendars for <STRONG>July 13 to 16, 2026</STRONG>, and join us in <STRONG>St. Leon-Rot, Germany</STRONG>, or online.</SPAN></P><P> </P><H2 id="toc-hId-1787830851"><SPAN>What is Code Connect?</SPAN></H2><P><SPAN>Code Connect creates a unique opportunity to experience three specialized events in one location: <A href="https://openui5.org/ui5con/" target="_blank" rel="noopener nofollow noreferrer">UI5con</A>, <A href="https://recap-conf.dev/" target="_blank" rel="noopener nofollow noreferrer">reCAP</A>, and <A href="https://hanatech.community/" target="_blank" rel="noopener nofollow noreferrer">HANA Tech Con</A>, allowing you to dive deep into different aspects of SAP development. Code Connect is designed for developers at every level: Whether you're an SAP veteran or just starting out, this is your chance to connect, learn, and innovate alongside a vibrant community of developers.</SPAN></P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Collage of photos from past Code Connect events" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361582i126BE1733C3ACA09/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="Collage.png" alt="Collage.png" /></span></SPAN></P><P> </P><H3 id="toc-hId-1720400065">Your Week at Code Connect</H3><H3 id="toc-hId-1523886560"><SPAN>July 13: Code Jam Sessions and Warmup</SPAN></H3><P><SPAN>Kick things off with our Code Jam sessions - a hands-on way to sharpen your skills before the main event. Afterward, join us for a pre-conference networking event to meet fellow attendees in a relaxed setting.</SPAN></P><P> </P><P><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Logo UI5con" style="width: 200px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361598i6723ED463E741644/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="UI5con2.png" alt="UI5con2.png" /></span></P><H3 id="toc-hId-1327373055"> </H3><H3 id="toc-hId-1130859550"> </H3><H3 id="toc-hId-934346045"><SPAN>July 14: UI5con </SPAN></H3><P>The official program kicks off with UI5con, bringing together UI5 enthusiasts to share insights, explore the latest innovations, and build new connections. Expect expert sessions, interactive workshops, and plenty of opportunities to engage with the UI5 community.</P><P><SPAN><A href="https://openui5.org/ui5con/" target="_blank" rel="noopener nofollow noreferrer">Learn more about UI5con</A>.</SPAN></P><P> </P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Logo reCAP" style="width: 200px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361611i7856787EDD6734D2/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="reCAP_3.png" alt="reCAP_3.png" /></span></SPAN></P><H5 id="toc-hId-995997978"> </H5><H5 id="toc-hId-799484473"> </H5><H5 id="toc-hId-602970968"> </H5><H5 id="toc-hId-406457463"> </H5><H3 id="toc-hId--123452849"><SPAN>July 15: reCAP</SPAN></H3><P>The next day focuses on the SAP Cloud Application Programming Model (CAP). At reCAP, developers, customers, and partners meet the CAP Product Team to discuss technical concepts, share project experiences, and explore future possibilities.</P><P><SPAN><A href="https://recap-conf.dev/" target="_blank" rel="noopener nofollow noreferrer">Learn more about reCAP</A>.</SPAN></P><P> </P><P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-left" image-alt="Logo HANA Tech Con" style="width: 150px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/361578iD47004A3E8E22283/image-size/large/is-moderation-mode/true?v=v2&px=999" role="button" title="HANA_tech_con.png" alt="HANA_tech_con.png" /></span></SPAN></P><P> </P><P> </P><P> </P><H3 id="toc-hId--319966354"> </H3><H3 id="toc-hId--516479859">July 16: HANA Tech Con</H3><P>The week concludes with HANA Tech Con. Delve into the HANA universe and join development experts, users, and partners to exchange knowledge and ignite new ideas. If you've ever had questions about HANA that you haven't found answers to, this is your chance to get them resolved.</P><P><SPAN><A href="https://hanatech.community/" target="_blank" rel="noopener nofollow noreferrer">Learn more about HANA Tech Con</A>.</SPAN></P><P><SPAN> </SPAN></P><H2 id="toc-hId--419590357">Early Bird Process</H2><P><SPAN>Planning a longer trip? We offer a limited number of early bird tickets for attendees who need to arrange travel well in advance. </SPAN></P><P>Check our <SPAN><A href="https://code-connect.dev/faq.html" target="_blank" rel="noopener nofollow noreferrer">FAQ document</A></SPAN> for details on how to secure your ticket.</P><P> </P><H2 id="toc-hId--616103862"><SPAN>Sponsorship Opportunities</SPAN></H2><P>Be a part of Code Connect 2026 and become a sponsor to support the developer community at UI5con, reCAP, and HANA Tech Con. By sponsoring Code Connect, you gain access to a diverse audience spanning front-end developers, backend specialists, and database experts – all in one event.</P><P><SPAN>Read our <A href="https://cap.cloud.sap/resources/events/Code_Connect_2026_Sponsor_Packages.pdf" target="_blank" rel="noopener nofollow noreferrer">sponsorship prospectus</A> to check our sponsorship opportunities.</SPAN></P><P> </P><H2 id="toc-hId--812617367"><SPAN>Call for Proposals</SPAN></H2><P>Our Call for Speakers runs from 26 January to 13 March. As a speaker, you’ll be an active part of Code Connect, shaping the conversation, sharing your expertise, and inspiring the developer community. Don’t wait and submit your session proposal by 13 March at the latest.</P><UL><LI><SPAN><A href="https://ui5con.cfapps.eu12.hana.ondemand.com/" target="_blank" rel="noopener nofollow noreferrer">Call for proposals UI5con</A></SPAN></LI><LI><SPAN><A href="https://recap.cfapps.eu12.hana.ondemand.com/" target="_blank" rel="noopener nofollow noreferrer">Call for proposals reCAP</A></SPAN></LI><LI><SPAN><A href="https://hanatech.cfapps.eu12.hana.ondemand.com/" target="_blank" rel="noopener nofollow noreferrer">Call for proposals HANA Tech Con</A></SPAN></LI></UL><H2 id="toc-hId--1009130872"> </H2><H2 id="toc-hId--1205644377"><SPAN>Important Dates</SPAN></H2><UL><LI><SPAN>Call for Proposals: January 26, 2026 to March 13, 2026.</SPAN></LI><LI><SPAN>Registration Opens: April 10, 2026</SPAN></LI><LI><SPAN>Agenda Published: Early June 2026</SPAN></LI><LI><SPAN>Code Connect Week: July 13 to 16, 2026</SPAN></LI></UL><P><SPAN> </SPAN></P><H2 id="toc-hId--1402157882"><SPAN>Ready to Connect?</SPAN></H2><P><SPAN><A href="https://code-connect.dev/" target="_blank" rel="noopener nofollow noreferrer">Code Connect 2026</A> represents more than just learning opportunities. It's about building relationships, sharing knowledge, and being part of a community that's shaping the future of SAP development.</SPAN></P><P><SPAN>Join us at Code Connect 2026 and be part of a community driving the future of technology. We can't wait to see you there!</SPAN></P><P> </P>2026-01-16T10:17:39.514000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/ui5-mcp-server-got-extended-for-ui-integration-cards-generation/ba-p/14312670UI5 MCP Server Got Extended for UI Integration Cards Generation2026-01-22T13:54:47.553000+01:00daniel_vladinovhttps://community.sap.com/t5/user/viewprofilepage/user-id/198422<P>Following the <A title="UI5 MCP server article" href="https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/ba-p/14200825" target="_blank">recent announcement</A> for the release and availability of the <SPAN><STRONG>UI5 MCP server</STRONG>, </SPAN><SPAN>we are pleased to announce that the UI5 MCP Server now supports the development of <STRONG>UI Integration Cards</STRONG>. In its latest version – v0.2.1 – it includes three new tools dedicated to UI Integration Cards. These tools assist Large Language Models (LLMs) in creating new cards from scratch or editing existing ones, following the best practices for card development.</SPAN></P><H2 id="toc-hId-1788602686">Infuse Creativity Into Your UI Integration Cards</H2><P>The updated UI5 MCP Server version v.0.2.1 enhances card development with AI Agents like Cline. Fostering creativity and efficiency, this approach makes it easy for both developers and non-developers to create UI Integration Cards smoothly.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="daniel_vladinov_0-1769086020322.png" style="width: 524px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/364106i404ABB65D842874E/image-dimensions/524x314/is-moderation-mode/true?v=v2" width="524" height="314" role="button" title="daniel_vladinov_0-1769086020322.png" alt="daniel_vladinov_0-1769086020322.png" /></span></P><P class="lia-align-center" style="text-align: center;"><EM>Usage of the UI5 MCP Server in Cline.</EM></P><H2 id="toc-hId-1592089181"> </H2><H2 id="toc-hId-1395575676">Why Use the UI5 MCP Server for Integration Cards Development?</H2><P>The reasons for initially using UI5 MCP Server are similarly applicable to developing Integration Cards:</P><UL><LI>LLMs might lack information on the latest best practices and APIs recommended for UI Integration Cards development.</LI><LI>LLMs might suggest using the properties of UI Integration Cards or APIs that are not available in your project's version or have been marked as deprecated.</LI><LI>Scaffolding can help agents kickstart new projects faster and with established patterns, saving token costs.</LI></UL><P><STRONG>and furthermore:</STRONG></P><UL><LI>Encouraging the use of cards of type Declarative to make the card also compatible with Mobile Native platforms.</LI><LI>Providing specific knowledge for each card type to the LLMs, such as the available chart types in Analytical cards, and enabling the LLMs to suggest the best chart type for your data.</LI><LI>And more…</LI></UL><H2 id="toc-hId-1199062171">Tools</H2><OL><LI>Best practice guidance for your agent: <STRONG><FONT face="courier new,courier">get_integration_cards_guidelines</FONT></STRONG></LI><LI>Scaffolding a new Integration Card: <FONT face="courier new,courier"><STRONG>create_integration_card</STRONG></FONT></LI><LI>Validating the Card’s manifest: <FONT face="courier new,courier"><STRONG>run_manifest_validation</STRONG></FONT></LI></OL><H2 id="toc-hId-1002548666">Setup & Use </H2><P>Install the UI5 MCP Server v.0.2.1 in your AI agent, as described <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/ba-p/14200825" target="_blank">here</A>. Once this version or later is installed, your AI Agent should automatically gain access to the new tools. Once you define your prompt for generating a Card the AI Agent will offer you as first step to run the <STRONG><FONT face="courier new,courier">get_integration_cards_guidelines</FONT></STRONG><FONT face="courier new,courier"><FONT face="arial,helvetica,sans-serif">, then will scaffold the very coding project with dynamically created Card, and finally will run the manifest schema validation tool. Enjoy your Card! </FONT></FONT></P><H2 id="toc-hId-806035161">What’s Next?</H2><P>We plan to further enhance the existing tools to handle more complex scenarios, such as creating or editing child cards, pagination in list and table cards, a more advanced configuration editor for the generated card, and more.</P>2026-01-22T13:54:47.553000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/practical-sapui5-form-validation-name-email-amp-password-fields/ba-p/14316811Practical SAPUI5 Form Validation: Name, Email & Password Fields2026-01-28T14:21:49.330000+01:00Myvizhipriya_Thangaraj_2810https://community.sap.com/t5/user/viewprofilepage/user-id/1477011<H3 id="toc-hId-1917806306">Introduction</H3><P>Form validation plays a critical role in SAPUI5 applications. It ensures clean data entry, improves user experience, and adds an extra layer of security.</P><P>In this blog, In this article, we build a simple <STRONG>employee registration form</STRONG> that focuses on practical, real-world basic validation scenarios as follows:</P><UL><LI>Allow only alphabets in the <STRONG>Name</STRONG> field</LI><LI>Validate <STRONG>Email</STRONG> format as the user types</LI><LI>Block <STRONG>copy, cut, and paste</STRONG> for the Password field</LI><LI>Show clear, real-time validation feedback</LI></UL><P>If you are unfamiliar with regex, please refer to my previous article <A title="Understanding Regex in JavaScript - A Beginner-Friendly Guide" href="https://community.sap.com/t5/technology-blog-posts-by-members/understanding-regex-in-javascript-a-beginner-friendly-guide/ba-p/14279708" target="_blank">Understanding Regex in JavaScript - A Beginner-Friendly Guide</A> for a better understanding before proceeding.</P><H3 id="toc-hId-1721292801">Let's discuss, along with the use case basic employee registration screen where data quality matters from the start:</H3><UL><LI>Names should not contain numbers or special characters </LI><LI>Email addresses must be structurally valid </LI><LI>Passwords should be typed manually to avoid reuse or weak stored values </LI><LI>Users should immediately know <EM>what is wrong</EM> and <EM>how to fix it</EM></LI></UL><P>SAPUI5 provides built-in support for this through value states and event handling.</P><H5 id="toc-hId-1782944734"><STRONG>Form.view.xml</STRONG></H5><pre class="lia-code-sample language-markup"><code><mvc:View
controllerName="demo.controller.Form"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<VBox class="sapUiSmallMargin">
<Input
id="nameInput"
placeholder="Full Name (Alphabets only)"
liveChange=".onNameChange" />
<Input
id="emailInput"
placeholder="Email Address"
liveChange=".onEmailChange" />
<Input
id="passwordInput"
type="Password"
placeholder="Password (Copy/Paste Disabled)"
paste=".onPasteBlock"
copy=".onCopyBlock"
cut=".onCutBlock" />
<Button text="Submit" press=".onSubmit" />
</VBox>
</mvc:View></code></pre><P><STRONG>Please Note: </STRONG>Using <CODE>liveChange</CODE> allows us to validate input as the user types, rather than waiting until submission.</P><P> </P><H3 id="toc-hId-1328265791">Name Validation (Alphabets Only)</H3><P>Names typically contain letters and spaces. A following simple regular expression helps enforce this rule.</P><H5 id="toc-hId-1389917724"><STRONG>Form.controller.js</STRONG></H5><pre class="lia-code-sample language-javascript"><code>onNameChange: function (oEvent) {
var sValue = oEvent.getParameter("value");
var oInput = oEvent.getSource();
var oRegex = /^[A-Za-z ]+$/; //Alphabets Regex
if (!oRegex.test(sValue)) {
oInput.setValueState("Error");
oInput.setValueStateText("Only alphabets are allowed.");
} else {
oInput.setValueState("None");
}
},
//Accepted: John, Anna Marie
//Rejected: John123, Anna!</code></pre><P> </P><H3 id="toc-hId-935238781">Email Validation (Structural Check)</H3><P>Instead of allowing any string, we validate whether the email follows a basic, widely accepted format.</P><H5 id="toc-hId-996890714"><STRONG>Form.controller.js</STRONG></H5><pre class="lia-code-sample language-javascript"><code>onEmailChange: function (oEvent) {
var sValue = oEvent.getParameter("value");
var oInput = oEvent.getSource();
var oRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; //Email Regex
if (!oRegex.test(sValue)) {
oInput.setValueState("Error");
oInput.setValueStateText("Invalid email format.");
} else {
oInput.setValueState("None");
}
},
//Valid example: test@example.com
//Invalid example: test@, name@mail</code></pre><P><STRONG>Please Note:</STRONG> This doesn’t verify whether the email exists, but it <STRONG>prevents clearly invalid entries early</STRONG>.</P><P> </P><H3 id="toc-hId-542211771">Password Security: Blocking Copy, Cut & Paste</H3><P>For sensitive fields like passwords, allowing paste can lead to reused or insecure credentials. Here, we explicitly block copy, cut and paste actions.</P><H5 id="toc-hId-603863704"><STRONG>Form.controller.js</STRONG></H5><pre class="lia-code-sample language-javascript"><code>onPasteBlock: function (oEvent) {
oEvent.preventDefault();
var oInput = oEvent.getSource();
oInput.setValueState("Error");
oInput.setValueStateText("Pasting is disabled for security reasons.");
},
onCopyBlock: function (oEvent) {
oEvent.preventDefault();
},
onCutBlock: function (oEvent) {
oEvent.preventDefault();
},</code></pre><P> </P><H3 id="toc-hId-149184761"><STRONG>Final Submit Check</STRONG></H3><P>Before submission, we verify that no field is in an error state.</P><H5 id="toc-hId--709366127"><STRONG>Form.controller.js</STRONG></H5><pre class="lia-code-sample language-javascript"><code>onSubmit: function () {
var sNameInput = this.byId("nameInput");
var sEmailInput = this.byId("emailInput");
var sPasswordInput = this.byId("passwordInput");
if (
sNameInput.getValueState() === "Error" ||
sEmailInput.getValueState() === "Error" ||
sPasswordInput.getValueState() === "Error"
) {
sap.m.MessageToast.show("Please fix the errors before submitting.");
return;
}
sap.m.MessageToast.show("Form submitted successfully!");
},</code></pre><P>This keeps the logic simple and ensures only valid data moves forward.</P><P> </P><H3 id="toc-hId--319073618"><STRONG>Conclusion:</STRONG></H3><P>With minimal code, we have achieved a clean and secure SAPUI5 form featuring:</P><UL><LI>Cleaner and more reliable user input</LI><LI>Immediate feedback using SAPUI5 value states</LI><LI>Better password handling for sensitive fields</LI><LI>A smoother and more professional user experience</LI></UL><P>This approach significantly improves data quality and user experience in SAPUI5 applications.</P><P> </P><P>This is basic form validation using regex, but if you have alternative methods, improvements, or insights, please feel free to share them. I would love to learn from your experience too!</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-01-28T14:21:49.330000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/declarative-ui-integration-cards-consistent-framework-with-flexible-content/ba-p/14320024Declarative UI Integration Cards - Consistent Framework With Flexible Content Capabilities2026-02-02T16:34:17.547000+01:00daniel_vladinovhttps://community.sap.com/t5/user/viewprofilepage/user-id/198422<H1 id="toc-hId-1660377989">Introduction</H1><P>Since the early announcement of the UI Integration Cards paradigm (as described in <A href="https://community.sap.com/t5/technology-blog-posts-by-sap/integration-cards/ba-p/13419040" target="_blank">this article</A>), we have been busy expanding the functionality of Cards, resulting in an ever-growing adoption across almost all SAP products.</P><P>Today, Cards are utilized in a range of solutions and scenarios, from the SAP S/4HANA home page with “To-Do” and Insights Cards, to SAP Build Work Zone, where Cards from any SAP product can be easily assembled together, to Joule, and also on mobile-native screens. </P><P> </P><TABLE border="1" width="100%"><TBODY><TR><TD width="25%"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_2-1770042983149.png" style="width: 152px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368153i8F551055D0A6A195/image-dimensions/152x305?v=v2" width="152" height="305" role="button" title="daniel_vladinov_2-1770042983149.png" alt="daniel_vladinov_2-1770042983149.png" /></span></TD><TD width="25%"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_4-1770043358952.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368156iECCDF6A08408B795/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_4-1770043358952.png" alt="daniel_vladinov_4-1770043358952.png" /></span><P> </P></TD><TD width="50%"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_3-1770042997774.png" style="width: 367px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368154i80C90A47DAE08235/image-dimensions/367x379?v=v2" width="367" height="379" role="button" title="daniel_vladinov_3-1770042997774.png" alt="daniel_vladinov_3-1770042997774.png" /></span></TD></TR></TBODY></TABLE><P> </P><P>The fundamental principles that underpin this technology, as detailed on our <A href="https://ui5.sap.com/test-resources/sap/ui/integration/demokit/cardExplorer/webapp/index.html" target="_blank" rel="noopener noreferrer">Card Explorer landing page</A>, are that it is designed to be Declarative, Easy to consume, and Consistent.</P><OL><LI><STRONG>Declarative</STRONG>: configured by a manifest.json ==><STRONG> Low to No-code skills required </STRONG>(AI-gen@<EM>UI5 MCP server</EM>)</LI><LI><STRONG>Easy to consume</STRONG>: product and UI-technology agnostic ==><STRONG> </STRONG><STRONG>Mobile-ready /</STRONG> <STRONG>Integration-ready via configuration</STRONG></LI><LI><STRONG>Consistent</STRONG>: <U>predefined</U> card types in Fiori/Horizon design ==> <STRONG>Thoughtfully designed / Product Standards covered</STRONG></LI></OL><H2 id="toc-hId-1592947203">Key Concepts and Capabilities</H2><P>In this article, we’ll detail the key qualities and advantages of the Declarative Integration Cards. Declarative Card types offer a range of display options and minimalist interactions, including List Card, Table Card, Timeline, Calendar, Analytical, and Object Card with input elements.</P><P>Declarative Card types enable the fast and easy creation of “Home page” and “Overview” pages, where Card developers can integrate data from various sources to display relevant business data for any SAP or non-SAP product.</P><H2 id="toc-hId-1396433698">Declarative and Consistent</H2><P>Integration Cards are straightforwardly defined using a single JSON descriptor file called manifest.json. This file is all you need to get a fully functional and nicely rendered representation of business data in the form of a Card. Once you have this descriptive manifest.json file, our Card Runtime can process it and render the card in a web browser or any mobile-native device, regardless of the UI framework. Hence – declarative! </P><H1 id="toc-hId-1070837474">Anatomy of Declarative UI Integration Cards</H1><P>Examining the Card's structure, we can identify three separate parts:<span class="lia-inline-image-display-wrapper lia-image-align-right" image-alt="daniel_vladinov_5-1770043508459.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368157i6E95926A72837282/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_5-1770043508459.png" alt="daniel_vladinov_5-1770043508459.png" /></span></P><P>The <STRONG>Card Header</STRONG> provides the card's identity, typically represented by a title, subtitle, and icon or avatar/image attributes. It can also display more dynamic or complex data, such as the special Numeric Header, which prominently features a key numeric indicator, or an extended Header.</P><UL><LI>The <STRONG>Content / Data</STRONG> is the main part of the card that displays relevant business data in a selected type of view. These data types can be List, Table, Object, Analytical, Timeline, or Calendar.</LI><LI>The <STRONG>Footer area</STRONG> is usually designated for intended Card interaction elements, such as action buttons or links for navigation.</LI></UL><P>The general structure of a <STRONG>declarative manifest.json</STRONG> looks like this: </P><P> </P><pre class="lia-code-sample language-json"><code>{
"sap.app": {
"id": "my.card.id",
"type": "card",
"applicationVersion": {
"version": "1.0.0"
}
},
"sap.card": {
"type": "List", // Card type: List, Table, Object, Analytical, Timeline, Calendar
"header": {
// Header configuration
},
"content": {
// Content configuration specific to card type
},
"footer": {
// Optional footer with actions
}
}
}</code></pre><P> </P><H2 id="toc-hId-1003406688">The Card Header</H2><P>The card header displays essential information about the card by grouping a set of card attributes and presenting them at the top of the card. The "sap.card"/header section is mandatory. Providing it together with the title is essential for proper screen reader support and other functionalities.</P><P>Integration Cards support three header configurations:</P><OL><LI><STRONG>Default Header</STRONG> - Standard header with title, subtitle, icon, status</LI><LI><STRONG>Numeric Header</STRONG> - KPI-focused header with prominent numeric indicators</LI><LI><STRONG>Extended Header</STRONG> <STRONG>with Info Section </STRONG>- flexible layout within either kind of header, capable of accommodating various components</LI></OL><P>All headers support actions, data timestamps, visibility control, and text wrapping options. </P><H3 id="toc-hId-935975902">Default Header</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_6-1770043609378.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368158iE9A8F29F11E12CD2/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_6-1770043609378.png" alt="daniel_vladinov_6-1770043609378.png" /></span></P><P>The standard header displays general information about the card. </P><P> </P><pre class="lia-code-sample language-json"><code>"header": {
"title": "An example title",
"subtitle": "Some subtitle",
"icon": {
"src": "sap-icon://business-objects-experience",
"visible": true
},
"status": {
"text": "5 of 20"
}
}</code></pre><P> </P><H3 id="toc-hId-739462397"><BR /><SPAN>Numeric Header</SPAN></H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_7-1770043791254.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368159iA5A1EB4D0FBF729D/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_7-1770043791254.png" alt="daniel_vladinov_7-1770043791254.png" /></span></P><P>The Numeric Header is optimized for displaying KPIs, metrics, and quantitative data with prominent visualization. </P><P> </P><pre class="lia-code-sample language-json"><code>"header": {
"type": "Numeric",
"title": "Project Cloud Transformation",
"subtitle": "Revenue",
"unitOfMeasurement": "EUR",
"mainIndicator": {
"number": "44",
"unit": "%",
"trend": "Down",
"state": "Critical"
},
"details": "Some details",
"sideIndicators": [
{
"title": "Target",
"number": "17",
"unit": "%"
},
{
"title": "Deviation",
"number": "5",
"unit": "%"
}
]
}</code></pre><P> </P><H3 id="toc-hId-542948892"><BR />Extended Header with Info Section</H3><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_8-1770044248795.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368160i43FEB42446CDA075/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_8-1770044248795.png" alt="daniel_vladinov_8-1770044248795.png" /></span></P><P>Both header types can be extended with an info section for additional grouped information.</P><P>Currently, only items of type "Status" are available.</P><P> </P><pre class="lia-code-sample language-json"><code>"header": {
"title": "Project Cloud Transformation",
"subtitle": "Revenue",
"infoSection": {
"rows": [
{
"items": [
{
"type": "Status",
"value": "On Track",
"state": "Success",
"inverted": true,
"showStateIcon": true
},
{
"type": "Status",
"value": "OKR Relevant",
"state": "None",
"inverted": true,
"showStateIcon": true
},
…
]
}
]
}
}</code></pre><P> </P><H2 id="toc-hId-217352668">The Card Content Area</H2><P>The Card content area defines how data is rendered by selecting a declarative card type, each offering predefined structures and configuration options to visualize business information in a consistent and self-contained way.</P><UL><LI><STRONG>List Card</STRONG> – Displays multiple items in a vertical list with flexible layouts to present titles, descriptions, and multiple attributes per item.</LI><LI><STRONG>Table Card</STRONG> – Visualizes structured data in rows and columns with configurable formatting, alignment, grouping, and item count to support clear data comparison and summarization.</LI><LI><STRONG>Object Card</STRONG> – Presents information about a single object in configurable groups, supporting rich attribute types, visibility control, and consistent usage across SAP products.</LI><LI><STRONG>Analytical Card</STRONG> – Displays analytical business data using chart visualizations with defined dimensions to ensure accurate and meaningful insight representation.</LI><LI><STRONG>Timeline Card</STRONG> – Shows time-related information as a sequence of events arranged chronologically with clear visual flow and contextual details.</LI><LI><STRONG>Calendar Card</STRONG> – Displays agenda or timetable data for an entity with interactive date selection, semantic legends, and dynamic data updates.</LI></UL><P>Stay tuned for future articles with details for every Card type. </P><H2 id="toc-hId-20839163">The Card Footer</H2><P>The card footer, located at the bottom of the card, is used for important or routine actions that directly impact the card functionality, such as "Approve" or "Submit" actions. It serves as the designated area where users can interact with or respond to the card's content through an Actions Strip—a flexible and responsive overflow toolbar that accommodates a variety of interactive elements.<BR /><BR /></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_0-1770046814495.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/368179iE5301E8C971F711B/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_0-1770046814495.png" alt="daniel_vladinov_0-1770046814495.png" /></span></P><P>The Actions Strip supports diverse interaction patterns through three primary element types: Buttons for actionable operations, Links for navigation purposes, and Labels for displaying contextual information. Additionally, ToolbarSpacer elements provide precise control over the visual arrangement of actions, enabling left-aligned, right-aligned, or centered positioning within the footer.</P><P>Each action element can be extensively configured to match specific use cases. Buttons offer multiple visual styles (Accept, Reject, Transparent, Emphasized) and can display either text, icons, or both—with the ability to prefer icon-only display in space-constrained layouts. All elements support standard properties including tooltips, visibility conditions, accessibility attributes, and overflow behavior through <STRONG><FONT face="courier new,courier">overflowPriority</FONT></STRONG> settings (<STRONG><FONT face="courier new,courier">High, Low, NeverOverflow, AlwaysOverflow</FONT></STRONG>), which determine how actions adapt to different screen sizes.</P><P>Actions themselves can be of various types: Navigation actions for opening URLs or triggering deep links, Submit actions for posting data to backend services, Custom actions that invoke extension methods. Each action can be configured with parameters and enabled/disabled states, often bound to dynamic data for context-sensitive behavior.</P><P>Here's an example demonstrating the versatility of footer configurations:</P><P> </P><pre class="lia-code-sample language-json"><code>"footer": {
"actionsStrip": [
{
"type": "Link",
"text": "View Details",
"icon": "sap-icon://detail-view",
"actions": [
{
"type": "Navigation",
"parameters": {
"url": "{detailsUrl}"
}
}
]
},
{
"type": "ToolbarSpacer"
},
{
"text": "Approve",
"buttonType": "Accept",
"overflowPriority": "High",
"actions": [
{
"type": "Submit",
"url": "/api/approve",
"method": "POST"
}
]
},
{
"buttonType": "Transparent",
"icon": "sap-icon://email",
"preferIcon": true,
"text": "Contact",
"tooltip": "Send an email",
"actions": [
{
"type": "Navigation",
"parameters": {
"url": "mailto:{company/email}?subject={company/emailSubject}"
}
}
]
}
]
}</code></pre><P> </P><P>Beyond the Actions Strip, the footer supports additional built-in features that enhance usability. It provides built-in pagination/scrolling support through the "Show More" button, which elegantly handles content overflow by opening cards in an resizable Dialog view. When cards are displayed within dialogs, the footer automatically manages a "Close" button, streamlining the user experience without requiring explicit configuration. This is achieved by defining <STRONG><FONT face="courier new,courier">paginator</FONT> </STRONG>element in the <STRONG><FONT face="courier new,courier">footer</FONT></STRONG> declaration, which automatically will show an extra "Show More" button, next to custom-defined actions. See it in action in <A href="https://ui5.sap.com/test-resources/sap/ui/integration/demokit/cardExplorer/webapp/index.html#/explore/pagination/clientactions" target="_blank" rel="noopener noreferrer">this sample</A>. </P><pre class="lia-code-sample language-json"><code> "footer": {
"paginator": {
"totalCount": "{/itemsCount}",
"pageSize": 5
}
}</code></pre><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="daniel_vladinov_0-1770282293533.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/369120i2A77D15C9DD94F30/image-size/medium?v=v2&px=400" role="button" title="daniel_vladinov_0-1770282293533.png" alt="daniel_vladinov_0-1770282293533.png" /></span></P><P> </P><P>The footer's visibility can be controlled dynamically through binding expressions, allowing it to appear only when specific conditions are met: </P><P> </P><pre class="lia-code-sample language-json"><code>"footer": {
"visible": "{= ${/itemsCount} > 0}",
"actionsStrip": [ /* actions configuration */ ]
}</code></pre><P> </P><H1 id="toc-hId-464983022">Final Thoughts</H1><P>You can use Declarative UI Integration Cards as an entry point to an app or if you want the user to focus on a single object, topic, or group of objects.</P><P>Avoid building complex, small apps within the card that require navigation flows. Instead of being complex or multifunctional, each card is designed to serve a specific role or display business data in a clear manner.</P><P>Clarity and simplicity are prioritized in Declarative UI Integration Cards, enabling users to easily understand and integrate the cards without unnecessary complexity or ambiguity.</P><P>The architecture's three-part structure—header, content, and footer—establishes a predictable framework that accelerates development while ensuring consistency across diverse use cases. This structured approach not only simplifies the creation process but also enhances the end-user experience through familiar, intuitive patterns that work seamlessly across all SAP solutions.</P><P>By embracing the declarative paradigm, developers can rapidly compose powerful, data-driven cards without deep technical expertise, while maintaining the enterprise-grade quality and accessibility standards that users expect. The result is a flexible, scalable solution that transforms how business information is surfaced and acted upon across the digital workplace.</P><P> </P>2026-02-02T16:34:17.547000+01:00