<!
DOCTYPE html>
<head>
<title>User Input Form</title>
</head>
<body>
<div class="form-container">
<h1>User Registration Form</h1>
<form id="userForm" action="#" method="POST">
<div class="form-group">
<label for="firstName">First Name:</label>
<input type="text" id="firstName" name="firstName" required>
<label for="lastName">Last Name:</label>
<input type="text" id="lastName" name="lastName" required>
<label for="email">Email Address:</label>
<input type="email" id="email" name="email" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<label for="phone">Phone Number:</label>
<input type="tel" id="phone" name="phone">
<label for="birthdate">Date of Birth:</label>
<input type="date" id="birthdate" name="birthdate">
<label for="age">Age:</label>
<input type="number" id="age" name="age" min="1" max="120">
<label for="country">Country:</label>
<select id="country" name="country">
a. <option value="">Select a country</option>
b. <option value="usa">United States</option>
c. <option value="uk">United Kingdom</option>
</select>
</div>
<!-- Radio Buttons -->
<div class="form-group">
<label>Gender:</label>
<div class="radio-group">
<div class="radio-item">
<input type="radio" id="male" name="gender" value="male">
<label for="male">Male</label>
</div>
<div class="radio-item">
<input type="radio" id="female" name="gender" value="female">
<label for="female">Female</label>
</div>
<div class="radio-item">
<input type="radio" id="other" name="gender" value="other">
<label for="other">Other</label>
</div>
</div>
</div>
<!-- Checkboxes -->
<div class="form-group">
<label>Interests:</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="sports" name="interests" value="sports">
<label for="sports">Sports</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="music" name="interests" value="music">
<label for="music">Music</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="reading" name="interests" value="reading">
<label for="reading">Reading</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="traveling" name="interests" value="traveling">
<label for="traveling">Traveling</label>
</div>
</div>
</div>
<!-- Textarea -->
<div class="form-group">
<label for="comments">Additional Comments:</label>
<textarea id="comments" name="comments" rows="4" placeholder="Enter any additional
information..."></textarea>
</div>
<!-- Hidden Input (not visible to user) -->
<input type="hidden" id="formVersion" name="formVersion" value="1.0">
<!-- Submit and Reset Buttons -->
<button type="submit" class="submit-btn">Submit Form</button>
<button type="reset" class="reset-btn">Reset Form</button>
</form>
</div>
<script>
// Form submission handler
document.getElementById('userForm').addEventListener('submit', function(e) {
e.preventDefault();
// Collect form data
const formData = new FormData(this);
const data = {};
// Convert FormData to regular object
for (let [key, value] of formData.entries()) {
if (data[key]) {
// Handle multiple values (like checkboxes)
if (Array.isArray(data[key])) {
data[key].push(value);
} else {
data[key] = [data[key], value];
}
} else {
data[key] = value;
}
}
// Display collected data
console.log('Form Data:', data);
alert('Form submitted successfully! Check the console for submitted data.');
});
// Form reset handler
document.getElementById('userForm').addEventListener('reset', function() {
setTimeout(() => {
alert('Form has been reset!');
}, 100);
});
</script>
</body>
</html>
SAP UI5 is a JavaScript-based framework to build web applications.
SAP Fiori is a design language (UI/UX standard) used by SAP to make apps easy, modern, and user-friendly.
So, when you open a clean SAP screen in a browser or mobile app, that’s usually a Fiori app built using SAP UI5.
Project Name should not start with the Capital letter
npm install --global @ui5/cli
Installs the UI5 CLI tool globally on your system.
--global (or -g) makes it available anywhere in the terminal, not just in one project.
Initializes a new UI5 project
ui5 init
ProjectAppRootFolderWebApp
AppRootFolder main folder that will hold all the source code and config files of your SAP UI5 app.
1. Step1: Create a folder on your local machine which will contain all the sources of the app we're going to build.
We'll refer to this folder as the "app root folder".
2. Create a new file called package.json which will enable you to execute commands and consume packages from
the npm registry via the npm command line interface.
Package.json
{
"name": "practice.ui5", // name of your project ,It's used internally by tools like npm and UI5 Tooling.
"version": "1.0.0", // version of your project, Useful for version control and dependency tracking.
"description": "Practice SAP UI5 Fiori App", // A short summary of your app/project.
"author": "Parameswara Rao Byreddy", // Project ownership and legal info
"license": "MIT",
"keywords": ["sapui5", "fiori", "ui5", "openui5", "tutorial"],
"scripts": { // Shortcuts to run UI5 commands
"start": "ui5 serve -o index.html", // Runs the app in browser
"build": "ui5 build --all", // Builds optimized version
"watch": "ui5 serve --open index.html --watch" // Auto-reloads browser on file changes
},
"devDependencies": { // Tools like @ui5/cli installed for development
"@ui5/cli": "^3.1.0"
}
}
Create a new HTML file named index.html in your webapp folder and enter the following content:
the index.html file. This is the starting point for loading the UI5 framework and initializing your application.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Quickstart Tutorial</title>
<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-libs="sap.m"
data-sap-ui-compat-version="edge"
data-sap-ui-async="true"
data-sap-ui-on-init="module:ui5/quickstart/index"
data-sap-ui-resource-roots='{
"ui5.quickstart": "./"
}'>
</script>
</head>
<body class="sapUiBody" id="content"></body>
</html>
<script> loads the SAP UI5 framework and bootstraps your application
src="resources/sap-ui-core.js"
Loads the main SAP UI5 framework from the resources/ folder.
This file contains all core functionalities of UI5 (controls, libraries, etc.).
This is the entry point to the whole SAPUI5 world!
data-sap-ui-libs="sap.m"
This tells UI5:
"Load the Mobile UI5 library (sap.m) that contains ready-made controls like Button, Input, Dialog, etc."
You can also add more here like sap.ui.layout, sap.f if needed.
data-sap-ui-compat-version="edge"
Use the latest stable UI5 features.
"edge" ensures you always get the most recent improvements.
data-sap-ui-async="true"
Loads UI5 asynchronously, meaning:
App loads faster
Better performance
data-sap-ui-on-init="module:ui5/quickstart/index"
Once UI5 is ready, it will run this module:
ui5/quickstart/index.js
This file will contain your app startup logic, like launching the main view
data-sap-ui-resource-roots='{ "ui5.quickstart": "./" }'
This maps your project's namespace to a folder.
So if you write:
module:ui5/quickstart/index → it means ./ui5/quickstart/index.js
🔗 It's like telling UI5:
"When I say ui5.quickstart, I actually mean this folder."
<body class="sapUiBody" id="content"></body>
<body> is where the UI is rendered.
class="sapUiBody" is a UI5 required class for styling and layout.
id="content" is usually the target container where your app will be injected.
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
about controls in sap ui5
ChatGPT said:
In SAP UI5, controls are the building blocks of the user interface — like buttons, tables, input fields,
checkboxes, dropdowns, etc. SAP UI5 provides a rich library of predefined controls which you can directly
use in your apps to create enterprise-ready UIs.
🔹 What are Controls in SAP UI5?
A control is a UI element that:
Renders something on the screen (like text, input box, button).
Has properties (e.g., text, enabled, visible).
Has events (e.g., press, change, liveChange).
Has methods (e.g., setText(), getValue()).
Can be bound to a model (for dynamic data).
🔹 Categories of Controls
SAP UI5 controls are organized into libraries like:
Library Description
sap.m Mobile controls (used in Fiori apps, responsive) ✅
sap.ui.commons Older desktop controls (not recommended for new apps) ❌
Library Description
sap.ui.layout Layout and structure controls
sap.ui.table Advanced table controls (not responsive)
sap.f Flexible column layouts, dynamic pages
🔹 Commonly Used Controls (from sap.m Library)
Control Description Example
Button Clickable action button 🔘
Label Text label for input or information
Input User text input field ✍️
Text Display read-only text 📄
List List of items (mobile-style) 📋
Table Grid-style table for rows and columns 📊
Select Dropdown box 🔽
DatePicker Select a date 📅
CheckBox Boolean input ☑️
RadioButton Choose one from many 🔘 Option
🔹 Example: Simple Input + Button in XML View
xml
CopyEdit
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
controllerName="myApp.controller.Main">
<VBox class="sapUiSmallMargin">
<Label text="Enter your name:" />
<Input id="nameInput" placeholder="Your name" />
<Button text="Say Hello" press="onPress" />
<Text id="outputText" />
</VBox>
</mvc:View>
🔹 Example: Event Handling in Controller
js
CopyEdit
onPress: function () {
var name = this.byId("nameInput").getValue();
this.byId("outputText").setText("Hello, " + name + "!");
}
🔹 How to Use Controls
You can use controls in:
XML Views (preferred for Fiori apps)
JavaScript Views
HTML Views
Each control has:
id: unique identifier
properties: like text, value, enabled
aggregations: child controls (e.g., items in a list)
events: like press, change, etc.
🔹 SAP UI5 Control Lifecycle
1. Initialization (init)
2. Rendering (onAfterRendering)
3. Event Handling
4. Destruction (exit)
🔹 How to Find All Controls
Check the UI5 Explored tool:
📌 UI5 SDK - Demo Kit (Explored)
It shows:
All available controls
Code samples (XML + JS)
Properties, events, methods
Live preview
🔹 Summary
Feature Purpose
Controls UI building blocks
sap.m.* Modern responsive Fiori controls
Used in XML Views, JS Views, Fragments
Bindable Yes (via models)
Events Enable user interaction
📁 Files Involved
1. webapp/index.html – This is the HTML shell (the entry point).
2. webapp/index.js – This is the JavaScript logic that tells SAPUI5 what to do (create and place
controls).
🔹 Step-by-Step Breakdown
✅ index.html
html
CopyEdit
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>UI5 Walkthrough</title>
<!DOCTYPE html>: Declares this as an HTML5 document.
<meta charset="utf-8">: Ensures your page supports all languages (text encoding).
<title>: Sets the title in the browser tab.
✅ SAPUI5 Bootstrap Script
html
CopyEdit
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_horizon"
data-sap-ui-libs="sap.m"
data-sap-ui-compat-version="edge"
data-sap-ui-async="true"
data-sap-ui-on-init="module:ui5/walkthrough/index"
data-sap-ui-resource-roots='{
"ui5.walkthrough": "./"
}'>
</script>
This is the heart of SAPUI5 setup. It loads the UI5 framework and configures it.
Here's what each attribute does:
Attribute Meaning
Loads the core SAP UI5 JavaScript file. Like loading a big
src="resources/sap-ui-core.js"
engine.
data-sap-ui-theme="sap_horizon" Applies a modern theme (colors, font, styling).
Attribute Meaning
Loads the sap.m library — modern, mobile-friendly UI
data-sap-ui-libs="sap.m"
controls like Button, Input, Text.
data-sap-ui-compat-version="edge" Makes sure the latest rendering behavior is used.
data-sap-ui-async="true" Loads SAPUI5 asynchronously (faster).
data-sap-ui-on-init="module:ui5/walkthrough/ After everything loads, this line tells UI5 to run your app logic
index" from index.js.
data-sap-ui-resource-roots='{ "ui5.walkthrough": Maps the namespace ui5.walkthrough to the local folder
"./" }' webapp.
✅ Body Section
html
CopyEdit
<body class="sapUiBody" id="content">
</body>
class="sapUiBody": Adds some default styles from the theme (like fonts, background).
id="content": Acts like a container or placeholder for your UI5 control to be inserted later.
✅ index.js – App Logic
javascript
CopyEdit
sap.ui.define([
"sap/m/Text"
], (Text) => {
"use strict";
new Text({
text: "Hello World"
}).placeAt("content");
});
Let’s explain this line by line:
🔹 sap.ui.define([...], function...)
This is how you define a module in SAPUI5.
You are saying: "I want to use the control Text from the sap.m library."
🔹 new Text({ text: "Hello World" })
You are creating a Text control (just like a label or <div> that shows text).
It's like saying: “Create a UI element of type Text and set its content to 'Hello World'.”
🔹 .placeAt("content")
You are telling UI5:
"Take the control I just created, and place it into the HTML element with id="content" (which is the
<body> tag)."
✅ Output
This whole setup creates this:
👉 A simple SAPUI5 app that shows:
nginx
CopyEdit
Hello World
on the screen — not using traditional HTML, but a UI5 control, which gives you more power (like styling,
events, i18n, etc.).
🔹 Important Notes
Do not use native <div>Hello World</div> in the HTML. Let UI5 generate and insert all UI elements.
All controls like Text, Button, Input, etc., come from UI5 libraries like sap.m.
✅ Summary for Beginners
Concept Meaning
Control A building block of the UI (like Text, Button).
sap.ui.define SAP’s way of defining modules with dependencies.
Concept Meaning
placeAt("content") Tells SAPUI5 where to insert the control on the web page.
UI5 Library (sap.m) A collection of modern, mobile-friendly UI controls.
index.html Loads SAPUI5 and starts the app.
index.js Your app’s JavaScript logic that defines what UI to create.
🧠 Why Move to XML Views?
Putting everything (UI + logic) in index.js works for very simple apps. But:
As your app grows, the file becomes messy.
It's hard to reuse, test, and maintain.
Following the MVC pattern (Model–View–Controller) separates:
o View – What the user sees (XML)
o Controller – What the app does (JavaScript logic)
o Model – Where the data comes from (like JSON, OData)
✅ Step-by-Step Explanation of the New Setup
📁 File Structure
pgsql
CopyEdit
webapp/
├── index.html
├── index.js
└── view/
└── App.view.xml
✅ App.view.xml – UI goes here now
xml
CopyEdit
<mvc:View
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Text text="Hello World"/>
</mvc:View>
Explanation:
<mvc:View> is the root tag for XML Views.
xmlns="sap.m" – This means we’re using controls like Text, Button from the sap.m library.
xmlns:mvc="sap.ui.core.mvc" – This is used to define that this file is a View in the MVC model.
<Text text="Hello World"/> – This creates a Text control with the text "Hello World", same as before
— but now done declaratively (cleaner).
✅ index.js – Load the View instead of creating UI manually
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/XMLView"
], (XMLView) => {
"use strict";
XMLView.create({
viewName: "ui5.walkthrough.view.App"
}).then((oView) => oView.placeAt("content"));
});
Explanation:
Line What It Does
sap.ui.define([...]) UI5's way to define dependencies. Here, we use XMLView.
XMLView.create({ viewName:
Load the XML View called App.view.xml inside the view folder.
"ui5.walkthrough.view.App" })
Line What It Does
This maps to webapp/view/App.view.xml. The folder path =
"ui5.walkthrough.view.App"
namespace.
Once the view is loaded, place it inside the HTML element with
.then((oView) => oView.placeAt("content"))
id="content" (<body> in index.html).
✅ index.html – No changes needed from previous step
Still looks like:
html
CopyEdit
<body class="sapUiBody" id="content"></body>
📌 Important Concept: Namespace
Your files are in folders like webapp/view/App.view.xml.
So your view name becomes: "ui5.walkthrough.view.App".
This means:
"ui5.walkthrough" namespace maps to webapp/ folder.
That’s why in index.html you wrote:
html
CopyEdit
data-sap-ui-resource-roots='{
"ui5.walkthrough": "./"
}'
It tells SAPUI5: Whenever I say ui5.walkthrough, look in the webapp folder.
✅ What Changed from Before?
Before After
UI created in JavaScript UI created in XML
new Text({...}).placeAt() <Text text="Hello World"/>
Everything in index.js Cleanly separated into view/App.view.xml
Not scalable Easy to maintain and extend
✅ Benefits of XML View
Easy to read and write
Keeps UI separate from logic
Follows best practices
Reusable and testable
🎯 Objective of This Step:
Replace the <Text> with a <Button>.
When the user clicks the button, it should show an alert saying:
“Hello World”
This logic will be written in a separate file called controller (good coding practice).
✅ Updated File Structure
pgsql
CopyEdit
webapp/
├── index.html
├── index.js
├── view/
│ └── App.view.xml
└── controller/
└── App.controller.js ← (New)
🧾 File 1: App.view.xml (UI Layout)
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Button
text="Say Hello"
press=".onShowHello"/>
</mvc:View>
🔍 Explanation:
Line Meaning
controllerName="ui5.walkthrough.controller.App" Links the XML View to a Controller file (App.controller.js)
Shows a Button. When clicked, it will call the onShowHello
<Button text="Say Hello" press=".onShowHello"/>
function inside the controller
The dot in .onShowHello means: call the function from the controller attached to this view.
🧾 File 2: App.controller.js (New – Controller Logic)
javascript
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller"
], (Controller) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {
onShowHello() {
// Show a browser alert when button is clicked
alert("Hello World");
}
});
});
🔍 Explanation:
Line Meaning
"sap/ui/core/mvc/Controller" We are using the SAPUI5 base controller
Controller.extend(...) Creates a new controller named "ui5.walkthrough.controller.App"
onShowHello() A custom function we wrote – will be called when button is pressed
alert("Hello World") This pops up a standard browser alert box
💡 Naming Conventions – Must Follow
Item Convention
Controller file Same name as view, ends with .controller.js
Function names Start with on, like onShowHello
Folder name controller/ folder stores your JS controller files
ControllerName attribute Fully-qualified namespace (ui5.walkthrough.controller.App)
✅ Final Output
Your app now:
Loads a button on screen labeled "Say Hello"
When you click it, it shows an alert:
"Hello World"
And now, the code is cleanly separated into:
File Role
App.view.xml UI (What user sees)
App.controller.js Logic (What happens when user interacts)
✅ Summary
Concept Explanation
Controller JavaScript file where you write the logic
Concept Explanation
View XML file that defines UI layout
Button press Triggered using press=".onShowHello"
Separation of Concerns View handles layout, controller handles actions
SAPUI5 MessageToast, which looks much better and integrates with the Fiori look and feel.
Let’s walk through this step-by-step clearly and simply, especially for beginners.
🎯 Goal of This Step
Previously, we showed a message using:
js
CopyEdit
alert("Hello World");
Now, we will use the SAPUI5 control MessageToast.show("Hello World") to display a non-blocking
message popup that fades away automatically, like this:
✅ Hello World (toast message at the bottom of the screen)
✅ Updated File: App.controller.js
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], (Controller, MessageToast) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {
onShowHello() {
MessageToast.show("Hello World");
}
});
});
🧠 What’s Happening Here? (Line-by-Line Explanation)
🔹 sap.ui.define([...], function...)
SAPUI5 uses something called AMD (Asynchronous Module Definition).
This is how you load other JavaScript resources before running your code.
js
CopyEdit
[
"sap/ui/core/mvc/Controller", // For controller logic
"sap/m/MessageToast" // For showing toast messages
]
These are the fully qualified module paths.
🔹 function(Controller, MessageToast)
You give names to the modules you imported — notice:
Module Name Variable
"sap/ui/core/mvc/Controller" Controller
"sap/m/MessageToast" MessageToast
You name them exactly after the last part of the path (Controller, MessageToast) — it's a SAPUI5 convention.
🔹 Controller.extend(...)
This is how SAPUI5 creates your controller object.
js
CopyEdit
return Controller.extend("ui5.walkthrough.controller.App", {
The string "ui5.walkthrough.controller.App" must match the controllerName in your view:
xml
CopyEdit
controllerName="ui5.walkthrough.controller.App"
🔹 onShowHello()
This is your event handler function.
js
CopyEdit
onShowHello() {
MessageToast.show("Hello World");
}
Here’s what it does:
Calls the MessageToast.show() function.
Displays a temporary non-intrusive message like:
✅ Hello World
This is nicer than alert() because:
It doesn't block the UI.
It auto-disappears.
It's styled like a Fiori message.
📌 Differences Between alert() and MessageToast.show()
Feature alert() MessageToast.show()
Native ✅ Yes ❌ No (SAPUI5)
Looks nice? ❌ No ✅ Yes
Blocks UI? ✅ Yes ❌ No
Auto disappears? ❌ No ✅ Yes
Used in real SAP apps? ❌ No ✅ Yes
✅ Best Practices & Conventions
SAPUI5 Term When to Use
sap.ui.define() For all JS modules (controllers, components, utilities). Declares a named module.
sap.ui.require() For loading modules only when needed (on-demand), no name defined.
🧠 Why Use sap.ui.define()?
It helps SAPUI5:
o Load only the files needed
o Understand dependencies clearly
o Avoid global variables (helps large apps)
Improves performance and maintainability
✅ Summary
Concept Meaning
MessageToast A lightweight, non-blocking toast popup message (from sap.m)
sap.ui.define Way to load modules before using them (required in UI5)
AMD pattern Keeps modules clean, efficient, and fast
Modular code Separates responsibilities → better performance and clean structure
Introducing the JSON Model and Data Binding — the M in MVC. This is where SAPUI5 really starts to
shine: you can bind your UI controls to data automatically, so the app updates live as users interact.
Let’s walk through this step like you're learning it for the first time.
🎯 Goal of This Step
Introduce a JSONModel (holds the data).
Bind that data to the Input field's value and description.
As the user types, the description updates automatically!
✅ File 1: App.controller.js (Updated)
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel"
], (Controller, MessageToast, JSONModel) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {
// This function runs automatically when the app starts
onInit() {
// Define initial data
const oData = {
recipient: {
name: "World"
}
};
// Create a JSON model with the data
const oModel = new JSONModel(oData);
// Attach the model to the view
this.getView().setModel(oModel);
},
// Button click event
onShowHello() {
MessageToast.show("Hello World");
}
});
});
🔍 What’s Happening Here?
🔹 1. onInit() – Initialization Function
SAPUI5 automatically calls this when the controller is created.
🔹 2. oData – Your Sample Data
This object:
js
CopyEdit
const oData = {
recipient: {
name: "World"
}
};
Represents:
bash
CopyEdit
/recipient/name → "World"
🔹 3. JSONModel
This turns your plain JS object into a SAPUI5 data model:
js
CopyEdit
const oModel = new JSONModel(oData);
🔹 4. setModel()
Attaches this model to the View, so all UI elements in the XML can bind to it:
js
CopyEdit
this.getView().setModel(oModel);
✅ File 2: App.view.xml (Updated with Input + Binding)
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Button
text="Say Hello"
press=".onShowHello"/>
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
</mvc:View>
🔍 What’s Happening Here?
🔹 <Input> Control
This is a UI element where users can type text.
Key Properties:
Property Description
value="{/recipient/name}" Binds input field's value to /recipient/name from model
description="Hello {/recipient/name}" Binds the description (suffix text) of the input
valueLiveUpdate="true" Updates binding in real time as user types
width="60%" Makes the input field 60% of the available width
🎯 Data Binding in SAPUI5 (Super Simple)
🔹 What is it?
Data Binding means:
UI elements (like Input) are connected to data in the background.
🔹 Example Binding:
xml
CopyEdit
<Input value="{/recipient/name}" />
Means:
→ This Input shows the value from the data model path /recipient/name
→ If the value in the model changes, the Input updates.
→ If the user types something new, the model updates. (two-way binding)
Output Preview
What the user sees:
css
CopyEdit
[ John ] Hello John
↑ Input box ↑ Dynamic description
As they type into the input field, the description next to it updates instantly.
✅ Summary of Step 7
Concept Description
JSONModel A JavaScript-based data model used in UI5
onInit() A lifecycle hook that sets up the model at app start
value="{/recipient/name}" Data binding from model to control
description="Hello {/recipient/name}" Live text update based on model
valueLiveUpdate="true" Ensures binding updates as user types
💡 Tips for Beginners
Data models in SAPUI5 can be JSON, OData, or XML.
In most apps, JSONModel is used for local or temporary data.
Always bind your UI using {path} or {model>path} — don’t hardcode.
Translatable Texts (i18n) in your SAPUI5 learning journey.
This step teaches you how to make your app multilingual by using resource files and the i18n model.
🎯 Goal of This Step:
Move UI texts like "Say Hello" and "Hello John" to a central file (i18n.properties).
Make the app translatable into different languages (e.g., English, German, Hindi).
Use placeholders like {0} in your message strings.
Load these texts dynamically using SAPUI5’s ResourceModel.
✅ File 1: webapp/i18n/i18n.properties (New File)
properties
CopyEdit
showHelloButtonText=Say Hello
helloMsg=Hello {0}
🔍 Explanation:
Key Value Meaning
showHelloButtonText Say Hello Text shown on the button
Message toast shown after click. {0} will be replaced with the name from the
helloMsg Hello {0}
model.
✅ You can add more languages later by creating:
i18n_de.properties (German)
i18n_fr.properties (French)
etc.
SAPUI5 automatically picks the correct one based on browser language.
✅ File 2: App.controller.js (Updated)
javascript
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], (Controller, MessageToast, JSONModel, ResourceModel) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {
onInit() {
// 1. Set up recipient model
const oData = {
recipient: {
name: "World"
}
};
const oModel = new JSONModel(oData);
this.getView().setModel(oModel);
// 2. Set up i18n model
const i18nModel = new ResourceModel({
bundleName: "ui5.walkthrough.i18n.i18n"
});
this.getView().setModel(i18nModel, "i18n");
},
onShowHello() {
// Get resource bundle from i18n model
const oBundle = this.getView().getModel("i18n").getResourceBundle();
// Get name entered in input
const sRecipient = this.getView().getModel().getProperty("/recipient/name");
// Get message with placeholder replaced
const sMsg = oBundle.getText("helloMsg", [sRecipient]);
// Show the message
MessageToast.show(sMsg);
}
});
});
🔍 What's New Here?
Part Meaning
ResourceModel Loads texts from i18n.properties
"ui5.walkthrough.i18n.i18n" → Maps to
bundleName
webapp/i18n/i18n.properties
this.getView().setModel(i18nModel, "i18n") Adds this as a named model called "i18n"
oBundle.getText("helloMsg", [sRecipient]) Replaces {0} with the name typed in input
✅ File 3: App.view.xml (Updated)
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello"/>
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
</mvc:View>
🔍 Explanation:
Property What it does
text="{i18n>showHelloButtonText}" Binds button label to key from i18n file
description="Hello {/recipient/name}" Shows dynamic description using binding
❗Note:
description is still hardcoded here as "Hello {/recipient/name}".
To fully internationalize it, we’d do this logic in controller or use a formatter (e.g., formatMessage from
sap/base/strings/formatMessage).
🧠 Key Concepts You Learned:
Concept Description
i18n (internationalization) Making your app text translatable
i18n.properties File that stores translatable strings
{0}, {1}... Placeholders to be replaced dynamically
ResourceModel Model used to manage i18n bundles
Named model i18n model used with {i18n>key} syntax
getText("key", [value]) Retrieves message from resource bundle with placeholders replaced
✅ Example Output at Runtime:
When user types Ravi in input and clicks button:
txt
CopyEdit
Button: Say Hello
Input: [Ravi] Description: Hello Ravi
Toast: Hello Ravi
Everything updates dynamically based on your input and translation file.
👣 What’s Next?
Next steps in the walkthrough will usually include:
Creating formatters (for smart descriptions).
Loading data from external JSON files.
Connecting to OData services.
Adding multiple views and routing.
Would you like to continue to the next step (e.g. formatters or external model file), or would you prefer a
quick recap of everything learned so far?
Let me know — I'm here to help you master SAPUI5 step by step.
Great question! 🎉 You're implementing internationalization (i18n) in your SAPUI5 app — and now want to
provide German translations via i18n_de.properties. That’s exactly how it's done in real-world SAP apps!
✅ What to Place in i18n_de.properties
Here’s a German version of your existing default i18n.properties file:
📄 webapp/i18n/i18n_de.properties
properties
CopyEdit
showHelloButtonText=Sag Hallo
helloMsg=Hallo {0}
💬 Meaning of Each Entry
Key English (i18n.properties) German (i18n_de.properties)
showHelloButtonText Say Hello Sag Hallo
helloMsg Hello {0} Hallo {0}
So if your user types Stefan, the output will be:
👇 Toast Message
Hallo Stefan
🌐 How to Test It
1. In Chrome or any browser:
o Go to Settings → Languages
o Set German (Deutsch) as the preferred language
o Restart your browser
2. Reload your SAPUI5 app
3. SAPUI5 will now try to load:
o i18n_de.properties ✅
o If not found, fallback to: i18n.properties (default)
✅ Pro Tip
You can also add:
js
CopyEdit
console.log(navigator.language);
in your index.js to check what language the browser is sending.
For German, it will be something like de or de-DE.
❗Don’t Forget:
File name must be exactly i18n_de.properties
It must be in the same i18n folder as your default file
You don’t need to change any code — SAPUI5 handles locale selection automatically 🎉
Component Configuration in your SAPUI5 walkthrough — this is a major milestone where your app becomes
modular, scalable, and ready for Fiori Launchpad or other advanced environments. Let's break it down
simply for you.
🧩 What is a Component in SAPUI5?
Think of a Component.js as the entry point and blueprint of your SAPUI5 app — kind of like the brain:
It initializes your app
Loads views, models, and resources
Can be reused in other apps or embedded in larger systems like Fiori Launchpad
🎯 Why Are We Doing This?
Before, everything was in index.js or App.controller.js.
Now, we move that logic into Component.js so that:
✅ Your app becomes modular and launchpad-ready
✅ Models are set globally, not per view
✅ Views are automatically loaded by metadata
📂 Final Folder Structure
pgsql
CopyEdit
webapp/
│
├── Component.js <-- Main app definition (NEW)
├── index.html <-- Entry point
├── index.js <-- Bootstraps the Component
├── controller/
│ └── App.controller.js
├── view/
│ └── App.view.xml
└── i18n/
└── i18n.properties
✅ Step-by-Step Code Breakdown
🔹 webapp/Component.js
js
CopyEdit
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], (UIComponent, JSONModel, ResourceModel) => {
"use strict";
return UIComponent.extend("ui5.walkthrough.Component", {
metadata : {
interfaces: ["sap.ui.core.IAsyncContentCreation"],
rootView: {
viewName: "ui5.walkthrough.view.App",
type: "XML",
id: "app"
}
},
init() {
// Call the parent's init function
UIComponent.prototype.init.apply(this, arguments);
// Set up data model (M in MVC)
const oData = {
recipient: { name: "World" }
};
const oModel = new JSONModel(oData);
this.setModel(oModel); // available to all views
// Set up i18n model
const i18nModel = new ResourceModel({
bundleName: "ui5.walkthrough.i18n.i18n"
});
this.setModel(i18nModel, "i18n");
}
});
});
🔍 Explanation:
Section Meaning
UIComponent.extend(...) Creates a custom SAPUI5 component
metadata.rootView The root view that loads automatically
init() Called automatically; we initialize models here
this.setModel(...) Sets model on component level, so all nested views get access
🔹 webapp/controller/App.controller.js (Simplified)
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], (Controller, MessageToast) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {
onShowHello() {
const oBundle = this.getView().getModel("i18n").getResourceBundle();
const sRecipient = this.getView().getModel().getProperty("/recipient/name");
const sMsg = oBundle.getText("helloMsg", [sRecipient]);
MessageToast.show(sMsg);
}
});
});
✅ We removed onInit() from here. Models are now set up globally in Component.js.
🔹 webapp/index.js
js
CopyEdit
sap.ui.define([
"sap/ui/core/ComponentContainer"
], (ComponentContainer) => {
"use strict";
new ComponentContainer({
name: "ui5.walkthrough", // namespace of Component.js
settings: {
id: "walkthrough"
},
async: true
}).placeAt("content");
});
Part Meaning
ComponentContainer A wrapper to load the app's component
name: "ui5.walkthrough" Matches your app's namespace
placeAt("content") Inserts app into <body id="content"> from index.html
✅ Bonus: index.html Reminder
html
CopyEdit
<body class="sapUiBody" id="content"></body>
SAPUI5 now inserts the full app dynamically into this placeholder using the ComponentContainer.
🧠 Concept Recap
Concept Purpose
Component.js Entry point that defines views, models, resources
metadata.rootView Automatically loads the main view
setModel() on component Makes models available everywhere
index.js Boots up Component.js via ComponentContainer
App.controller.js Only contains logic now — no model init needed
🧠 What Is manifest.json?
It’s the heart of your SAPUI5 application configuration.
Instead of writing code to initialize:
the root view
the i18n model
library dependencies
device support
…you now move that logic into a clean, structured file called manifest.json.
This is especially useful for:
SAP Fiori Launchpad deployment ✅
Clean project structure ✅
Easy translation/localization ✅
No hardcoding in Component.js ✅
📁 Folder Structure Update
pgsql
CopyEdit
webapp/
├── manifest.json ✅ NEW
├── Component.js ⬅ Simplified now
├── index.html ✅ Updated for descriptor usage
├── controller/
│ └── App.controller.js
├── view/
│ └── App.view.xml
├── i18n/
│ └── i18n.properties ✅ Includes app title/description
📄 manifest.json Explained
json
CopyEdit
{
"_version": "1.65.0",
"sap.app": {
"id": "ui5.walkthrough",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"type": "application",
"applicationVersion": {
"version": "1.0.0"
}
},
"sap.ui": {
"technology": "UI5",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"dependencies": {
"minUI5Version": "1.108.0",
"libs": {
"sap.ui.core": {},
"sap.m": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "ui5.walkthrough.i18n.i18n",
"supportedLocales": [""],
"fallbackLocale": ""
}
}
},
"rootView": {
"viewName": "ui5.walkthrough.view.App",
"type": "XML",
"id": "app"
}
}
}
🔍 Breakdown of Sections
Section Purpose
sap.app App-level metadata: ID, i18n path, title, description
sap.ui UI tech and device support (responsive apps)
sap.ui5 SAPUI5-specific setup: dependencies, models, root view
🌍 i18n Text File Update
📄 i18n/i18n.properties
properties
CopyEdit
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts of SAPUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
Now, manifest.json picks appTitle and appDescription from here.
🧠 How is this used in SAP Fiori Launchpad?
Fiori Launchpad reads the manifest:
Displays appTitle and appDescription
Loads i18n automatically
Instantiates the component (no need for index.js)
Loads the correct view and settings automatically
✅ Component.js – Simplified!
js
CopyEdit
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel"
], (UIComponent, JSONModel) => {
"use strict";
return UIComponent.extend("ui5.walkthrough.Component", {
metadata: {
interfaces: ["sap.ui.core.IAsyncContentCreation"],
manifest: "json"
},
init() {
// Call parent init
UIComponent.prototype.init.apply(this, arguments);
// Set JSON model
const oData = {
recipient: { name: "World" }
};
const oModel = new JSONModel(oData);
this.setModel(oModel);
}
});
});
🚫 No need to manually define i18n model — that’s now in manifest.json.
🔄 index.html – Final Structure
html
CopyEdit
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>UI5 Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_horizon"
data-sap-ui-compat-version="edge"
data-sap-ui-async="true"
data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"
data-sap-ui-resource-roots='{
"ui5.walkthrough": "./"
}'>
</script>
</head>
<body class="sapUiBody" id="content">
<div
data-sap-ui-component
data-name="ui5.walkthrough"
data-id="container"
data-settings='{"id": "walkthrough"}'>
</div>
</body>
</html>
🔍 What’s happening here?
Enables ComponentSupport to automatically load the component
No need for index.js anymore ✅
It uses manifest.json to know what to load
📌 Summary
Change Benefit
Added manifest.json Clean config in one place
Moved i18n model config Automatic loading
Replaced index.js with div+ComponentSupport More like Fiori apps
App becomes reusable, modular, launchpad-ready ✅
✅ Conventions to Remember
Rule Example
manifest.json always lives in webapp/ webapp/manifest.json
App title & description use handlebars {{}} {{appTitle}}
All models should be listed under sap.ui5.models "i18n": { ... }
Component uses manifest: "json" In Component.js
Component root view in manifest.json No need in code anymore
Pages and Panels — and this step is all about making your SAPUI5 app look more like a real UI5/Fiori
application. Until now, you only used simple controls directly inside a view. Now you're organizing them into
a Page and wrapping everything inside an App control.
🧠 What You'll Learn in This Step
How to structure your app's layout using App, Page, and Panel.
What aggregations are in UI5.
How to group controls visually for a cleaner user interface.
🧩 Final Output
You'll see:
A page title on top
A panel grouping the input and button
Everything in a full-screen layout, ready for navigation in future steps
✅ Final App.view.xml
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App>
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<Panel headerText="{i18n>helloPanelTitle}">
<content>
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello"/>
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</mvc:View>
📄 i18n/i18n.properties Additions
properties
CopyEdit
homePageTitle=Walkthrough
helloPanelTitle=Hello World
You’re referencing these with {i18n>homePageTitle} and {i18n>helloPanelTitle} in the XML.
📦 What are These New Controls?
Control Purpose
App Top-level container for mobile apps. Manages navigation between Pages.
Page Represents one screen. Has a header (title) and content aggregation.
Panel A grouping container for related UI elements. Often used for grouping forms or buttons.
📌 Why displayBlock="true" in the View?
It ensures the view takes full height and blocks the display, which is important for mobile-friendly apps.
Without it, your content might shrink or not fill the screen properly.
💡 What Are Aggregations?
SAPUI5 uses aggregations instead of standard HTML nesting. This means that controls like Page, App, and
Panel define how many and which child controls they can hold.
For example:
<Page> has a content aggregation — you place controls inside that.
<App> has a pages aggregation — you add <Page>s there.
<Panel> has its own content aggregation.
Think of it like saying:
📦 App
└── 📄 Page
└── 📍 Panel
└── 🔘 Button, 🧾 Input (inside Panel’s content)
✅ Summary
Feature Benefit
App + Page Fiori-compliant screen layout
Panel Groups related UI elements
displayBlock=true Full height view layout
i18n keys for titles Fully translatable UI
Shell Control as Container – and this step is all about making your SAPUI5 app look visually optimized for
different screen sizes, especially desktop and mobile.
Let me explain this clearly and simply for a beginner:
🧠 What Is the Shell Control?
sap.m.Shell is a top-level container control.
It adds padding/margins ("letterboxing") on large screens so your app looks clean and focused.
It makes your app look centered on desktops and still full-screen on mobile/tablets.
It is optional if you're deploying inside the SAP Fiori Launchpad, which already uses a shell.
✅ Updated XML View
Here’s your complete App.view.xml now:
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<Shell>
<App>
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<Panel headerText="{i18n>helloPanelTitle}">
<content>
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello" />
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%" />
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
🎯 Why Add a <Shell>?
Without Shell With Shell
App stretches full width on large screens App is centered and has margins on large screens
Might look too wide on desktop Looks polished and consistent across devices
No device-specific adaptation Automatically handles screen size
📦 Structure of Your View (Visual Hierarchy)
plaintext
CopyEdit
Shell
└── App
└── Page
└── Panel
└── Button + Input
Each control nests inside its parent through aggregations.
🔍 A Note for Fiori Launchpad Apps
If your app will run inside the SAP Fiori Launchpad, do NOT include the <Shell> control.
Why?
👉 Because the Launchpad already provides a Shell. Adding your own would create a "Shell inside a Shell"
problem.
🧪 Optional: How to Customize Shell (advanced)
If you're building standalone apps and want to customize:
xml
CopyEdit
<Shell
showLogout="false"
showSearch="false"
backgroundColor="blue"
icon="img/logo.png"
appTitle="My UI5 App">
But for now, the default <Shell> is enough.
🧠 Summary
Feature Why It's Used
<Shell> Adds outer layout for responsive design (esp. desktop)
<App> Top-level container that holds pages
<Page> Represents a single screen
<Panel> Groups related controls
Button/Input Functional UI controls
🧠 Why Do We Need Margins and Padding?
Without spacing:
The controls (Button, Input, Text) are stuck to the edges.
It looks cluttered and unprofessional, especially on desktops.
With proper spacing:
The UI feels visually balanced.
It works well on all devices (phones, tablets, desktops).
You don’t need to write custom CSS — just use SAPUI5 built-in CSS classes.
✅ Updated App.view.xml with Margins
Here's your updated code with comments for each change:
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<Shell>
<App>
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<!-- Add margin around panel -->
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto">
<content>
<!-- Small space to the right of button -->
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello"
class="sapUiSmallMarginEnd" />
<!-- Input field -->
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%" />
<!-- New text control instead of description -->
<Text
text="Hello {/recipient/name}"
class="sapUiSmallMargin" />
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
🧱 What These CSS Classes Do:
Class Use
sapUiResponsiveMargin Adds auto-adjusting margins based on device (Panel here)
sapUiSmallMarginEnd Adds small space after (right of) the button
sapUiSmallMargin Adds small margin around the Text control
✅ These are built-in SAPUI5 CSS classes — no need to create styles manually.
📐 Layout Result Summary
Panel is centered and not sticking to edges.
Button has a gap before the Input.
Text is below with clean spacing.
Everything is now device-responsive.
🎓 Tip: More Layout Classes
SAPUI5 provides several utility layout classes:
Class Name Description
sapUiTinyMargin 4px
sapUiSmallMargin 8px
sapUiMediumMargin 16px
sapUiLargeMargin 32px
sapUiResponsiveMargin Auto-adjusts based on screen size
sapUiSmallMarginBegin, sapUiSmallMarginEnd, sapUiSmallMarginTop, etc. Add margin on specific sides
💡 Best Practices
Avoid hardcoding styles like style="margin-left: 10px" — it breaks responsiveness.
Use UI5 classes which are RTL-compatible (support Right-to-Left languages like Arabic, Hebrew).
Keep your layout mobile-first and adaptive.
✅ Summary of What You Did in Step 13
Before After
Controls glued to corners Controls have breathing room
Input and button too close Now spaced neatly
Harder to use on mobile Now responsive and readable
Plain UI Now more professional-looking
🧠 What’s the Goal of This Step?
We want to:
✅ Add a small space between the button and the input
✅ Make the output text bold and theme-colored
✅ Apply these styles in a clean, isolated, and responsive way
📁 1. style.css – Your Custom CSS
📍 Location: webapp/css/style.css
css
CopyEdit
/* For LTR layout (like English) */
html[dir="ltr"] .myAppDemoWT .myCustomButton.sapMBtn {
margin-right: 0.125rem; /* ~2px */
}
/* For RTL layout (like Arabic/Hebrew) */
html[dir="rtl"] .myAppDemoWT .myCustomButton.sapMBtn {
margin-left: 0.125rem;
}
/* Bold and inline output text */
.myAppDemoWT .myCustomText {
display: inline-block;
font-weight: bold;
}
🔍 Explanation:
We define custom classes (myCustomButton, myCustomText) and limit their scope using the parent
app class .myAppDemoWT to avoid affecting other apps.
We use .sapMBtn to increase specificity so it overrides SAPUI5 default styles.
We make sure RTL is supported by flipping margin sides.
We don't hardcode any colors — we’ll use theme-based CSS classes later.
🧩 2. Register Your CSS in manifest.json
📍 In sap.ui5 section:
json
CopyEdit
"resources": {
"css": [
{
"uri": "css/style.css"
}
]
}
✅ This tells SAPUI5 to load your CSS globally when the app starts.
3. Update App.view.xml
Here’s the full updated XML code with clear highlights:
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<!-- Add custom class to root App control for CSS scoping -->
<Shell>
<App class="myAppDemoWT">
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto">
<content>
<!-- Custom styled button (smaller space) -->
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello"
class="myCustomButton" />
<!-- Same input -->
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%" />
<!-- Formatted bold, colored text -->
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText" />
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
🎨 4. Using SAPUI5 Theme Colors
We use this standard SAPUI5 class:
xml
CopyEdit
class="sapThemeHighlight-asColor"
This applies a theme-based color to the text, so:
If the user changes the theme (e.g., sap_horizon, sap_fiori_3_dark), the color automatically changes to
match
No need to hardcode RGB/HEX values
For more, see: SAPUI5 Theme Classes List
✅ Summary
What You Did How You Did It
Added custom spacing to button CSS .myCustomButton with margin
Made text bold and responsive CSS .myCustomText and inline-block
Used theme-aware color sapThemeHighlight-asColor
Scoped all CSS to your app class="myAppDemoWT" on <App>
Loaded CSS properly Via manifest.json under resources.css[]
🛑 Warnings (Important!)
❗ Don’t Do This Do This Instead
Style controls by tag like button {} Use class selectors only
Hardcode colors like color: red Use theme-aware CSS classes
Style SAPUI5 internals like .sapMInputBaseInner Stick to public APIs + well-known classes
Apply global styles outside your app Scope them with a unique class like .myAppDemoWT
Nested Views, and this is a big milestone in your SAPUI5 learning journey! 🎯
Let me walk you through it in a simple, visual, and beginner-friendly way — so you know exactly why
you're doing this, and how it all fits together.
🌱 Goal of This Step
Instead of having one big App view with lots of code, we are now:
✅ Moving the panel code into its own small view
✅ Giving it its own controller
✅ Keeping the main app clean and easier to maintain
This approach is called View Composition (or Nested Views). You’re reusing UI pieces like building blocks
— very important in real apps.
🔧 What Changes?
🧩 You split your app like this:
Part Old Location New Location
Panel UI App.view.xml HelloPanel.view.xml
Panel Logic (button click) App.controller.js HelloPanel.controller.js
✅ Final Output: What Your Files Look Like
📁 webapp/view/App.view.xml
This is your main container view. Just loads other views inside.
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<Shell>
<App class="myAppDemoWT">
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<!-- 👇 Load nested view -->
<mvc:XMLView viewName="ui5.walkthrough.view.HelloPanel"/>
</content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
📁 webapp/view/HelloPanel.view.xml ✅ NEW
This file contains the actual panel with button, input, and text.
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.HelloPanel"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto">
<content>
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello"
class="myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText"/>
</content>
</Panel>
</mvc:View>
📁 webapp/controller/HelloPanel.controller.js ✅ NEW
This controller handles button clicks for the HelloPanel.
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], (Controller, MessageToast) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.HelloPanel", {
onShowHello() {
const oBundle = this.getView().getModel("i18n").getResourceBundle();
const sRecipient = this.getView().getModel().getProperty("/recipient/name");
const sMsg = oBundle.getText("helloMsg", [sRecipient]);
MessageToast.show(sMsg);
}
});
});
📁 webapp/controller/App.controller.js
Now this file is empty — but keep it, because you’ll use it in future steps like navigation and routing.
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller"
], (Controller) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {});
});
🔍 Why Use Nested Views?
Benefit Explanation
✅ Clean Code Keeps each file short and focused
✅ Reusability You can include HelloPanel.view.xml in other places if needed
✅ Team Collaboration Different developers can work on different views independently
✅ Easier Testing You can test HelloPanel alone if needed
🧠 In Simple Terms…
Think of it like this:
The main App.view.xml is like a house, and HelloPanel.view.xml is one of its rooms. You can now organize
your house better, and even reuse the room elsewhere if needed.
✅ You Just Learned:
✅ How to split an XML view into smaller, reusable views
✅ How to use mvc:XMLView to include nested views
✅ How to give each view its own controller
✅ How to clean up the App.controller.js
Dialogs and Fragments, one of the most powerful and reusable UI patterns in SAPUI5. Let me explain the key
concepts, how it works, and then show the code in context in a very simple and beginner-friendly way.
🎯 What’s the Goal in This Step?
✅ Create a dialog box (popup message)
✅ Use a fragment to define the dialog
✅ Load that fragment only when needed (lazy loading)
✅ Keep the UI clean, reusable, and modular
🌱 What is a Fragment?
A fragment is like a partial mini-view:
Feature View Fragment
Has a controller ✅ Yes ❌ No (uses the parent controller)
Can be reused ✅ Yes ✅ Yes
Loaded automatically ✅ Yes via XMLView ❌ No, load via controller
DOM Footprint ✅ Yes ❌ Only the inner controls
🧱 Full Working Example Breakdown
📁 view/HelloPanel.view.xml
We add a new button to open the dialog
xml
CopyEdit
<mvc:View
controllerName="ui5.walkthrough.controller.HelloPanel"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto">
<content>
<!-- 🔘 New Button to Open Dialog -->
<Button
id="helloDialogButton"
text="{i18n>openDialogButtonText}"
press=".onOpenDialog"
class="sapUiSmallMarginEnd"/>
<!-- Existing Button -->
<Button
text="{i18n>showHelloButtonText}"
press=".onShowHello"
class="myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText"/>
</content>
</Panel>
</mvc:View>
📁 view/HelloDialog.fragment.xml ✅ New File
This contains only the Dialog UI
xml
CopyEdit
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<Dialog
id="helloDialog"
title="Hello {/recipient/name}">
</Dialog>
</core:FragmentDefinition>
✅ No controller
✅ No extra wrapper — it’s just a container for controls
✅ This dialog will be opened manually using .open() from the controller
📁 controller/HelloPanel.controller.js
Add the new function onOpenDialog
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], (Controller, MessageToast) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.HelloPanel", {
onShowHello() {
const oBundle = this.getView().getModel("i18n").getResourceBundle();
const sRecipient = this.getView().getModel().getProperty("/recipient/name");
const sMsg = oBundle.getText("helloMsg", [sRecipient]);
MessageToast.show(sMsg);
},
async onOpenDialog() {
// ✅ Lazy load only once
this.oDialog ??= await this.loadFragment({
name: "ui5.walkthrough.view.HelloDialog"
});
this.oDialog.open(); // 👇 opens the dialog
}
});
});
How it works?
1. onOpenDialog() is called when user clicks the button.
2. It checks if the dialog was already loaded (this.oDialog ??=).
3. If not, loadFragment() loads the XML file and stores it.
4. Then, it calls .open() to show the dialog.
📁 i18n/i18n.properties
Add new text:
ini
CopyEdit
openDialogButtonText=Say Hello With Dialog
🧠 In Simple Words:
A fragment is a mini UI part with no controller. You load it when needed using code (not automatically like
views), and it becomes part of the screen. A dialog is a perfect example — reusable and shown only when
needed.
✅ Benefits You Gained:
Feature Benefit
🔁 Reusable The dialog can be reused across views
🚀 Lazy Loaded Faster startup; loaded only when needed
💡 Clean Code View and controller stay lightweight
📦 Organized Modular design — easy to manage in real projects
🛠 Best Practices
Always lazy load large UI elements like dialogs using fragments.
Use IDs like helloDialogButton for automation and testing.
Use theme-aware CSS and never hard-code colors.
Prefer FormattedText over normal Text for flexible output formatting.
🔄 What's Next?
Would you like to:
1. ✅ Continue to Step 17: Reuse Components
2. 🧪 Do a revision quiz for Steps 11–16?
3. 🧵 Get a visual diagram of how Views, Fragments, and Controllers interact?
Fragment Callbacks, where you learned how to handle user interaction inside fragments — specifically,
how to close a dialog using an event handler.
Let’s break this step down in a very simple and beginner-friendly way:
🎯 Goal of Step 17
Add a button inside the dialog.
Attach a click handler to it.
When clicked, the dialog should close.
🔧 Updated Files
✅ HelloDialog.fragment.xml
Before:
xml
CopyEdit
<Dialog
id="helloDialog"
title="Hello {/recipient/name}" />
After:
xml
CopyEdit
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<Dialog
id="helloDialog"
title="Hello {/recipient/name}">
<beginButton>
<Button
text="{i18n>dialogCloseButtonText}"
press=".onCloseDialog"/>
</beginButton>
</Dialog>
</core:FragmentDefinition>
🧠 What changed?
You added a beginButton aggregation.
Inside it, you placed a Button.
The press event of that button calls .onCloseDialog.
🗣 The dot (.) means the handler is in the same controller that opened the dialog, i.e., HelloPanel.controller.js.
✅ HelloPanel.controller.js
Add this function:
javascript
CopyEdit
onCloseDialog() {
this.byId("helloDialog").close();
}
🧠 Explanation:
byId("helloDialog") looks up the dialog using its ID.
.close() closes the dialog window.
✅ You don’t need to worry about promises or async logic here — this method is only triggered after the dialog
has already been loaded.
✅ i18n.properties
Add this line to support translatable button text:
properties
CopyEdit
dialogCloseButtonText=Ok
💡 Tips for Beginners
✅ Why not destroy() the dialog?
close() hides the dialog so it can be used again.
destroy() removes it permanently from memory.
We want reuse ➤ so we use close().
✅ Why use beginButton?
beginButton means "start of footer section" in a language-neutral way.
In LTR (English) → button appears on the left
In RTL (Arabic) → button appears on the right
Use endButton for "right side" buttons.
📦 Summary Table
Feature Code Used
Define dialog fragment HelloDialog.fragment.xml
Attach close handler press=".onCloseDialog"
Write close logic this.byId("helloDialog").close();
i18n label for button dialogCloseButtonText=Ok
Where logic lives HelloPanel.controller.js
✅ What You Learned
How to add buttons inside a fragment.
How to handle events from fragments.
What beginButton and endButton mean.
How to use i18n for dialog labels.
How byId() helps retrieve UI controls.
: Icons, where you learned how to add beautiful, scalable icons from the built-in SAPUI5 Icon Font to your
app — specifically inside dialogs and buttons.
🎯 Goal of Step 18
Add a globe icon 🌍 to the "Say Hello With Dialog" button.
Add a "Hello World" icon inside the dialog.
Learn how to use icon fonts via sap-icon:// syntax.
🔧 What You Did
✅ 1. HelloPanel.view.xml (Dialog Open Button Updated)
xml
CopyEdit
<Button
id="helloDialogButton"
icon="sap-icon://world"
text="{i18n>openDialogButtonText}"
press=".onOpenDialog"
class="sapUiSmallMarginEnd"/>
🧠 Explanation:
You added icon="sap-icon://world" to the button.
This shows a world icon 🌍 on the left of the button.
These icons come from the SAP icon font – no download needed!
✅ 2. HelloDialog.fragment.xml (New Icon Added to Dialog)
xml
CopyEdit
<Dialog
id="helloDialog"
title ="Hello {/recipient/name}">
<content>
<core:Icon
src="sap-icon://hello-world"
size="8rem"
class="sapUiMediumMargin"/>
</content>
<beginButton>
<Button
text="{i18n>dialogCloseButtonText}"
press=".onCloseDialog"/>
</beginButton>
</Dialog>
🧠 Explanation:
You added a core:Icon inside the dialog's content.
src="sap-icon://hello-world" uses a built-in Hello World icon.
size="8rem" makes the icon big.
class="sapUiMediumMargin" adds spacing around it.
🖼 Example: What It Looks Like
UI Element Icon Used Syntax
Button (Open Dialog) 🌍 World sap-icon://world
Inside Dialog 🙋♂️Hello World Icon sap-icon://hello-world
📘 What is sap-icon://?
It's a special URI scheme that tells SAPUI5 to load an icon from its built-in icon font.
You can explore all icons at: SAP Icon Explorer
Example:
o "sap-icon://add" ➝ ➕
o "sap-icon://employee" ➝ 👤
o "sap-icon://cart" ➝ 🛒
🔒 Why Use Icon Fonts?
✅ Advantages:
Scalable (they are vector-based) — no quality loss.
Faster — no extra image downloads.
Theme-aware — adjusts to dark/light themes automatically.
Always aligned to SAP design standards.
🚫 Avoid:
Using image files (like .png or .jpg) for standard icons.
🧠 Recap: How Icons Work
Purpose Where Used Code
Add icon to button HelloPanel.view.xml icon="sap-icon://world"
Display icon inside dialog HelloDialog.fragment.xml <core:Icon src=... />
Adjust icon size Use size="8rem" rem = relative to font
Add spacing CSS class sapUiMediumMargin For clean layout
🧠 1. What is Aggregation Binding (In Simple Words)
In SAPUI5:
“Aggregation” means: a UI control contains a group of other controls.
o Example: A List has many items (each item is a control like ObjectListItem).
“Binding” means: you connect your UI controls to data (like from a JSON file).
👉 So aggregation binding means:
You're telling a control like a List to show multiple entries from a data source (like a JSON file), and
automatically repeat UI elements for each entry.
🧾 Example:
json
CopyEdit
{
"Invoices": [
{ "ProductName": "Milk", "Quantity": 4 },
{ "ProductName": "Bread", "Quantity": 1 }
]
}
SAPUI5 will automatically:
Create a list item for “Milk”
Create another list item for “Bread”
By binding List.items aggregation to Invoices[].
💡 2. Why Do We Use Aggregation Binding?
Imagine this:
You have 10 invoices.
Instead of manually creating 10 ObjectListItems, you can bind the list to your data, and it will auto-
create list items for every invoice.
🟢 Saves time
🟢 Automatic updates
🟢 Dynamic and scalable
3. How to Use Aggregation Binding (Beginner Step-by-Step)
🔹 Step A: Create Your Data File (Invoices.json)
This is your data source in JSON format:
json
CopyEdit
{
"Invoices": [
{
"ProductName": "Milk",
"Quantity": 4
},
{
"ProductName": "Bread",
"Quantity": 1
}
]
}
🔹 Step B: Load Data Model in manifest.json
Tell SAPUI5 to load that JSON file as a data model.
json
CopyEdit
"models": {
"invoice": {
"type": "sap.ui.model.json.JSONModel",
"uri": "Invoices.json"
}
}
✅ "invoice" is the model name
✅ Invoices.json is the data file
🔹 Step C: Bind the List in XML View (InvoiceList.view.xml)
xml
CopyEdit
<List
headerText="{i18n>invoiceListTitle}"
items="{invoice>/Invoices}">
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"/>
</items>
</List>
🧠 Explanation:
Element Purpose
items="{invoice>/Invoices}" Binds the List to the array of invoices in the JSON
invoice> Tells UI5 to use the model named "invoice"
ObjectListItem Template for every invoice – repeated automatically
title="{invoice>Quantity} x {invoice>ProductName}" Shows values like "4 x Milk" dynamically
🔹 Step D: Add View in Your App.view.xml
xml
CopyEdit
<mvc:XMLView viewName="ui5.walkthrough.view.InvoiceList"/>
Now, your InvoiceList view will be shown under the main panel.
🗂 Full Component Flow Summary
File Role
Invoices.json Holds data (list of invoices)
manifest.json Loads JSONModel named invoice
InvoiceList.view.xml UI that binds to invoice model
App.view.xml Loads HelloPanel and InvoiceList
i18n.properties Adds label "Invoices" for header
✅ Final Output on Screen
You will see a list like this:
markdown
CopyEdit
Invoices
--------
4 x Milk
1 x Bread
...
All automatically generated by aggregation binding from your JSON file. 🚀
📘 Conclusion
Concept Beginner Meaning
Aggregation Binding Bind a list of UI controls to an array of data
JSONModel Data model using JSON files
Relative Binding invoice>PropertyName inside template
Auto-Repeat SAPUI5 auto-creates list items
🔄 Next Steps?
Would you like me to:
Show you color-coded diagrams of how data flows from JSON → Model → UI?
Explain how to add more invoice details (like price and shipper)?
Give a hands-on mini quiz to practice aggregation binding?
Let me know! I'm here to help you master this the easy way. 🧑🏫✨
🎯 1. What is Data Binding?
Data Binding means connecting UI controls (like Input, List, Text, etc.) to data models (like JSON, XML, or
OData).
Instead of hardcoding values, you bind controls to data so that:
Data updates are reflected automatically on screen.
The UI becomes dynamic.
📦 2. Types of Models in SAPUI5
Model Type Description When to Use
🔸 JSONModel Loads data from a .json file or JS object Simple static or dynamic local data
Rare. For XML-based backend or config
🔸 XMLModel Loads data from .xml file
files
🔸 ResourceModel Used for i18n (multi-language support) For translating text (like Hello, Submit)
Connects to SAP backend using OData When app needs real backend data (SAP
🔸 ODataModel
protocol DB)
A type of local model (JSON or XML, in
🔸 ClientModel For temporary UI interactions
browser)
✅ Most Commonly Used Models:
JSONModel — for local mock/test data
ODataModel — for real-time SAP backend integration
ResourceModel — for multilingual apps
🔗 3. Types of Data Binding in SAPUI5
SAPUI5 provides 5 types of bindings:
Binding Type Description Example
🔹 Property Binding Bind control property to a model value text="{/name}"
🔹 Aggregation
Bind a group of controls to an array of data items="{/Products}"
Binding
Bind a whole control (like a view or form) to a
🔹 Element Binding this.getView().bindElement("/Product/0")
specific part of the model
🔹 Expression
Use logic or formatting inside curly braces text="{= ${price} > 100 ? 'High' : 'Low' }"
Binding
🔹 Context Binding Similar to element binding, focuses on current Used with detail navigation
Binding Type Description Example
context of data path
🔧 4. How to Use Each Model (With Syntax)
✅ JSONModel (Local data)
When to use: Test data, small internal data
js
CopyEdit
let oModel = new sap.ui.model.json.JSONModel("model/products.json");
this.getView().setModel(oModel);
xml
CopyEdit
<Text text="{/ProductName}" />
✅ ODataModel (SAP Backend Data)
When to use: For backend SAP services (read, write, filter, etc.)
js
CopyEdit
let oModel = new sap.ui.model.odata.v2.ODataModel("/sap/opu/odata/sap/ZPRODUCT_SRV/");
this.getView().setModel(oModel);
xml
CopyEdit
<List items="{/ProductSet}">
<items>
<ObjectListItem title="{ProductName}" />
</items>
</List>
✅ ResourceModel (for i18n)
When to use: For translation, multi-language support
manifest.json
json
CopyEdit
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "myapp.i18n.i18n"
}
}
}
XML View
xml
CopyEdit
<Text text="{i18n>helloText}" />
🧭 5. When to Use Which Model?
Scenario Use This Model Why?
You want to show test data from a file JSONModel Easy, fast, supports offline
You want to call SAP backend services ODataModel Fully integrates with SAP Gateway
You want to make your app multi-language ResourceModel (i18n) Best for internationalization
You use XML configuration data XMLModel (rarely used) Only if backend gives XML response
You temporarily bind data in controller only ClientModel (JSON/XML) For temporary logic (e.g., filters, states)
🔄 Bonus: Setting Global vs Named Models
Global (Default Model)
Set without a name – used directly like {/name}
js
CopyEdit
this.getView().setModel(oModel); // default model
Named Model
Set with a name – use like {invoice>/name}
js
CopyEdit
this.getView().setModel(oInvoiceModel, "invoice");
📌 Summary Chart
Feature Description Example
JSONModel Local static/dynamic data text="{/ProductName}"
ODataModel SAP backend service items="{/ProductSet}"
ResourceModel (i18n) Translation data text="{i18n>submitButton}"
Property Binding Bind single property value="{/username}"
Aggregation Binding Bind arrays to control groups items="{/Products}"
Expression Binding Logic in curly braces text="{= ${qty} > 0 ? 'Yes' : 'No' }"
🧠 Final Tip for Freshers
🌱 Start with JSONModel — easy to understand and helps you master binding.
🔄 Move to ODataModel once you're comfortable with backend services.
🌍 Always include ResourceModel if your app supports multiple languages.
Would you like:
A diagram that explains how models, bindings, and views are connected?
A hands-on example where you do CRUD (Create, Read, Update, Delete) with OData?
A mini quiz to test your understanding of models and bindings?
Let me know how I can help you further!
🧠 What Is This Step About?
This step shows you how to:
1. Format technical price data (like 87.2) into a nice readable format (87.20 EUR)
2. Use data types (like Currency) instead of writing custom formatting logic
3. Use calculated fields and multi-part bindings to combine data values
🤔 Why Do We Need This?
In real apps:
Prices, dates, quantities come as raw data (like "87.2" or "2024-01-01T00:00:00")
We want to display them nicely: with currency, decimals, correct separators (dot/comma based on
language)
✅ So we use SAPUI5 data types which automatically:
Add the right decimal places
Use the correct currency format
Respect the user's locale/language
🧱 What is a Data Type?
SAPUI5 comes with built-in data types like:
Type What It Formats
sap.ui.model.type.Currency Formats prices & currency
sap.ui.model.type.Date Formats date fields
sap.ui.model.type.Integer Formats whole numbers
sap.ui.model.type.Float Formats decimals
🛠 How We Use It in the Code?
📁 Step 1: Add Price & Currency in View
xml
CopyEdit
<ObjectListItem
core:require="{
Currency: 'sap/ui/model/type/Currency'
}"
title="{invoice>Quantity} x {invoice>ProductName}"
number="{
parts: [
'invoice>ExtendedPrice', // 87.2
'view>/currency' // EUR
],
type: 'Currency',
formatOptions: {
showMeasure: false // hides "EUR" here
}
}"
numberUnit="{view>/currency}" <!-- shows EUR separately -->
/>
🧠 Explanation:
core:require → loads the Currency formatter into the view.
parts → combines multiple values: price from invoice model, currency from view model
type: 'Currency' → applies proper currency formatting
number → formatted result (like 87.20)
numberUnit → shows the currency separately (EUR)
📁 Step 2: Add Currency in View Model (Controller)
webapp/controller/InvoiceList.controller.js
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], (Controller, JSONModel) => {
"use strict";
return Controller.extend("ui5.walkthrough.controller.InvoiceList", {
onInit() {
const oViewModel = new JSONModel({
currency: "EUR"
});
this.getView().setModel(oViewModel, "view"); // named model 'view'
}
});
});
✅ Result:
"ExtendedPrice" like 87.2 becomes 87.20
"EUR" currency is appended or separated depending on the property
🔍 Where Did the Currency Come From?
We didn’t have "currency" in our Invoices.json. So we:
Created a new view model with currency manually (EUR)
Bound that currency model to both the number formatter and the currency label
💡 What Are "Calculated Fields"?
They are bindings like this:
js
CopyEdit
parts: [ 'price', 'currency' ]
Means: SAPUI5 calculates the final display value using multiple pieces of data.
👨🏫 As a Fresher, Remember:
Concept Meaning
Data types Built-in formatters (no code needed!)
Concept Meaning
Currency formatter Auto adds decimals & formats numbers
View model A small JSON model only for view settings
Multi-part binding Combine multiple values to bind to 1 property
🧠 Final Tips:
✅ Use built-in data types like Currency, Date — they are smart and handle locales
✅ Use view models for config or constant values
✅ Avoid writing custom format functions unless necessary — SAPUI5 already gives powerful types
🧠 What is Expression Binding?
Expression Binding in SAPUI5 lets you write small formulas directly inside XML view.
🔹 It’s used when:
You want to do simple logic, like comparisons or calculations
You want to format the UI differently based on the value of data
You don’t want to write JavaScript code for a tiny condition
🤔 Why Do We Use It?
Instead of writing a formatter function in the controller, we can do simple decisions like:
"If price > 50 then show red, else green"
"If quantity = 0 then hide this item"
"If product = 'Milk' then show icon"
All directly in XML, which is:
Clean
Fast
Declarative
🛠 How to Use It
Expression Binding Syntax:
xml
CopyEdit
property="{= expression }"
Inside that expression, you can:
Use model values like ${invoice>ExtendedPrice}
Use JavaScript-style operators: +, -, >, <, ===, ? :, etc.
✅ Example Used in This Step:
xml
CopyEdit
numberState="{= ${invoice>ExtendedPrice} > 50 ? 'Error' : 'Success' }"
🔍 Let's break this:
Part Meaning
{= ... } This is expression binding syntax
${invoice>ExtendedPrice} Access ExtendedPrice from invoice model (need $ in expressions)
> 50 ? 'Error' : 'Success' If price > 50 → show in red, else show in green
🔵 numberState is a property of ObjectListItem
🟥 Error shows red color (warning or bad)
🟩 Success shows green color (good)
👀 Output in UI:
Product Price State Color
Pineapple 87.2 🔴 Error
Milk 10 🟢 Success
Canned Beans 6.85 🟢 Success
✅ When to Use Expression Binding?
Use it for simple conditions only, like:
Coloring (Success/Error/Warning)
Visibility (true/false)
Enable/disable buttons
Choose icon/image/text based on a value
❌ When NOT to Use Expression Binding?
Avoid it if:
The logic is too complex
You need loops, formatting, localization
You're calling external services or have many conditions
➡ In those cases, create a formatter function in JavaScript.
🔧 Supported Operators in Expression Binding
Type Examples
Comparison >, <, ===, !=
Math +, -, *, /
Logical &&, `
Ternary condition ? A : B
📝 Full list: SAPUI5 Expression Binding Docs
🎯 Summary for Freshers
Concept Explanation
Expression Binding A way to do small calculations/conditions directly in XML views
When to use? When logic is very simple and related to UI only
Syntax {= condition or formula }
How to get values Use $ like ${invoice>price} inside expressions
Common use cases Coloring, Visibility, Button Enable/Disable, Small calculations
🧠 What is a Custom Formatter?
A custom formatter in SAPUI5 is a small JavaScript function you write yourself to convert or format data
before displaying it on the UI.
✅ Why Use Formatters?
SAPUI5 lets you bind data from your model to UI controls, but sometimes the raw data isn’t pretty or user-
friendly.
For example:
"A" ➝ should show "New"
"B" ➝ should show "In Progress"
87.2 ➝ should show "87.20 EUR"
true/false ➝ should show a checkmark or "Yes"/"No"
👉 For such cases, you write your own formatter to change the data before it's shown.
📦 How to Create a Formatter (Step-by-Step)
🗂 Step 1: Create formatter.js inside the model folder
js
CopyEdit
// webapp/model/formatter.js
sap.ui.define([], () => {
"use strict";
return {
statusText(sStatus) {
const oResourceBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
switch (sStatus) {
case "A":
return oResourceBundle.getText("invoiceStatusA"); // "New"
case "B":
return oResourceBundle.getText("invoiceStatusB"); // "In Progress"
case "C":
return oResourceBundle.getText("invoiceStatusC"); // "Done"
default:
return sStatus;
}
}
};
});
🔍 This formatter:
Takes A, B, or C
Converts it into human-readable text like "New", "In Progress", "Done" using i18n texts
🧠 Why use this.getOwnerComponent()?
Because:
We're inside a formatter, not directly in a controller
To access the component's model (like i18n), we must go through getOwnerComponent()
🖼 How to Use Formatter in View
📍 In InvoiceList.view.xml
We display invoice list items using ObjectListItem.
To apply our formatter:
xml
CopyEdit
<ObjectStatus
core:require="{
Formatter: 'ui5/walkthrough/model/formatter'
}"
text="{
path: 'invoice>Status',
formatter: 'Formatter.statusText.bind($controller)'
}"/>
🔍 Let's break it down:
Part Meaning
core:require Loads the formatter JS file
Formatter: 'ui5/walkthrough/model/formatter' Gives it a short name "Formatter"
formatter: Calls our statusText() function and binds it to the current
'Formatter.statusText.bind($controller)' controller context
💡 bind($controller) is important so this.getOwnerComponent() inside formatter works correctly.
🌍 i18n Texts for Status (in i18n.properties)
properties
CopyEdit
invoiceStatusA=New
invoiceStatusB=In Progress
invoiceStatusC=Done
When:
Status is "A" ➝ It will show "New"
Status is "B" ➝ It will show "In Progress"
The actual text shown can also change based on the selected language (i18n – internationalization).
🧪 Final UI Output:
Your list will now show like this:
Product Quantity Price Status
Pineapple 21 87.20 EUR 🔴 New
Milk 4 10.00 EUR 🟢 In Progress
Canned Beans 3 6.85 EUR 🟢 In Progress
Salad 2 8.80 EUR 🟢 Done
Product Quantity Price Status
Bread 1 2.71 EUR 🔴 New
📝 Summary for Freshers
Concept Explanation
Custom Formatter A JS function to transform data for display
Why use it? To show readable text or UI state instead of technical values
Where is it stored? In webapp/model/formatter.js
How to use in XML? formatter: 'Formatter.functionName.bind($controller)'
Where to put i18n text? webapp/i18n/i18n.properties
✅ Custom formatters are useful when:
Data needs conversion
You want to localize output
Or apply complex display logic
Filtering, which is about adding a search functionality to your SAPUI5 app using filters. Let's break it down
clearly and simply, especially for you as a fresher.
🧠 What Is Filtering in SAPUI5?
Filtering means:
Showing only the list items that match a certain condition (like a search term).
Example:
You have 5 products: Apple, Milk, Bread, Banana, Pineapple.
If the user types "Pine", only "Pineapple" is shown in the list.
✅ Why Do We Use It?
We use filtering to:
Help users find what they’re looking for quickly
Make large lists easier to navigate
Respond to user input like a SearchField
📦 What Did We Add in This Step?
Let’s go through what, why, and how line by line.
1. Adding a Search Field in the View (XML)
xml
CopyEdit
<List
id="invoiceList"
items="{invoice>/Invoices}">
<headerToolbar>
<Toolbar>
<Title text="{i18n>invoiceListTitle}"/>
<ToolbarSpacer/>
<SearchField
width="50%"
search=".onFilterInvoices"/>
</Toolbar>
</headerToolbar>
🔍 What’s Happening?
Line What It Does
headerToolbar Adds a toolbar (custom list header)
<Title/> Shows the "Invoices" title
<ToolbarSpacer/> Pushes the search bar to the right
<SearchField Adds a search box and links it to the function onFilterInvoices() in the
search=".onFilterInvoices"/> controller
🧠 2. Why Give id="invoiceList"?
Because we want to access the list in the controller to filter its items when user searches.
🧠 3. What Is Filtering Logic in Controller?
Here is the new code in InvoiceList.controller.js:
javascript
CopyEdit
onFilterInvoices(oEvent) {
const aFilter = [];
const sQuery = oEvent.getParameter("query"); // what user typed
if (sQuery) {
aFilter.push(new Filter("ProductName", FilterOperator.Contains, sQuery));
}
const oList = this.byId("invoiceList"); // get the List
const oBinding = oList.getBinding("items"); // get its data binding
oBinding.filter(aFilter); // apply the filter
}
🔍 Explanation in Simple Terms:
Line What It Does
sQuery = oEvent.getParameter("query") Gets what user typed
new Filter(...) Creates a filter: show items where ProductName contains sQuery
byId("invoiceList") Finds the <List> from XML
getBinding("items") Gets the connection between List and JSON model
oBinding.filter(aFilter) Applies the filter: list auto-updates
🔧 Required Imports at Top of Controller
You need to import Filter classes like this:
js
CopyEdit
import Filter from "sap/ui/model/Filter";
import FilterOperator from "sap/ui/model/FilterOperator";
These help you create and apply filtering logic.
🧪 Example Scenario
If you type milk in the search bar:
sQuery = "milk"
Filter becomes: ProductName contains "milk"
Only 1 item (Milk) is shown in the list
If you clear the search:
sQuery = ""
Filter array is empty → all items are shown again
✅ Summary for Freshers
Concept Explanation
Filtering Shows only data that matches search or condition
SearchField Input box that calls a function when user searches
Filter Object Used to define what to show or hide
FilterOperator.Contains Checks if a field contains the search value
byId() Finds a control in the current view
getBinding("items") Gets the list’s connection to model data
filter() Applies the filtering logic to the list
📘 Tip for Interview or Practice
💡 When asked:
"How do you implement search in SAPUI5?"
You can say:
"I use a SearchField in the view and bind its search event to a function. In that function, I create a Filter on the
relevant property using FilterOperator.Contains, then apply it to the List binding using
getBinding('items').filter()."
Sorting and Grouping in your SAPUI5 walkthrough app — in a very simple, beginner-friendly way.
🎯 Goal of This Step:
To make your invoice list easier to read by:
1. Sorting products (e.g., alphabetically by product name).
2. Grouping them by shipping company (e.g., ACME, Fun Inc.).
🔍 What Is Sorting and Grouping in SAPUI5?
Concept Meaning
Sorting Arranging items in a certain order (like A–Z or Z–A).
Clustering similar items together under a common header (e.g., all invoices from "ACME" grouped
Grouping
together).
💡 Why Use It?
Because:
Sorting makes it easier to scan and find items.
Grouping gives users context — like which shipping company handled the order.
✅ 1. Sorting in the List Binding
✅ Code Snippet:
xml
CopyEdit
<List
id="invoiceList"
items="{
path : 'invoice>/Invoices',
sorter : {
path : 'ProductName'
}
}">
🧠 Explanation:
Part Meaning
items="{...}" You're binding the items aggregation to the /Invoices array
sorter Automatically sort the list
path: 'ProductName' Sort by the ProductName (like Apple, Banana, Milk...)
ascending (default) By default, sort is A-Z. You can also add descending: true
So the list will now be sorted alphabetically by product name!
✅ 2. Grouping by ShipperName
✅ Updated Code:
xml
CopyEdit
<List
id="invoiceList"
items="{
path : 'invoice>/Invoices',
sorter : {
path : 'ShipperName',
group : true
}
}">
🧠 Explanation:
Part Meaning
path: 'ShipperName' Group invoices by the ShipperName (e.g., ACME, Fun Inc.)
group: true Tells UI5 to group list items based on this field
You’ll now see:
markdown
CopyEdit
ACME
- Milk
- Salad
- Canned Beans
Fun Inc.
- Pineapple
- Bread
✨ SAPUI5 Handles Group Headers Automatically!
When you write group: true, SAPUI5 automatically shows the group headers (like “ACME” and “Fun Inc.”)
— no extra code needed.
If you want custom group header formatting, you can use groupHeaderFactory, but in most simple apps, this
default grouping is perfect.
🧪 Optional: Sort by ProductName & Group by Shipper
You can even do both like this:
xml
CopyEdit
items="{
path: 'invoice>/Invoices',
sorter: [
{ path: 'ShipperName', group: true },
{ path: 'ProductName' }
]
}"
This will:
Group by ShipperName
Inside each group, sort ProductName alphabetically
🧠 Summary for Freshers
Term Meaning
sorter Tells SAPUI5 how to sort or group the list
path Field name to sort or group on (e.g., ProductName, ShipperName)
group: true Adds automatic group headers
Default sort order Ascending (A → Z)
descending: true Optional, sorts Z → A
✅ Tip for Interviews or Practice
💬 Q: How do you implement sorting and grouping in a SAPUI5 List?
You can answer:
“We use the sorter property inside the binding. For sorting, we define path: 'ProductName'. To group, we add
group: true and use a field like ShipperName. SAPUI5 automatically renders group headers.”
Remote OData Service in your SAPUI5 application in a simple way that a fresher can fully understand.
🧠 What You’ll Learn:
What is a remote OData service?
Why use it instead of local data?
How to connect to remote data using a proxy
How SAPUI5 automatically loads the data for you with very little change!
📦 Before This Step:
You were showing invoice data from a local JSON file (Invoices.json) using a JSONModel.
Now you're going to:
✅ Replace the local data
✅ Fetch real-time data from the Northwind public OData service
✅ Use ODataModel instead of JSONModel
✅ What Is OData?
OData = Open Data Protocol
It's a standard way to query and update data via URLs (REST API) — mostly used in SAP systems.
Example of OData Service:
📡 https://services.odata.org/V2/Northwind/Northwind.svc/
💡 Why Use Remote OData Instead of Local JSON?
Local JSON Remote OData
Static, fixed data Real-time data
Good for testing Used in production
No server needed Needs backend (e.g., SAP, cloud)
No sorting/filtering done on server Can query/filter/sort on server side
How to Use OData in SAPUI5
🔧 Step 1: Add Proxy to Avoid CORS
Browsers block direct remote calls to another server due to security.
To bypass this, we use a proxy server in development. It looks like:
rust
CopyEdit
App --> Proxy (/V2) --> https://services.odata.org
📁 ui5.yaml (Configure Proxy)
yaml
CopyEdit
server:
customMiddleware:
- name: ui5-middleware-simpleproxy
afterMiddleware: compression
mountPath: /V2
configuration:
baseUri: "https://services.odata.org"
📌 This means:
Any UI5 request to /V2/* will be forwarded to https://services.odata.org/*.
⚙️Install Proxy
bash
CopyEdit
npm install -D ui5-middleware-simpleproxy
🧠 Step 2: Configure manifest.json
1️⃣ Add the remote data source
json
CopyEdit
"dataSources": {
"invoiceRemote": {
"uri": "V2/Northwind/Northwind.svc/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
}
2️⃣ Replace the old invoice model
json
CopyEdit
"models": {
"invoice": {
"dataSource": "invoiceRemote"
}
}
📌 Here:
invoiceRemote is linked to the actual Northwind service.
SAPUI5 now automatically creates an ODataModel and loads data from the real server.
✅ Result:
No need to change your InvoiceList.view.xml!
Because the OData structure looks the same as your old JSON, bindings like:
xml
CopyEdit
items="{invoice>/Invoices}"
title="{invoice>ProductName}"
still work!
📝 Important Notes for Beginners
Concept What it Means
ODataModel Used for remote data via OData service
JSONModel Used for local data (like your earlier steps)
proxy Used in development to avoid browser security (CORS) issues
manifest.json Central config file where you register data sources and models
this.getModel("invoice") Access the model defined by name "invoice"
this.getModel() Gets the default model (if no name is given)
🎓 Interview/Certification Tip
💬 Q: How do you connect to remote data in SAPUI5?
✅ Answer:
We configure an ODataModel in the manifest.json using a data source URI and define a model with a reference
to that data source. In development, we use a proxy to handle CORS. SAPUI5 automatically creates and loads
the model during app start-up.
✅ Summary
Step What You Did
1. Installed Proxy npm i -D ui5-middleware-simpleproxy
2. Configured Proxy ui5.yaml with /V2 mountPath
3. Updated manifest.json Added dataSources and changed invoice model
No change! SAPUI5 binds to remote data now
4. Bound List View
Mock Server Configuration, which is a powerful and professional way to simulate backend OData services
without needing real servers—perfect for offline testing, development, and demo purposes.
Let’s break it down in a simple and beginner-friendly way:
🧠 Why Do We Need a Mock Server?
When you’re building an app:
Sometimes real back-end servers are unavailable.
You don’t want to put load on live servers during testing.
You may want to test features offline.
So instead of calling a real server, we simulate one using the SAPUI5 Mock Server.
📊 Key Benefits of Using a Mock Server
Feature Advantage
Runs locally No need for internet or backend
Fast Lightweight and quick testing
Flexible Customize mock data anytime
Realistic Uses OData format and simulates real responses
📁 Folder Structure After Step 26
bash
CopyEdit
webapp/
├── controller/
├── localService/ <-- Mock server and mock data here
│ ├── mockserver.js
│ ├── metadata.xml
│ └── mockdata/
│ └── Invoices.json
├── test/ <-- For test entry point (not for production)
│ └── mockServer.html
├── view/
├── Component.js
├── manifest.json
└── index.html <-- Main app using real data
✅ How It Works Step-by-Step
🧪 1. mockServer.html – Entry Point for Testing
html
CopyEdit
<script
id="sap-ui-bootstrap"
...
data-sap-ui-on-init="module:ui5/walkthrough/test/initMockServer"
>
</script>
✅ This tells UI5:
First run the initMockServer.js module → which initializes the mock backend → then load the app.
⚙️2. initMockServer.js – Starts the Mock Server
javascript
CopyEdit
sap.ui.define([
"../localService/mockserver"
], (mockserver) => {
mockserver.init();
sap.ui.require(["sap/ui/core/ComponentSupport"]);
});
✅ It:
Loads mockserver.js
Starts the mock server
Loads the app like normal, but uses mock data.
3. mockserver.js – Core Logic to Fake the OData Service
javascript
CopyEdit
const oMockServer = new MockServer({
rootUri: "ui5/walkthrough/V2/Northwind/Northwind.svc/"
});
Then:
javascript
CopyEdit
MockServer.config({
autoRespond: true,
autoRespondAfter: 500
});
oMockServer.simulate(
"ui5/walkthrough/localService/metadata.xml",
"ui5/walkthrough/localService/mockdata"
);
oMockServer.start();
✅ It simulates all OData requests to the Northwind service using:
Metadata from metadata.xml
Data from Invoices.json
📦 4. metadata.xml – OData Service Description
Defines structure of data: fields, types, etc.
Used by mockserver.js to mimic a real OData service
📌 You can get metadata from any real service by calling:
https://<odata-service-url>/$metadata
You can edit it to add custom fields like Status.
📄 5. Invoices.json – Local Test Data
Example:
json
CopyEdit
[
{
"ProductName": "Pineapple",
"Quantity": 21,
"ExtendedPrice": 87.2,
"ShipperName": "Fun Inc.",
"Status": "A"
}
]
✅ Data will be served as if coming from a real remote OData server.
⚡ 6. manifest.json – No Change Needed!
The data source invoiceRemote is already pointing to:
json
CopyEdit
"uri": "V2/Northwind/Northwind.svc/"
So UI5 doesn’t care whether that path is real or mocked—it just works!
🧪 Run in Mock Mode
Run this command to launch mock mode:
bash
CopyEdit
npm start
Your package.json is set up like:
json
CopyEdit
"scripts": {
"start": "ui5 serve -o test/mockServer.html"
}
Test Like a Pro
URL What It Does
index.html Runs the app using real remote OData data
mockServer.html Runs the app using mocked local test data
✅ Summary
Concept Explanation
Mock Server Simulates backend OData server
mockServer.html Entry point for testing mode
mockserver.js Starts the mock server using metadata and local data
Invoices.json Holds sample invoice data
metadata.xml Describes the structure of the mock service
Concept Explanation
manifest.json No change required if URI stays the same
🧠 Pro Tips for Interviews or Exams
❓Q: Why would you use a mock server in SAPUI5?
✅ A: To simulate OData service responses during development and testing without relying on real backend
servers.
❓Q: What file defines the mock service’s metadata?
✅ A: metadata.xml located in webapp/localService.
❓Q: How do you start the mock server?
✅ A: With the custom mockServer.html entry point, which runs initMockServer.js.
Unit Testing with QUnit, which is a critical step in professional SAPUI5 development. Let’s break it down
clearly and step by step, so you fully understand how QUnit testing works, especially in the context of your
formatter function.
✅ What Are Unit Tests?
Unit tests check small, isolated pieces of your code — like one function — to ensure they behave as expected.
In this case, we are testing the statusText formatter function.
🧠 Why Use QUnit in SAPUI5?
🔍 Feature ✅ Reason
Lightweight Easy to set up and fast
Integrated with UI5 UI5 supports QUnit natively
Helps catch bugs early You test before running full app
Encourages clean code You isolate logic for easier testing
Folder Structure for Tests
Here’s how your test structure looks now:
text
CopyEdit
webapp/
└── test/
├── unit/ <-- Unit test logic
│ ├── model/
│ │ └── formatter.js <-- Unit test for formatter function
│ └── unitTests.qunit.js <-- Entry point to unit test modules
├── Test.qunit.html <-- Main test execution page
├── testsuite.qunit.js <-- Test suite configuration
└── testsuite.qunit.html <-- Browser view to run tests
🧪 1. Testing the Formatter Function
🧾 webapp/test/unit/model/formatter.js
We are testing this function (defined earlier in your app):
js
CopyEdit
statusText: function(sStatus) {
const oResourceBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
switch (sStatus) {
case "A": return oResourceBundle.getText("invoiceStatusA");
case "B": return oResourceBundle.getText("invoiceStatusB");
case "C": return oResourceBundle.getText("invoiceStatusC");
default: return sStatus;
}
}
✅ Now you test it using this:
js
CopyEdit
sap.ui.define([
"ui5/walkthrough/model/formatter",
"sap/ui/model/resource/ResourceModel",
], (formatter, ResourceModel) => {
"use strict";
QUnit.module("Formatting functions");
QUnit.test("Should return the translated texts", (assert) => {
const oResourceModel = new ResourceModel({
bundleUrl: sap.ui.require.toUrl("ui5/walkthrough/i18n/i18n.properties"),
supportedLocales: [""],
fallbackLocale: ""
});
const oControllerMock = {
getOwnerComponent() {
return {
getModel() {
return oResourceModel;
}
};
}
};
const fnFormatter = formatter.statusText.bind(oControllerMock);
assert.strictEqual(fnFormatter("A"), "New", "Status A → New");
assert.strictEqual(fnFormatter("B"), "In Progress", "Status B → In Progress");
assert.strictEqual(fnFormatter("C"), "Done", "Status C → Done");
assert.strictEqual(fnFormatter("X"), "X", "Unknown status → fallback to original");
});
});
🔍 What’s Happening?
You simulate a ResourceModel using your i18n file.
You mock getOwnerComponent().getModel("i18n").
You bind the formatter function to your mock.
Then you test all the possible input values.
📥 2. Entry Point: unitTests.qunit.js
This loads the test modules:
js
CopyEdit
sap.ui.define([
"./model/formatter"
]);
🟢 You can add more tests later in this file (e.g., controller logic).
🌐 3. Page to Run Tests: Test.qunit.html
html
CopyEdit
<script
src="../resources/sap/ui/test/starter/runTest.js"
data-sap-ui-resource-roots='{
"test-resources.ui5.walkthrough": "./"
}'
></script>
✅ This file runs the tests in the browser.
📋 4. Test Suite Configuration: testsuite.qunit.js
js
CopyEdit
sap.ui.define(() => {
"use strict";
return {
name: "QUnit test suite for UI5 Walkthrough",
defaults: {
page: "ui5://test-resources/ui5/walkthrough/Test.qunit.html?testsuite={suite}&test={name}",
qunit: { version: 2 },
ui5: { theme: "sap_horizon" },
loader: {
paths: {
"ui5/walkthrough": "../"
}
}
},
tests: {
"unit/unitTests": {
title: "UI5 Walkthrough - Unit Tests"
}
}
};
});
🎯 This tells UI5:
Which test files to load (unit/unitTests)
Which HTML page to use (Test.qunit.html)
Theme and loader settings
5. Launch Page: testsuite.qunit.html
html
CopyEdit
<script
src="../resources/sap/ui/test/starter/createSuite.js"
data-sap-ui-testsuite="test-resources/ui5/walkthrough/testsuite.qunit"
data-sap-ui-resource-roots='{
"test-resources.ui5.walkthrough": "./"
}'
></script>
🎯 This is the final file you open in your browser to launch all unit tests.
🔎 What You’ll See
Open this in your browser:
bash
CopyEdit
webapp/test/testsuite.qunit.html
👉 You'll see:
A list of all tests (unit/unitTests)
Click to run tests
✅ Green if pass, ❌ red if fail
Detailed results of each assertion
✅ Summary
Step What You Did
1 Created a unit test for formatter
2 Mocked i18n ResourceModel
3 Configured entry point and test suite
4 Created UI5-compatible test runner pages
5 Opened testsuite.qunit.html to run tests
Integration Test with OPA (One Page Acceptance tests)! This is where we start testing actual user
interactions — not just logic.
Let’s break everything down in a simple, beginner-friendly way, and walk through what's happening, why it
matters, and how it all fits together.
🧪 What Are OPA Integration Tests?
Type Description
🧠 Unit Test Tests a single function (like statusText)
Integration Test Tests how parts of the app work together, especially UI actions
✅ Example "Does the dialog open when I click the button?"
📋 What You’re Testing in Step 28
You're testing:
1. The app loads correctly.
2. The user clicks the “Say Hello with Dialog” button.
3. A Dialog box appears.
4. If successful, the test turns green.
Folder Structure Added
You now have:
plaintext
CopyEdit
webapp/
└── test/
├── integration/
│ ├── NavigationJourney.js <-- Full integration scenario
│ ├── pages/
│ │ └── App.js <-- Page object abstraction
│ └── opaTests.qunit.js <-- Entry point for OPA tests
└── testsuite.qunit.js <-- Now updated to include integration tests
✅ Step-by-Step Explanation
1️⃣ NavigationJourney.js – The “Storyline”
This is like your test script: “Click the button and check if the dialog appears.”
js
CopyEdit
sap.ui.define([
"sap/ui/test/opaQunit",
"./pages/App"
], (opaTest) => {
"use strict";
QUnit.module("Navigation");
opaTest("Should open the Hello dialog", (Given, When, Then) => {
Given.iStartMyUIComponent({
componentConfig: { name: "ui5.walkthrough" }
});
When.onTheAppPage.iPressTheSayHelloWithDialogButton();
Then.onTheAppPage.iShouldSeeTheHelloDialog();
Then.iTeardownMyApp();
});
});
🧠 What Each Line Does:
Given.iStartMyUIComponent: Launches the app.
When.onTheAppPage.iPressTheSayHelloWithDialogButton(): Simulates a click.
Then.onTheAppPage.iShouldSeeTheHelloDialog(): Asserts that the dialog appeared.
Then.iTeardownMyApp(): Cleans up (closes app after test).
2️⃣ pages/App.js – The Page Object
This separates UI logic from test code, for reusability and readability.
js
CopyEdit
sap.ui.define([
"sap/ui/test/Opa5",
"sap/ui/test/actions/Press"
], (Opa5, Press) => {
"use strict";
const sViewName = "ui5.walkthrough.view.HelloPanel";
Opa5.createPageObjects({
onTheAppPage: {
actions: {
iPressTheSayHelloWithDialogButton() {
return this.waitFor({
id: "helloDialogButton",
viewName: sViewName,
actions: new Press(),
errorMessage: "Button not found"
});
}
},
assertions: {
iShouldSeeTheHelloDialog() {
return this.waitFor({
controlType: "sap.m.Dialog",
success() {
Opa5.assert.ok(true, "Dialog opened successfully");
},
errorMessage: "Dialog not found"
});
}
}
}
});
});
🧠 What It’s Doing:
waitFor: Waits until a control is available on the screen.
actions: Simulates clicking the dialog button.
assertions: Confirms that the dialog has been shown.
3️⃣ opaTests.qunit.js – The Integration Entry Point
js
CopyEdit
sap.ui.define([
"./NavigationJourney"
]);
✅ Just loads the integration test NavigationJourney.js.
4️⃣ Update in testsuite.qunit.js
Now your full test suite file looks like this:
js
CopyEdit
sap.ui.define(() => {
"use strict";
return {
name: "QUnit test suite for UI5 Walkthrough",
defaults: {
page: "ui5://test-resources/ui5/walkthrough/Test.qunit.html?testsuite={suite}&test={name}",
qunit: { version: 2 },
ui5: { theme: "sap_horizon" },
loader: {
paths: {
"ui5/walkthrough": "../"
}
}
},
tests: {
"unit/unitTests": {
title: "UI5 Walkthrough - Unit Tests"
},
"integration/opaTests": {
title: "UI5 Walkthrough - Integration Tests"
}
}
};
});
5️⃣ Run Your Test!
✅ Open this file in your browser:
bash
CopyEdit
webapp/test/testsuite.qunit.html
Click: integration/opaTests
You will see:
App loads
Button is clicked automatically
Dialog appears
Green success bar appears for the test
🧠 Summary – What You Achieved
✅ You Did 🌟 Outcome
Created your first OPA5 test Checked UI interaction (button click + dialog)
Used Given/When/Then Follows Behavior Driven Development (BDD)
Organized clean structure Reusable, readable, maintainable
Integrated with QUnit Unified test reporting
📌 Conventions Recap
📁 Folder 💡 Purpose
test/unit Unit tests (formatters, logic)
test/integration OPA tests (UI interactions)
test/integration/pages/ Page objects (actions + assertions)
test/testsuite.qunit.js Test configuration
test/testsuite.qunit.html Browser-based test runner
Debugging Tools, a crucial skill for any SAPUI5 developer. In this step, you learn how to identify and fix
issues when your app doesn't behave as expected — like when prices suddenly stop showing!
Let’s break it down in a clear and beginner-friendly way.
🧠 What Happened in This Step?
You're simulating a real-world error:
A typo in your data binding causes the price (number) field to disappear from the invoice list.
🐞 Problem Introduced
❌ Bug Line in View:
xml
CopyEdit
'**invoice>ExTendedPrice**'
This is wrong because:
The real property name is ExtendedPrice (with a lowercase 'x' in your typo: ExTendedPrice).
🔍 Impact:
The price column disappears (or shows blank) in the app.
There’s no visible error on screen.
Only behind-the-scenes something went wrong.
How to Debug the Issue
✅ 1. Use SAPUI5 Diagnostics Tool
💡 Shortcut:
Press:
scss
CopyEdit
Ctrl + Shift + Alt + S (Windows/Linux)
⌘ + Shift + Option + S (Mac)
This opens the SAPUI5 Diagnostics Window.
📂 In the Diagnostics Window:
Go to the Control Tree tab
Expand and find the ObjectListItem control
On the right, click Binding Infos
There you’ll see:
o ✅ Working bindings (green/black)
o ❌ Failing bindings (marked in red with warning or error)
➡️It will say something like:
Invalid property path: invoice>ExTendedPrice
✅ 2. Correct the Binding
Fix this in your XML view:
🔧 Change this:
xml
CopyEdit
'invoice>ExTendedPrice'
✅ To this:
xml
CopyEdit
'invoice>ExtendedPrice'
✅ 3. Use Browser Debugging Tools (DevTools)
If diagnostics didn’t help:
Steps:
1. Open DevTools (F12 or Right click > Inspect)
2. Go to Console or Sources tab
3. Add a breakpoint in your controller/model/formatter if needed
4. Watch values step-by-step
🐢 Optional: Enable Debug Mode
By default, SAPUI5 files are minified (.js) — hard to read.
You can enable debug sources like this:
🧭 Option 1: Use URL Parameter
Add this at the end of your app URL:
lua
CopyEdit
?sap-ui-debug=true
🧭 Option 2: Use Shortcut
Press:
css
CopyEdit
Ctrl + Shift + Alt + P
Then check ✅ “Use Debug Sources”
Reload the app. Now you will see -dbg.js files in Network tab – easier to debug.
🧠 Bonus: UI5 Inspector (Chrome Plugin)
If you're using Google Chrome, install this:
🔗 UI5 Inspector Chrome Extension
This tool helps:
Inspect UI5 control hierarchy visually
View and test bindings
Access control properties at runtime
✅ Final Code Fix Summary
Your number attribute inside <ObjectListItem> should look like:
xml
CopyEdit
number="{
parts: [
'invoice>ExtendedPrice',
'view>/currency'
],
type: 'Currency',
formatOptions: {
showMeasure: false
}
}"
📌 Conventions Recap
Convention Meaning
-dbg.js Debug (unminified) version of JavaScript source
sap-ui-debug=true URL param to force debug mode
Ctrl + Shift + Alt + S SAPUI5 Diagnostics shortcut
Ctrl + Shift + Alt + P Enable debug sources shortcut
Control Tree > Binding Infos Where you see binding errors clearly
✅ What You Learned in Step 29
Skill Description
🔍 Debugging Bindings Using Control Tree + Binding Infos
🧪 Fixing Mistakes Corrected binding path error
🐞 Debug Mode How to view readable (unminified) source files
🔧 Browser DevTools How to inspect and debug code step-by-step
Routing and Navigation — a major milestone where you make your SAPUI5 app behave like a real, modern
single-page application with multiple navigable views and URL-aware routing. Let’s break it down clearly
and simply.
🧭 What You Learned in This Step
You learned how to:
Define multiple views (Overview, Detail)
Use the SAPUI5 Router for navigation between pages
Update the URL dynamically based on navigation (e.g., #/detail)
Respond to a click event (on a list item) to navigate to a new page
🗂 Folder and File Structure Added
File Purpose
Overview.view.xml New main/home view for your app content
Detail.view.xml New detail page shown on item click
InvoiceList.controller.js Now includes navigation logic
manifest.json Configures routing rules and targets
Component.js Initializes router at startup
🧠 How Routing Works — Step-by-Step Explanation
1️⃣ Routing Defined in manifest.json
json
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"type": "View",
"viewType": "XML",
"path": "ui5.walkthrough.view",
"controlId": "app",
"controlAggregation": "pages"
},
"routes": [
{ "pattern": "", "name": "overview", "target": "overview" },
{ "pattern": "detail", "name": "detail", "target": "detail" }
],
"targets": {
"overview": { "name": "Overview", "id": "overview" },
"detail": { "name": "Detail", "id": "detail" }
}
}
controlId: "app" → This is the <App id="app"> inside App.view.xml.
pattern: "" → Default route (home).
pattern: "detail" → Navigates to Detail page.
target: { name } → View name without .view.xml.
2️⃣ App.view.xml Acts as Shell
xml
<Shell>
<App id="app" />
</Shell>
🔄 All navigation content is loaded inside this App tag dynamically.
3️⃣ Component.js Initializes Routing
js
this.getRouter().initialize();
This reads the current URL (like #/detail) and loads the right view.
4️⃣ Overview.view.xml Shows App Content
xml
<Page title="{i18n>homePageTitle}">
<mvc:XMLView viewName="ui5.walkthrough.view.HelloPanel" />
<mvc:XMLView viewName="ui5.walkthrough.view.InvoiceList" />
</Page>
🧱 You embed your earlier HelloPanel and InvoiceList views here.
5️⃣ InvoiceList.view.xml Updated for Navigation
xml
<ObjectListItem
press=".onPress"
type="Navigation"
... />
press triggers navigation
type="Navigation" shows a forward arrow (UX cue)
6️⃣ Navigation Logic in InvoiceList.controller.js
js
onPress() {
const oRouter = this.getOwnerComponent().getRouter();
oRouter.navTo("detail");
}
🧭 This navigates to Detail.view.xml when an invoice is clicked.
7️⃣ Detail.view.xml Displays Target Page
xml
CopyEdit
<Page title="{i18n>detailPageTitle}">
<ObjectHeader title="Invoice" />
</Page>
🧾 You see the Detail page load when navigating.
🪛 Extra Details
Resource Bundle: i18n.properties
properties
CopyEdit
detailPageTitle=Walkthrough - Details
This adds the title to the Detail page.
📌 Conventions & Best Practices
Convention Meaning
Routing in manifest.json Clean and declarative configuration
Control ID app Must match the one used in routing
Use navTo("routeName") Triggers navigation
Views go into targets Dynamically created and loaded into app control
Initialize router in Component.js Only once, at startup
🧪 Want to Try This?
Test It in Browser:
Load: index.html or webapp/test/testsuite.qunit.html
Click an invoice item → 💥 Navigates to Detail Page
URL becomes: #/detail
🧩 What's Next?
You’ve now set up the foundation of a Master-Detail UI using SAPUI5 routing. Upcoming steps often
involve:
Passing parameters to the detail view (e.g., invoice ID)
Binding dynamic data in the detail view
Adding back navigation
✅ Summary
You now have:
🧭 Routing and URL-aware navigation
📄 Multiple views (Overview, Detail)
Clickable list items that route to other pages
📦 Cleanly separated concerns: views, router config, and controllers
: Routing with Parameters, you've made your SAPUI5 app dynamic by passing contextual information
between views. Let's break this down simply and clearly so you understand what’s happening, why it works,
and how you can reuse it later.
🧭 What You Just Learned
You added support for navigation with dynamic parameters so that clicking an invoice opens its individual
detail view — not just a generic one.
In simple terms:
You clicked on an item in a list.
The app captured that item’s data path.
It passed the path as a parameter in the URL.
The detail page read this parameter and bound its UI to the correct data.
✅ End result: You now see personalized data on the detail page.
🧱 Key Files & Code Changes
File What Changed
manifest.json Added invoicePath parameter to the detail route
InvoiceList.controller.js Reads clicked item’s path and navigates with invoicePath
Detail.controller.js Reads parameter and binds correct data to view
Detail.view.xml Displays specific invoice data
Overview.view.xml Remains the invoice list page
⚙️How Routing with Parameters Works
1️⃣ Define Route with Parameter in manifest.json
json
{
"pattern": "detail/{invoicePath}",
"name": "detail",
"target": "detail"
}
{invoicePath} is a route parameter.
It will get filled dynamically at runtime (like /detail/invoices/3).
"pattern" maps to the URL structure.
2️⃣ Set Navigation Parameter on Click
InvoiceList.controller.js
js
onPress(oEvent) {
const oItem = oEvent.getSource();
const oRouter = this.getOwnerComponent().getRouter();
const sPath = oItem.getBindingContext("invoice").getPath().substring(1);
oRouter.navTo("detail", {
invoicePath: window.encodeURIComponent(sPath)
});
}
oItem.getBindingContext("invoice").getPath() gives something like /Invoices/2
.substring(1) → Invoices/2 (removes /)
encodeURIComponent() → Handles special characters in URLs
🧠 Now, the final URL will be:
shell
CopyEdit
#/detail/Invoices%2F2
3️⃣ Decode the Parameter & Bind the View
Detail.controller.js
js
onInit() {
const oRouter = this.getOwnerComponent().getRouter();
oRouter.getRoute("detail").attachPatternMatched(this.onObjectMatched, this);
},
onObjectMatched(oEvent) {
const sPath = "/" + decodeURIComponent(oEvent.getParameter("arguments").invoicePath);
this.getView().bindElement({
path: sPath,
model: "invoice"
});
}
When the detail route is hit, onObjectMatched() runs.
It decodes the invoicePath (e.g. Invoices/2) and binds the detail view to that data path.
4️⃣ Use the Bound Data in the XML View
Detail.view.xml
xml
<ObjectHeader
intro="{invoice>ShipperName}"
title="{invoice>ProductName}" />
The invoice> binding accesses the model bound in the controller.
You can now show any invoice-specific data.
🔁 Data Flow Summary
pgsql
1. User clicks invoice item in list
↓
2. Controller reads binding path and calls navTo("detail", { invoicePath })
↓
3. Router updates the URL → #/detail/Invoices%2F1
↓
4. Router matches route "detail/{invoicePath}"
↓
5. Detail controller gets the parameter → binds to model path
↓
6. Detail view shows correct invoice data
🧪 Test Case
Try this in your app:
Click the first invoice: URL becomes #/detail/Invoices%2F0
Click the second invoice: URL becomes #/detail/Invoices%2F1
Try visiting the URL directly — it works!
💡 That's the power of deep linking — very useful for bookmarks, sharing, or browser navigation.
🧼 Best Practices Recap
Practice Why It’s Important
manifest.json routing setup Declarative, readable routing config
navTo("route", params) Clean way to navigate + pass context
encodeURIComponent Avoids issues with special characters in URLs
attachPatternMatched Ensures routing logic is only triggered on match
bindElement() Binds view context efficiently to specific data
✅ Final Output
When you click a list item:
🔗 URL becomes dynamic: #/detail/Invoices%2F2
🧾 Detail view shows that item’s actual data
🔁 Page is reusable — shows different data depending on URL
✅ What is “Routing Back and History” in SAPUI5?
When you're on the Detail Page (e.g., viewing a single invoice), you often want a way to go back to the
previous screen (like the Overview Page showing the list of all invoices).
This step helps us:
Add a Back button
Handle what should happen when the Back button is clicked — either go back in browser history or go
to a default route like the overview page
❓ Why do we need it?
Imagine this:
User opens the detail page directly from a bookmark (there’s no “previous page” inside the app).
Or, user navigated from overview to detail, and now wants to return.
If we don’t handle this properly:
Pressing back might do nothing, or
It might exit the app, or go somewhere unexpected.
So, we must add logic to handle:
1. Go back if the user came from inside the app.
2. Otherwise, go to overview manually.
⚙️How It Works – Step-by-Step
🧾 1. Update the Detail View to Show the Back Button
File: Detail.view.xml
xml
CopyEdit
<Page
title="{i18n>detailPageTitle}"
showNavButton="true"
navButtonPress=".onNavBack">
showNavButton="true" adds a back button to the top-left.
navButtonPress=".onNavBack" connects the button to your controller’s logic (onNavBack() function).
🧠 2. Implement the Back Button Logic in Controller
File: Detail.controller.js
First: Add this import at the top
js
"sap/ui/core/routing/History"
Then: Add the method
js
onNavBack() {
const oHistory = History.getInstance(); // 1. Get SAPUI5 history object
const sPreviousHash = oHistory.getPreviousHash(); // 2. Get previous page’s route (if exists)
if (sPreviousHash !== undefined) {
window.history.go(-1); // 3. Go back using browser history
} else {
const oRouter = this.getOwnerComponent().getRouter(); // 4. If no previous route, go to overview
manually
oRouter.navTo("overview", {}, true); // 5. Replace current page (not add new one)
}
}
🔍 Let’s Break it Down Simply
Line What it Does
History.getInstance() Gets the navigation history of the app
getPreviousHash() Tries to find the last page the user was on
window.history.go(-1) If we do have a previous page → go back using browser
navTo("overview", {}, true) If we don’t have history (user came from bookmark), go to the Overview page
true (3rd param) Replaces the current page in browser history (to avoid stacking back pages)
🔁 Example Scenarios
🧭 Scenario 1:
You open the app normally → click invoice → go to detail page → press Back.
✅ It goes back to the Overview page.
🔗 Scenario 2:
You open the Detail page directly via a URL or bookmark
✅ It still takes you to Overview page when you click Back!
This is smart navigation using router + history check.
📌 Best Practices Recap
Practice Why It Matters
showNavButton="true" Clearly shows a back button in mobile/desktop UIs
Use History.getPreviousHash() Makes app navigation smarter (not just browser-dependent)
Fallback with router.navTo() Ensures app doesn’t get stuck if no browser history exists
Use replace = true Prevents adding unnecessary steps to browser history stack
📦 Code Summary
🔹 In Detail.view.xml:
xml
CopyEdit
<Page showNavButton="true" navButtonPress=".onNavBack">
🔹 In Detail.controller.js:
js
CopyEdit
onNavBack() {
const oHistory = History.getInstance();
const sPreviousHash = oHistory.getPreviousHash();
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
const oRouter = this.getOwnerComponent().getRouter();
oRouter.navTo("overview", {}, true);
}
}
✅ What Is a Custom Control in SAPUI5?
A Custom Control in SAPUI5 is like building your own Lego piece using smaller Lego blocks (standard
controls like buttons, labels, etc.).
It's a reusable UI component that:
Groups multiple standard controls
Encapsulates the logic and layout
Can be used in any view just like built-in SAPUI5 controls
❓Why Do We Need Custom Controls?
Reuse the same UI + logic in multiple places without rewriting
Group multiple UI elements (like Rating + Label + Button) into one unit
Keep the code cleaner and organized
Make components modular and easy to maintain
In this example, we create a ProductRating custom control that lets the user:
Give a star rating
See helpful messages like “Thank you for your rating!”
Submit the rating using a button
⚙️How It Works — Step by Step
🏗 1. Create the Control File
📁 File: webapp/control/ProductRating.js
js
sap.ui.define([
"sap/ui/core/Control"
], (Control) => {
return Control.extend("ui5.walkthrough.control.ProductRating", {
metadata: {},
init() {},
renderer(oRM, oControl) {}
});
});
This is the skeleton of a custom control.
It has 3 parts:
Part Purpose
metadata Defines properties, aggregations, events
init() Called once when control is created — used to prepare inner controls
renderer() Tells SAPUI5 how to draw the control on screen
🧠 2. Add Logic and Structure to ProductRating
We add:
One property: value (float) → the rating
Three internal controls (aggregations):
o _rating: A RatingIndicator (star rating)
o _label: A Label (text like “Your rating: 4 out of 5”)
o _button: A Button to submit the rating
One event: change → fired when rating is submitted
🛠 3. init() Method – Prepare Internal Controls
js
init() {
this.setAggregation("_rating", new RatingIndicator({
value: this.getValue(),
iconSize: "2rem",
visualMode: "Half",
liveChange: this._onRate.bind(this)
}));
this.setAggregation("_label", new Label({
text: "{i18n>productRatingLabelInitial}"
}).addStyleClass("sapUiSmallMargin"));
this.setAggregation("_button", new Button({
text: "{i18n>productRatingButton}",
press: this._onSubmit.bind(this)
}).addStyleClass("sapUiTinyMarginTopBottom"));
}
This creates:
A star-rating widget
A message label
A "Rate" button
🧾 4. renderer() Method – Draw HTML
js
renderer(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.class("myAppDemoWTProductRating");
oRm.openEnd();
oRm.renderControl(oControl.getAggregation("_rating"));
oRm.renderControl(oControl.getAggregation("_label"));
oRm.renderControl(oControl.getAggregation("_button"));
oRm.close("div");
}
This wraps everything in a <div> and renders the 3 inner controls.
🔁 5. Helper Methods
Method What it Does
setValue() Updates rating value + inner control
reset() Resets rating to 0; re-enables rating + button
_onRate() Called when user changes the rating; updates label text
_onSubmit() Called when button is clicked; disables rating and shows thank-you; fires change event
💡 Where Do We Use the Control?
📄 In Detail View (Detail.view.xml)
xml
<mvc:View
xmlns:wt="ui5.walkthrough.control">
<wt:ProductRating
id="rating"
class="sapUiSmallMarginBeginEnd"
change=".onRatingChange"/>
</mvc:View>
Add a namespace: xmlns:wt="..." for custom controls
Add <wt:ProductRating> to use it just like any standard control
🧠 In Detail Controller (Detail.controller.js)
1. Reset rating when new invoice is shown
js
onObjectMatched(oEvent) {
this.byId("rating").reset();
...
}
2. Show a message when rating is submitted
js
onRatingChange(oEvent) {
const fValue = oEvent.getParameter("value");
MessageToast.show("You have rated this product with " + fValue + " stars");
}
🎨 Style It: style.css
css
.myAppDemoWTProductRating {
padding: 0.75rem;
}
.myAppDemoWTProductRating .sapMRI {
vertical-align: initial;
}
This adds space and ensures rating stars are aligned nicely.
🌍 i18n Strings
properties
productRatingLabelInitial=Please rate this product
productRatingLabelIndicator=Your rating: {0} out of {1}
productRatingLabelFinal=Thank you for your rating!
productRatingButton=Rate
ratingConfirmation=You have rated this product with {0} stars
All text is stored in one place to support multi-language.
📦 Summary Table
Item Purpose
metadata Defines control structure
aggregations Inner controls (rating, label, button)
init() Creates and sets up internal controls
renderer() Renders the HTML
setValue() Updates rating
reset() Resets to original state
onRate() Updates label on star change
onSubmit() Disables control and fires event
📌 Best Practices
Rule Why
Put custom controls in /control folder Keeps project clean
Use setAggregation() and getAggregation() To manage child controls properly
Use visibility: "hidden" Keeps internal controls private
Fire custom events like change Allows external views to respond
Use i18n model Makes app multilingual-ready
✅ What Is Responsiveness in SAPUI5?
Responsiveness means the app looks and works well on all screen sizes:
📱 Mobile (Phone)
💊 Tablet
💻 Desktop
In SAPUI5, many controls (like sap.m.Table) are built to be responsive, so they automatically adjust their
layout on different screen sizes.
❓Why Make an App Responsive?
Because:
Users access apps from phones, tablets, and desktops
If an app isn't responsive, it looks broken or cluttered on small screens
A good user experience = users can read, click, and scroll comfortably
SAPUI5 gives built-in tools (like minScreenWidth and demandPopin) so we can hide/show columns or move
them below the main content on small screens.
⚙️How Do We Make Our App Responsive?
We replace our regular <List> with a responsive <Table> that:
Automatically adjusts layout
Hides less important columns on small screens
Moves secondary columns into a “pop-in” area
🧱 Step-by-Step Breakdown
🔁 1. Replace <List> with <Table>
xml
CopyEdit
<Table
id="invoiceList"
class="sapUiResponsiveMargin"
width="auto"
items="{
path: 'invoice>/Invoices',
sorter: { path: 'ShipperName', group: true }
}">
class="sapUiResponsiveMargin" → Adds spacing on all screen sizes
width="auto" → Makes the table shrink/grow based on screen size
📊 2. Define Columns with Responsiveness
Inside <columns>, define what shows on which screen:
xml
CopyEdit
<Column
hAlign="End"
minScreenWidth="Small"
demandPopin="true"
width="5em">
<Text text="{i18n>columnQuantity}" />
</Column>
Property Meaning
hAlign="End" Aligns text to the right
minScreenWidth="Small" Shows this column on Small (Tablet+) only
demandPopin="true" Shows this column below the main column on phones
Repeat this for all 5 columns:
1. Quantity – hidden on small screens (pop-in below name)
2. Name – always shown
3. Status – hidden on phones (pop-in)
4. Supplier – hidden completely on phones
5. Price – always shown
🧱 3. Replace <ObjectListItem> with <ColumnListItem>
xml
CopyEdit
<ColumnListItem type="Navigation" press=".onPress">
<cells>
<ObjectNumber number="{invoice>Quantity}" />
<ObjectIdentifier title="{invoice>ProductName}" />
<Text text="...Status..." />
<Text text="{invoice>ShipperName}" />
<ObjectNumber number="...Price..." unit="{view>/currency}" />
</cells>
</ColumnListItem>
Each <cell> corresponds to a column.
We use these controls inside:
ObjectNumber for quantity and price
ObjectIdentifier for product name
Text for status and supplier
📥 4. Handle Responsive Features
The real power comes from these two attributes on <Column>:
Attribute What It Does
minScreenWidth Tells the table when to show or hide a column
demandPopin If true, puts hidden column data under the row as pop-in
🈶 5. Add Labels to i18n
Add this to i18n.properties:
properties
invoiceListTitle=Invoices
invoiceStatusA=New
invoiceStatusB=In Progress
invoiceStatusC=Done
columnQuantity=Quantity
columnName=Name
columnSupplier=Supplier
columnStatus=Status
columnPrice=Price
These are labels for table headers.
👁 Preview: How It Adapts
Screen Size View
🖥 Desktop All columns visible
💊 Tablet Some columns pop in below
📱 Phone Less important columns hidden or shown below the row
Try shrinking your browser window to test it!
📌 Summary Table
Concept Meaning
sap.m.Table Responsive table with columns
ColumnListItem Holds data for each row
minScreenWidth Controls when to show columns
demandPopin Moves column data below rows
sapUiResponsiveMargin Adds padding/margins on all devices
📋 Best Practices (Conventions)
Use sap.m.Table instead of List for multiple columns
Use minScreenWidth and demandPopin for each column
Prioritize important data (e.g., Product Name, Price)
Test your app on different screen sizes
✅ What Is Device Adaptation?
Device Adaptation means customizing the visibility and behavior of UI elements based on the type of device
(📱 phone, 💊 tablet, 💻 desktop) that the app is running on.
For example:
Hide some buttons on phones to save space.
Collapse panels automatically on small screens.
Change layout or styling depending on device type.
❓Why Do We Need It?
Because:
Apps should work differently on a mobile phone vs. desktop.
Mobile devices have limited screen space.
Users expect a smooth and simple experience based on their device.
SAPUI5 provides a special object called sap.ui.Device to detect device details (like if it’s a phone or tablet) and
adapt accordingly.
⚙️How Is It Implemented in SAPUI5?
We use:
sap.ui.Device → to detect device type
A JSON model (called device) → for data binding in views
sapUiVisibleOnlyOnDesktop → to hide/show controls based on device
expandable & expanded bindings → to collapse panels on phones
🧱 Step-by-Step Breakdown
🧩 1. Create a device Model in Component.js
js
import Device from "sap/ui/Device";
const oDeviceModel = new JSONModel(Device);
oDeviceModel.setDefaultBindingMode("OneWay");
this.setModel(oDeviceModel, "device");
Device contains information like system.phone, system.desktop, os.name, etc.
Set OneWay binding because we only want to read (not write to) this model.
📌 Now this model can be used anywhere in your app like this:
xml
{device>/system/phone}
🪟 2. Use device Model in the HelloPanel View
xml
CopyEdit
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto"
expandable="{device>/system/phone}"
expanded="{= !${device>/system/phone} }">
On phones, the panel is collapsible
On larger screens, it’s expanded by default
👁 3. Conditionally Show or Hide Buttons
xml
CopyEdit
<Button
id="helloDialogButton"
icon="sap-icon://world"
text="{i18n>openDialogButtonText}"
press=".onOpenDialog"
class="sapUiSmallMarginEnd sapUiVisibleOnlyOnDesktop"/>
CSS class sapUiVisibleOnlyOnDesktop hides this button on phones/tablets
You can also use sapUiHideOnPhone, sapUiVisibleOnlyOnTablet, etc.
📌 No need to write JS logic—just assign the CSS class!
🧾 4. Use Responsive Features in ObjectHeader
In Detail.view.xml:
xml
CopyEdit
<ObjectHeader
responsive="true"
fullScreenOptimized="true"
...>
These make the ObjectHeader look clean and optimized on all devices.
fullScreenOptimized adjusts how info is shown based on screen size.
📅 5. Format and Bind Data for Display
Display a formatted ShippedDate in a human-friendly way:
xml
CopyEdit
<ObjectAttribute
title="{i18n>dateTitle}"
text="{
path: 'invoice>ShippedDate',
type: 'Date',
formatOptions: {
style: 'long',
source: {
pattern: 'yyyy-MM-ddTHH:mm:ss'
}
}
}"/>
The source.pattern tells SAPUI5 how the date looks in the backend
style: 'long' makes it readable like: July 6, 2025
💱 6. Use Currency with Formatter
Show product price properly using:
xml
CopyEdit
number="{
parts: [
'invoice>ExtendedPrice',
'view>/currency'
],
type: 'Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"
This pulls currency (EUR) from the view model
📂 7. Add i18n Labels
In i18n.properties:
properties
CopyEdit
dateTitle=Order date
quantityTitle=Quantity
🧠 What You’ve Learned in Step 35
Concept What it does
sap.ui.Device Detects the type of device (phone, tablet, desktop)
JSONModel(Device) Wraps device info as a model for binding
expandable/expanded Controls panel behavior on small screens
sapUiVisibleOnlyOnDesktop Hides or shows controls based on device
responsive & fullScreenOptimized Adjust ObjectHeader layout based on screen
type: 'Date' Formats backend date string into readable format
🧪 Testing Tips
In Google Chrome:
1. Right-click → Inspect
2. Click the 📱 "Toggle device toolbar" icon
3. Choose a phone or tablet
4. Refresh the page (important!)
5. You’ll see changes like the panel collapsed or button hidden
📌 Summary
✅ This step teaches how to adapt UI dynamically based on device type using:
Device detection (sap.ui.Device)
Expression binding
SAPUI5 responsive controls
CSS classes for device visibility
✅ What is Content Density?
Content Density refers to how big or small SAPUI5 UI controls appear on screen based on the type of
device:
Density Mode Description Devices
Small controls, less space, more
sapUiSizeCompact 🖱 Desktop (mouse, keyboard)
data visible
📱 Mobile / 💊 Tablet (touch
sapUiSizeCozy Larger controls, touch-friendly
screen)
(There’s also sapUiSizeCondensed – not
commonly used)
❓Why Is It Needed?
Different users interact with your app in different ways:
On a desktop, users prefer smaller controls to fit more data.
On a tablet/phone, bigger controls make it easier to tap with fingers.
So, we automatically switch between compact and cozy layout using the Device API.
🧠 What Will You Learn?
How to detect touch devices
How to apply the right CSS class to change content density
How to configure content density support in manifest.json
🔧 Step-by-Step Explanation
🧩 1. Add Helper Method in Component.js
js
CopyEdit
getContentDensityClass() {
return Device.support.touch ? "sapUiSizeCozy" : "sapUiSizeCompact";
}
📌 What it does:
Checks if the device supports touch
Returns the correct CSS class for density
o "sapUiSizeCozy" for touch devices
o "sapUiSizeCompact" for desktops
🪟 2. Apply the CSS Class in App.controller.js
js
CopyEdit
onInit() {
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
}
📌 What it does:
When the main app view is initialized
Adds the class (sapUiSizeCompact or sapUiSizeCozy) to the <body> or main container
All child controls will automatically follow this density
🗂 3. Configure Supported Densities in manifest.json
json
CopyEdit
"contentDensities": {
"compact": true,
"cozy": true
}
📌 Why?
If the app is deployed to SAP Fiori Launchpad (FLP), FLP will know:
o That your app supports both densities
o It can switch between them depending on the user’s device
✅ End Result
Device Applied Class UI Experience
Desktop (mouse) sapUiSizeCompact Smaller UI controls, more data shown
Touch (mobile/tablet) sapUiSizeCozy Larger, easier to tap UI elements
🧪 Try It Yourself
On desktop browser: You’ll see compact layout
Open Dev Tools → Toggle Device Toolbar → Refresh → You’ll see cozy layout (if touch is detected)
You can inspect the main DOM node and see which CSS class is applied (sapUiSizeCompact or
sapUiSizeCozy)
🧠 Summary
Concept Purpose
Device.support.touch Detects if user is on a touch device
sapUiSizeCompact Content density for desktops
sapUiSizeCozy Content density for touch devices
addStyleClass() Applies the density class to your app view
manifest.json > contentDensities Tells SAP Fiori which modes are supported
✅ What is Accessibility in SAPUI5?
Accessibility means making your application usable by everyone, including:
Visually impaired users
People using screen readers
Users with motor disabilities
SAPUI5 helps us make the app accessible by adding ARIA (Accessible Rich Internet Applications)
attributes.
🎯 Goal of This Step
To add ARIA landmarks so that screen readers can easily identify page structure like:
Header
Content area
Sections (like panels)
🧠 What Are ARIA Landmarks?
Think of landmarks as navigation shortcuts for screen reader users — like signs in a building. Examples:
📌 Region – A general labeled section
📌 Main – The main content
📌 Banner – The header or top section
🔧 Step-by-Step Explanation
✅ 1. Update Overview.view.xml
xml
CopyEdit
<Page title="{i18n>homePageTitle}">
<landmarkInfo>
<PageAccessibleLandmarkInfo
rootRole="Region"
rootLabel="{i18n>Overview_rootLabel}"
contentRole="Main"
contentLabel="{i18n>Overview_contentLabel}"
headerRole="Banner"
headerLabel="{i18n>Overview_headerLabel}" />
</landmarkInfo>
🔍 This assigns ARIA roles:
rootRole = Region → Labels the whole page
headerRole = Banner → Marks the header/title
contentRole = Main → Marks the page content area
📘 Labels are taken from i18n file (you'll add that soon).
✅ 2. Update InvoiceList.view.xml
xml
CopyEdit
<Panel accessibleRole="Region">
<headerToolbar>
<Toolbar>
<Title text="{i18n>invoiceListTitle}"/>
🧩 Why this change?
We wrapped the table and toolbar inside a <Panel> with role = Region
This helps screen readers understand that this is a meaningful section with its own title
✅ 3. Update HelloPanel.view.xml
xml
CopyEdit
<Panel
headerText="{i18n>helloPanelTitle}"
accessibleRole="Region">
✔ Just add accessibleRole="Region" to the existing Panel
✅ 4. Add Labels in i18n/i18n.properties
properties
CopyEdit
# Overview Page
Overview_rootLabel=Overview Page
Overview_headerLabel=Header
Overview_contentLabel=Page Content
🔤 These texts are used by screen readers to describe each region clearly.
📈 Result
🔴 Before Accessibility:
No meaningful structure for screen readers
Hard to navigate by keyboard or voice reader
✅ After Accessibility:
Clear sections:
o Overview Page
o Header
o Page Content
o Panel regions (HelloPanel and InvoiceList)
Improved navigation for all users – including those with disabilities
📌 SAPUI5 Control Used
Control Purpose
PageAccessibleLandmarkInfo Adds ARIA roles to <Page>
Panel accessibleRole="Region" Marks panels as meaningful screen reader regions
🔍 Tip for Testing
You can test ARIA landmarks using:
Chrome → Install Landmark Navigation Extension.
Or use screen reader tools like NVDA (Windows) or VoiceOver (Mac).
🧠 Summary
Concept Purpose
aria-* roles Improve screen reader experience
PageAccessibleLandmarkInfo Adds roles to Page (e.g., Banner, Main)
accessibleRole="Region" Defines important Panel sections
i18n Labels Used as screen reader descriptions
✅ Goal of This Step
To build your SAPUI5 app and test the final, production-ready version.
Built apps are:
Faster (minified)
Smaller (optimized)
Ready to deploy on servers like SAP BTP or on-premise systems
🧱 1. Add Build Script in package.json
You modify your package.json to include a build command using the UI5 CLI.
🔧 package.json (Updated)
json
CopyEdit
{
"name": "ui5.walkthrough",
"version": "1.0.0",
"description": "The UI5 walkthrough application",
"scripts": {
"start": "ui5 serve -o test/mockServer.html",
"build": "ui5 build --all --clean-dest"
},
"devDependencies": {
"@ui5/cli": "^3",
"ui5-middleware-simpleproxy": "^3"
}
}
⚙ What these do:
"build" → Runs ui5 build to generate the production app in a dist/ folder.
o --all → Includes SAPUI5 libraries (like sap.ui.core)
o --clean-dest → Clears old build files
2. Run the Build
Open your terminal inside the project root folder and run:
bash
CopyEdit
npm run build
✔️It creates a dist/ folder with the optimized application.
🌐 3. Test the Built Application
To test it like it’s in production, you need a static web server.
Install local-web-server:
bash
CopyEdit
npm install -D local-web-server
Add this new script to package.json:
json
CopyEdit
"serve-dist": "ws --compress -d dist --open"
Full scripts section now looks like:
json
CopyEdit
"scripts": {
"start": "ui5 serve -o test/mockServer.html",
"build": "ui5 build --all --clean-dest",
"serve-dist": "ws --compress -d dist --open"
}
▶️4. Serve the Built App
Now run:
bash
CopyEdit
npm run serve-dist
🧭 This:
Opens your default browser
Hosts the dist/ folder with the built UI5 app
Shows the index.html (but you need mockServer.html to see data)
📝 Important Note
local-web-server doesn’t support backend proxying.
So to see your sample invoice data, open manually:
bash
CopyEdit
http://localhost:8000/test/mockServer.html
✅ This will show your HelloPanel and InvoiceList in the built version!
🎉 Congratulations!
You've now:
Built the UI5 app like a real production project
Hosted it using a lightweight web server
Practiced industry-level skills in SAP Fiori development
🧭 What's Next?
Now that you completed the SAPUI5 Walkthrough, you can:
Explore Routing & Navigation in depth
Learn OData service integration
Build Fiori Elements apps
Explore deployment to SAP BTP
🔑 Step-by-Step Key Concepts (1–38)
Step Key Concept Simple Explanation
You need a starting point — a simple HTML file acts like a wrapper for
1 Basic HTML
your SAPUI5 app.
Connect your HTML to SAPUI5 libraries using <script> and define the
2 Bootstrap SAPUI5
UI5 version.
3 Basic Control (Button) UI5 controls like Button are predefined UI elements you can use easily.
Instead of putting everything in HTML, we use Views (XML, JSON, JS)
4 View
for cleaner UIs.
5 Controller Controllers handle logic. If View is the face, Controller is the brain.
Model-View-Controller keeps UI (View), data (Model), and logic
6 MVC Pattern
(Controller) separate.
7 Fragments Reusable UI blocks (like Dialogs) that don’t have controllers.
8 Dialog A pop-up window (like alert box) using sap.m.Dialog.
Store all text (labels, titles) in a .properties file so the app supports multiple
9 i18n - Internationalization
languages.
10 Models Structure and manage data using JSONModel, like a mini database in UI.
Connect UI to data using {} syntax (e.g., {/recipient/name} shows data
11 Data Binding
from model).
12 Two-Way Binding If user types in input, data updates and vice versa.
Add simple logic inside bindings (e.g., {= ${price} > 100 ? 'High' :
13 Expression Binding
'Low' }).
14 Event Handlers Functions in controllers that respond to user actions like button click.
15 Component.js Entry point of UI5 app; used to set up router, models, etc.
16 Routing Navigation between views (pages) using URLs.
The app’s configuration file — like a blueprint that defines routes, models,
17 manifest.json
views, etc.
18 App View The parent view that contains navigation container (<App> or <Shell>).
19 Multiple Views Learn how to switch between views using routing.
20 Mock Data Fake backend data (JSON) used for development and testing.
21 Table Used to display lists of data with rows and columns.
22 Sorter Automatically sort items in a table or list.
23 Filter Show only specific data based on conditions.
24 Group Group list/table items based on a property like Vendor name.
Format raw data into user-friendly form (e.g., converting status codes to
25 Formatter Functions
text).
26 Custom Controls Your own reusable UI elements.
Component-based
27 A modular, maintainable way to build large apps with reusable parts.
Architecture
28 Shell Control SAP standard layout that includes header, user menu, and content area.
Step Key Concept Simple Explanation
App Navigation (Back
29 Use navButtonPress to navigate back programmatically.
Button)
30 ObjectHeader A fancy header showing object info like title, status, price, etc.
31 Custom Events Trigger your own events like rating change.
32 Custom Control (Rating) Learn how to create and use custom UI5 controls.
Fragments are UI blocks without their own lifecycle; views are standalone
33 Fragments vs Views
screens.
34 Responsive Table Using sap.m.Table to adapt to screen sizes by hiding/showing columns.
35 Device Model (sap.ui.Device) Detect device type (phone, tablet, desktop) and adapt UI accordingly.
36 Content Density Use compact layout for desktop (mouse), cozy for touch devices (mobile).
37 Accessibility (ARIA) Add attributes that help screen readers understand your app.
38 Build the App Use ui5 build to generate a minified, optimized version for production.
🧠 What You MUST Understand as a Fresher
Here’s your Top 10 Essentials before jumping into real SAPUI5/Fiori Projects:
1. MVC Pattern
➡ Understand how Views, Controllers, and Models work together.
2. Data Binding (JSONModel)
➡ How data is connected to the UI using {} syntax.
3. Routing & Navigation
➡ Moving between pages using URLs and route configuration.
4. Component.js & manifest.json
➡ App entry point and app metadata/configuration.
5. i18n & Accessibility
➡ Write clean, globalized, accessible apps.
6. Table, Filter, Sorter, Formatter
➡ Display and manage data properly.
7. Fragments & Dialogs
➡ Use modular UI for pop-ups and reusability.
8. Responsive UI
➡ Design for all devices (desktop, tablet, mobile).
9. Device Model & Content Density
➡ Dynamically adapt layout for different device types.
10. Build & Deploy
➡ Understand how to prepare app for production using ui5 build.
🗂 Bonus Tips
Concept Learn This Next
OData Connect real backend services
Smart Controls Auto-generated controls from metadata
SAP Fiori Elements Build apps with low code
BTP Deployment Host your app on SAP Business Technology Platform
💡 Big Picture: The Main Structure
pgsql
index.html ⟶ Component.js ⟶ manifest.json
CopyEdit
⬇
Views (.view.xml)
⬇
Controllers (.controller.js)
Models (JSONModel)
🧩 1. index.html – App Entry Point
Role: This is the first file the browser opens.
What it does:
Loads SAPUI5 libraries (sap.ui.core, sap.m)
Boots up the app by pointing to the Component.js
html
CopyEdit
<script>
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name: "ui5.walkthrough"
}).placeAt("content");
});
</script>
✅ Key Connection:
name: "ui5.walkthrough" matches the namespace defined in the folder structure.
It runs Component.js (like the engine of the app).
🧩 2. Component.js – Main Engine of the App
Role: Initializes the app, sets models, and starts routing.
What it does:
Loads the manifest.json
Sets global models (like recipient, device)
Starts the router (navigation)
js
CopyEdit
this.getRouter().initialize(); // triggers manifest routing
✅ Key Connection:
Loads manifest.json
Sets up global data models (this.setModel(...))
Used by views like HelloPanel or InvoiceList
🧩 3. manifest.json – App Configuration File
Role: Tells SAPUI5:
What views to load
What routes are available
What models to register
What root view to show
json
CopyEdit
"sap.ui5": {
"rootView": {
"viewName": "ui5.walkthrough.view.App",
...
},
"routing": {
"routes": [
{
"pattern": "",
"name": "overview",
"target": "overview"
}
],
"targets": {
"overview": {
"viewName": "Overview"
}
}
}
}
✅ Key Connection:
Defines initial view (App.view.xml)
Maps URL to view navigation
Registers models (e.g., invoice, device)
🧩 4. App.view.xml – Root View
Role: Loads subviews like HelloPanel and InvoiceList.
xml
CopyEdit
<content>
<mvc:XMLView viewName="ui5.walkthrough.view.HelloPanel"/>
<mvc:XMLView viewName="ui5.walkthrough.view.InvoiceList"/>
</content>
✅ Key Connection:
Directly uses other views
Starts the UI building
🧩 5. Views (.view.xml) & Controllers (.controller.js)
Each view:
Has its own controller file with the same name
Uses data from models
Binds controls to that data
Example:
HelloPanel.view.xml uses:
xml
CopyEdit
<Input value="{/recipient/name}" />
HelloPanel.controller.js has:
js
CopyEdit
onShowHello: function () {
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
}
✅ Key Connection:
View binds to data in models
Controller handles logic (button clicks, filtering, etc.)
🧩 6. i18n/i18n.properties – Text Storage
Used for:
Multilingual support
Clean separation of text
Example:
xml
CopyEdit
<Title text="{i18n>invoiceListTitle}" />
✅ Key Connection:
Injected as a named model in manifest.json
json
CopyEdit
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "ui5.walkthrough.i18n.i18n"
}
}
}
🧩 7. Models (JSONModel) – Data Layer
Defined in Component.js:
js
CopyEdit
const oModel = new JSONModel({ recipient: { name: "World" } });
this.setModel(oModel);
✅ Used in Views:
xml
CopyEdit
<Input value="{/recipient/name}" />
🧩 8. Build Files (dist/)
When you run:
bash
CopyEdit
npm run build
It creates the dist/ folder with:
Minified version of your app
Optimized HTML, JS, CSS
✅ Use with:
bash
CopyEdit
npm run serve-dist
🎯 Visual Connection Flow
pgsql
CopyEdit
⬇ boots
index.html
⬇ loads
Component.js
⬇ defines
manifest.json
⬇ includes
App.view.xml
⬇ ⬇
HelloPanel.view.xml InvoiceList.view.xml
⬇ ⬇
HelloPanel.controller.js InvoiceList.controller.js
⬇ ⬇
JSONModel i18n Model
View Binding Text from i18n.properties
✅ Summary for Freshers
File Role Connects to
index.html Boots the app Component.js
Component.js Initializes app manifest.json, Models
manifest.json Configuration App.view.xml, routing, i18n
Views UI layout Controllers, Models
Controllers Logic Views, Events
Models (JSON) App data Views (via binding)
i18n.properties Labels/text Views (via {i18n>...})
Fragments Reusable UI blocks Called by controllers
dist/ Build output Used for deployment
Would you like a diagram image of this structure for easy revision or a one-page cheat sheet PDF? I can
make it for you.
You said:
in the folder structure we don't have the file named ui5.walkthrough
ChatGPT said:
That's a great observation! You're 100% right — there is no file named ui5.walkthrough in the folder
structure. Let me explain why it's written like that and how it works behind the scenes in very simple terms.
🔍 What is ui5.walkthrough?
ui5.walkthrough is not a file — it is a namespace that maps to the folder structure of your project.
🧠 Think of it like this:
When we write:
js
name: "ui5.walkthrough"
We're telling SAPUI5:
"Look for files inside the webapp/ folder using this path as a guide:
webapp/ui5/walkthrough/..."
So, it maps to this folder structure:
pgsql
CopyEdit
webapp/
└── ui5/
└── walkthrough/
├── Component.js
├── controller/
│ ├── App.controller.js
│ └── HelloPanel.controller.js
├── view/
│ ├── App.view.xml
│ ├── HelloPanel.view.xml
└── model/
└── formatter.js
✅ Why is this used?
Namespaces help SAPUI5 load files correctly using the viewName, controllerName, or manifest routing.
For example:
xml
<mvc:XMLView viewName="ui5.walkthrough.view.HelloPanel"/>
➡ SAPUI5 looks for:
pgsql
CopyEdit
webapp/ui5/walkthrough/view/HelloPanel.view.xml
Another example:
js
CopyEdit
return Controller.extend("ui5.walkthrough.controller.HelloPanel", {
➡ SAPUI5 looks for:
bash
CopyEdit
webapp/ui5/walkthrough/controller/HelloPanel.controller.js
📦 Where is the namespace defined?
In the ComponentContainer inside index.html:
js
new sap.ui.core.ComponentContainer({
name: "ui5.walkthrough", // <-- Namespace
async: true
})
AND
In manifest.json:
json
CopyEdit
"sap.app": {
"id": "ui5.walkthrough"
}
This is how SAPUI5 knows:
"The base folder for everything is webapp/, and I’ll look for files in folders following this namespace pattern."
🔁 So the actual connection is:
Namespace Maps To Folder
ui5.walkthrough webapp/ui5/walkthrough/
ui5.walkthrough.view.App webapp/ui5/walkthrough/view/App.view.xml
ui5.walkthrough.controller.App webapp/ui5/walkthrough/controller/App.controller.js
✅ Summary for Freshers
ui5.walkthrough is a namespace, not a file.
It matches the folder path under webapp/.
It helps SAPUI5 locate views, controllers, and models automatically.
Always make sure your folder names match the namespace exactly.
✅ What are Controls in SAP UI5?
Think of controls like building blocks of your app UI.
Examples:
sap.m.Button → A button
sap.m.Input → A text input field
sap.m.Table → A table
sap.m.ObjectIdentifier → For showing title + subtitle
sap.m.Text → For displaying text
Example:
xml
<Button text="Click Me" press="onButtonPress" />
🧠 This creates a button with label Click Me, and triggers a function when clicked.
✅ What are Formatters?
A formatter is used to modify the data before displaying it on the UI.
📌 Why use it?
Let’s say the backend gives status as:
"A", "B", "C"
But we want to show:
✅ "New", 🔄 "In Progress", ✅ "Done".
You use a formatter function to convert values like this.
✅ Step-by-Step: How to Create and Use a Formatter
🔧 1. Create a formatter file
📁 File path: webapp/model/formatter.js
js
sap.ui.define([], function () {
"use strict";
return {
statusText: function (sStatus) {
switch (sStatus) {
case "A":
return "New";
case "B":
return "In Progress";
case "C":
return "Done";
default:
return sStatus;
}
}
};
});
🧩 2. Use the formatter in the XML view
First, import the formatter using core:require in your XML:
xml
CopyEdit
<Text
core:require="{ Formatter: 'ui5/walkthrough/model/formatter' }"
text="{
path: 'invoice>Status',
formatter: 'Formatter.statusText'
}" />
✅ This binds the field invoice>Status to the text control, but modifies it using the formatter before showing it.
🧠 What Actually Happens:
Raw Value Formatter UI Output
"A" statusText() "New"
"B" statusText() "In Progress"
"C" statusText() "Done"
💡 Another Example: Conditional Formatter
Let’s say we want to color the price red if it’s above 1000.
In formatter.js:
js
CopyEdit
priceColor: function (price) {
return price > 1000 ? "Error" : "Success";
}
In View:
xml
CopyEdit
<ObjectNumber
number="{invoice>Price}"
state="{
path: 'invoice>Price',
formatter: 'Formatter.priceColor'
}"
/>
✅ Summary
Concept Description
Control A UI element (button, input, text, table, etc.)
Formatter A function that changes how raw data is displayed
Where to put it webapp/model/formatter.js
How to use Use core:require in view, bind with formatter function
Overview of Data Flow
📦 Model (data source like JSON/OData)
↓
🎨 Formatter (optional - modifies how data appears)
↓
🧱 UI Control (displays data in view - XML/JS)
↔
🎮 Controller (handles user interaction, events)
🔁 Real-World Example: Product Table with Status
Let’s say your app displays a list of products with:
Product Name
Status ("A", "B", "C") — needs formatting
Price — shown with currency
1️⃣ 📦 Model (JSON/OData)
JSON File (e.g. Invoices.json):
json
CopyEdit
{
"Invoices": [
{
"ProductName": "Laptop",
"Status": "A",
"Price": 1500
},
{
"ProductName": "Mouse",
"Status": "C",
"Price": 300
}
]
}
Connected in Component.js:
js
CopyEdit
const oModel = new JSONModel("model/Invoices.json");
this.setModel(oModel, "invoice");
✅ Now your view can use: invoice>ProductName, invoice>Status, etc.
2️⃣ 🎨 Formatter (optional)
📁 webapp/model/formatter.js:
js
CopyEdit
sap.ui.define([], () => {
"use strict";
return {
statusText: function (sStatus) {
switch (sStatus) {
case "A": return "New";
case "B": return "In Progress";
case "C": return "Done";
default: return sStatus;
}
}
};
});
3️⃣ 🧱 UI Control (View)
In InvoiceList.view.xml:
xml
CopyEdit
<Text
core:require="{ Formatter: 'ui5/walkthrough/model/formatter' }"
text="{
path: 'invoice>Status',
formatter: 'Formatter.statusText'
}"
/>
✅ This will take "A" from model, pass it to Formatter.statusText("A"), and show "New" on the screen.
4️⃣ 🎮 Controller
The controller does NOT directly change how model data appears — but it:
Handles events like button clicks
Can update the model
Can react to model changes
Example in InvoiceList.controller.js:
js
CopyEdit
onFilterInvoices(oEvent) {
const sQuery = oEvent.getParameter("query");
const oTable = this.byId("invoiceList");
const oBinding = oTable.getBinding("items");
if (sQuery) {
const oFilter = new Filter("ProductName", FilterOperator.Contains, sQuery);
oBinding.filter([oFilter]);
} else {
oBinding.filter([]);
}
}
✅ This reads the user input, and filters the model data shown in the table.
🔄 Complete Flow (Diagram Style)
pgsql
CopyEdit
Model
├── JSON file: "Laptop", "A", 1500
└── Bound to → View (XML)
View (Control)
├── Text bound to invoice>Status
├── Uses Formatter: Formatter.statusText("A") = "New"
└── UI displays "New"
User clicks "Search" → Controller onFilterInvoices() triggered
└── Controller filters Model → View updates automatically
✅ Summary Table
Layer Purpose Involved Files
Model Stores data Invoices.json, Component.js
Formatter Transforms data for UI formatter.js
View Displays controls, uses bindings InvoiceList.view.xml
Controller Handles logic, events InvoiceList.controller.js
Do you want me to show a live example using a table + formatter + input field + controller all working
together? Or do you want a visual diagram image for this flow?
You said:
give live example
ChatGPT said:
Perfect! Here's a complete working example of a SAPUI5 app that shows:
✅ Data Flow: Model → Formatter → View (Control) → Controller
It has:
A table showing products
A formatter to convert status codes ("A", "B", "C") into readable text
A search field in the toolbar
A controller that filters based on the search
✅ Folder Structure
pgsql
CopyEdit
webapp/
├── controller/
│ └── App.controller.js
├── model/
│ └── formatter.js
├── view/
│ └── App.view.xml
├── Component.js
├── index.html
├── manifest.json
└── model/
└── Products.json
✅ model/Products.json
json
CopyEdit
{
"Products": [
{
"ProductName": "Laptop",
"Status": "A",
"Price": 1500
},
{
"ProductName": "Mouse",
"Status": "B",
"Price": 300
},
{
"ProductName": "Monitor",
"Status": "C",
"Price": 800
}
]
}
✅ model/formatter.js
js
CopyEdit
sap.ui.define([], function () {
"use strict";
return {
statusText: function (sStatus) {
switch (sStatus) {
case "A": return "New";
case "B": return "In Progress";
case "C": return "Done";
default: return sStatus;
}
}
};
});
✅ view/App.view.xml
xml
CopyEdit
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
controllerName="ui5.walkthrough.controller.App"
xmlns:core="sap.ui.core"
displayBlock="true">
<Page title="Product List">
<headerContent>
<SearchField
placeholder="Search Product..."
search=".onFilterProducts"/>
</headerContent>
<Table
id="productTable"
items="{path: 'product>/Products'}">
<columns>
<Column><Text text="Name" /></Column>
<Column><Text text="Status" /></Column>
<Column><Text text="Price" /></Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{product>ProductName}" />
<Text
core:require="{formatter: 'ui5/walkthrough/model/formatter'}"
text="{
path: 'product>Status',
formatter: 'formatter.statusText'
}" />
<Text text="{product>Price}" />
</cells>
</ColumnListItem>
</items>
</Table>
</Page>
</mvc:View>
✅ controller/App.controller.js
js
CopyEdit
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (Controller, Filter, FilterOperator) {
"use strict";
return Controller.extend("ui5.walkthrough.controller.App", {
onInit: function () {
const oModel = new sap.ui.model.json.JSONModel("model/Products.json");
this.getView().setModel(oModel, "product");
},
onFilterProducts: function (oEvent) {
const sQuery = oEvent.getParameter("query");
const oTable = this.byId("productTable");
const oBinding = oTable.getBinding("items");
if (sQuery) {
const oFilter = new Filter("ProductName", FilterOperator.Contains, sQuery);
oBinding.filter([oFilter]);
} else {
oBinding.filter([]);
}
}
});
});
✅ Component.js
js
CopyEdit
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel"
], function (UIComponent, JSONModel) {
"use strict";
return UIComponent.extend("ui5.walkthrough.Component", {
metadata: {
manifest: "json"
},
init: function () {
UIComponent.prototype.init.apply(this, arguments);
}
});
});
✅ index.html
html
CopyEdit
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SAPUI5 App - Formatter Demo</title>
<script
src="https://sdk.openui5.org/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-libs="sap.m"
data-sap-ui-resourceroots='{
"ui5.walkthrough": "./webapp"
}'
data-sap-ui-compatVersion="edge"
data-sap-ui-onInit="module:ui5/walkthrough/Component"
data-sap-ui-async="true">
</script>
</head>
<body class="sapUiBody" id="content"></body>
</html>
✅ manifest.json
json
CopyEdit
{
"sap.app": {
"id": "ui5.walkthrough",
"type": "application",
"title": "Formatter Demo",
"applicationVersion": {
"version": "1.0.0"
}
},
"sap.ui5": {
"rootView": {
"viewName": "ui5.walkthrough.view.App",
"type": "XML",
"async": true,
"id": "app"
},
"dependencies": {
"minUI5Version": "1.108",
"libs": {
"sap.m": {},
"sap.ui.core": {}
}
},
"resources": {},
"models": {}
}
}
✅ What This Does
Feature How It Works
Model JSON file loaded and set as named model "product"
View Binds data using product>ProductName, etc.
Formatter Translates "A" into "New", "C" into "Done"
Controller Filters product table by name when user types in search