Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Do NOT continue with the Next Steps of the tutorial web page. They will guide you into a different learning
path as anticipated by this SAP CodeJam.
TTC: 5 min
Steps
1.
2.
3.
4.
TTC: 10 15 min
Steps
1.
2.
3.
4.
Further Reading:
-
TTC: 5 10 min
Description
Due to cross-site scripting restrictions in JavaScript, we cannot connect to a remote URL from our local app. Instead
we need to make sure our requests are sent via some proxy.
A proxy is simply a local script that connects to the remote server and returns the response to our app. In a
productive scenario this would of course be a direct connection. We use the proxy to access the Northwind test
service from OData.org.
First, we need to configure a destination for a remote service in SAP Web IDE. When using such a destination,
requests to our service will be proxied for us automatically. The URL in the app descriptor requires a corresponding
destination configuration.
Steps
1.
Do NOT continue with the Next Steps of the tutorial web page. They will guide you into a different learning
path as anticipated by this SAP CodeJam.
2.
Modify the app descriptor files for SAPUI5 and SAP HCP as described below to accomplish automatic
instantiation of the OData Model. The automatic instantiation is available since SAPUI v1.30.
Changes
webapp/manifest.json
-
Within the sap.app section, add a new section for dataSoruces. The section will include our main service
(Northwind) and a path to the API endpoint. With this approach, there is no need for you to instantiate the OData
model in the controller anymore.
Save your changes with Ctrl + S or by hitting the Save button
{
"_version": "1.1.0",
"sap.app": {
. . .
"sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"version": "1.30.3"
},
"dataSources": {
"mainService": {
"uri": "/destinations/Northwind/V3/Northwind/Northwind.svc",
"type": "OData"
}
}
},
. . .
"sap.ui5": {
. . .
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "HelloWorld.i18n.i18n"
}
},
"" : {
"dataSource": "mainService"
}
}
}
neo-app.json
-
To ensure the app descriptor of SAPUI5 redirects the mainService url to the SAP HCP destination, your
project descriptor requires a new route definition
Save your changes with Ctrl + S or by hitting the Save button
. . .
{
"path": "/test-resources",
"target": {
"type": "service",
"name": "sapui5",
"entryPath": "/test-resources"
},
"description": "SAPUI5 Test Resources"
},
{
"path": "/destinations/Northwind",
"target": {
"type": "destination",
"name": "Northwind"
},
"description": "Northwind Destination"
}
],
"sendWelcomeFileRedirect": true
}
Now run your application (use the RUN button at the top of the editor screen).
Next, open your Developer Tools to see if a HTTP request was made to the Northwind service. We suggest you to
use Chrome, in which the developer tools are accessible through the menu View > Developer > Developer
Tools. As soon as your application was loaded, switch to the Network tab of the Developer Tools and re-load
the application. You will see your requests and responses listed. You can now use the Filter text area to show
only requests to Northwind. You should see one request and the associated response, as depicted in the screen
below.
Further Reading:
-
TTC: 5 min
Preview
Description
What were going to do in this exercise is to add a list to first XML view (View1.xml). The list will display data from
the data model, that got filled with data from the Northwind data service. Every list element will represent a product
entity in the data model. Thanks to the data binding, SAPUI5 takes the path of the aggregation and automatically
creates as many list items as the aggregation includes (all the product entities).
For now, we will just display the product names, more specifically the attribute ProductName. As were planning
to have a detail view with additional information per list element, we will also implement a mock function for the
press event, which is triggered on key-press or tap on mobile devices.
For that matter, well change the master view and controller. To fix a design-flaw of the template, we will also quickly
update the CSS styling.
Changes
webapp/view/View1.view.xml
-
type="Active"
press="handleListItemPress"
title="{ProductName}" />
</List>
</content>
</Page>
</core:View>
webapp/css/style.css
Set the section overflow parameters to initial in order to avoid hiding the content of the Page in the XML view.
.sapMMessageDialog {
width: 18rem;
}
.sapMPage>section {
overflow-x: initial !important;
overflow-y: initial !important;
}
webapp/controller/View1.controller.js
-
Add the event handler function for the press event, as just defined in the XML view
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("HelloWorld.controller.View1", {
handleListItemPress: function (evt) {
// show in a popup which list element was pressed
alert("Pressed: " + evt.getSource().getBindingContext());
}
});
});
Now re-run the app and try pressing on a list item. A popup will appear. Check out the text in the popup window.
Further Reading:
-
Data Binding:
https://sapui5.netweaver.ondemand.com/#docs/guide/68b9644a253741e8a4b9e4279a35c247.html
List: https://sapui5.netweaver.ondemand.com/#docs/guide/295e44b2d0144318bcb7bdd56bfa5189.html
StandardListItem:
https://sapui5.netweaver.ondemand.com/explored.html#/entity/sap.m.StandardListItem/properties
TTC: 10 - 15 min
Description
You have to add a routing configuration to the descriptor file and initialize the router instance within the component
definition (webapp/Component.js).
Changes
webapp/view/App.view.xml
Create a new view for the wrapper of your web app in the webapp/view folder (App.view.xml). The only content
of the App view is the App instance. This app will contain the different views, injected as pages into the App
instance
Save your changes with Ctrl + S or by hitting the Save button
<mvc:View
controllerName="HelloWorld.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App id="app"/>
</mvc:View>
webapp/controller/App.controller.js
Create a new controller for the wrapper of your web app in the folder webapp/controller (App.controller.js)
Save your changes with Ctrl + S or by hitting the Save button
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("HelloWorld.controller.App", {
});
});
webapp/manifest.json
Modify the app descriptor to re-define the root view. Instead of the View1, it will be our new wrapper view App
Add the routing configuration listed below. The configuration contains a standard route to the View1
Save your changes with Ctrl + S or by hitting the Save button
"sap.ui5": {
"_version": "1.1.0",
"rootView": {
"viewName": "HelloWorld.view.App",
"type": "XML"
},
. . .
"models": {
. . .
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "HelloWorld.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide"
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}],
"targets": {
"home": {
"viewName": "View1",
"viewLevel" : 1
}
}
}
}
}
This exercise demonstrates why we need to wrap our web app into an App view & controller. The App controller
acts as navigation enabler because it contains the app instance.
webapp/Component.js
Paste the router instantiation into the init method of the component
Save your changes with Ctrl + S or by hitting the Save button
. . .
return UIComponent.extend("HelloWorld.Component", {
metadata: {
manifest: "json"
},
/**
* . . .
*/
init: function() {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// set the device model
this.setModel(models.createDeviceModel(), "device");
// create the views based on the url/hash
this.getRouter().initialize();
},
. . .
webapp/css/style.css
Reset the style to ensure a unified web experience across devices
Save your changes with Ctrl + S or by hitting the Save button
.spacerTop {
margin-top: 2rem;
}
.sap-tablet .sapMDialog,
.sap-desktop .sapMDialog {
min-width: 18rem;
}
.sapMMessageDialog {
width: 18rem;
}
Further Reading:
-
TTC: 10 15 min
Preview
Description
In order to improve our web app, we would like to display a more detail information for each sales order / list item.
We will therefore create a new view and controller for a detail screen. The detail screen will receive the ID of the list
item and will access the data model in order to get the requested data.
For that matter, well add a Detail view and controller as well as alter the View1 view and controller.
Changes
webapp/view/View1.view.xml
-
webapp/view/View1.controller.js
SAP CodeJam: Building SAPUI5 Applications using SAP WebIDE
Modify the handleListItemPress method to receive the ProductID of the selected list item
Use the router method navTo with the selected ProductID to navigate to the Detail view through the appDetail
navigation target
Save your changes with Ctrl + S or by hitting the Save button
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("HelloWorld.controller.View1", {
handleListItemPress: function (evt) {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
var selectedProductId =
evt.getSource().getBindingContext().getProperty("ProductID");
oRouter.navTo("appDetail", {
productId: selectedProductId
});
}
});
});
webapp/manifest.json
-
Add a new navigation pattern which will get receive a mandatory parameter for the ProductId
Add a new target for the Detail view and increase the viewLevel by one.
Save your changes with Ctrl + S or by hitting the Save button
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "HelloWorld.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide"
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "detail/{productId}",
"name": "appDetail",
"target": "detail"
}],
"targets": {
"home": {
"viewName": "View1",
"viewLevel" : 1
},
"detail": {
"viewName": "Detail",
"viewLevel" : 2
}
}
}
}
}
webapp/view/Detail.view.xml
<mvc:View
controllerName="HelloWorld.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc" >
<Page
title="{i18n>DetailTitle}"
showNavButton="true"
navButtonPress="handleNavButtonPress" >
<VBox>
<Text text="{ProductName}" />
<Text text="{UnitPrice}" />
<Text text="{QuantityPerUnit}" />
<Text text="{UnitsInStock}" />
</VBox>
</Page>
</mvc:View>
As were focusing on the separation of MVC, the definition of views with XML has a key benefit: You cant get
confused and mix up business logic (usually inside the controller) with the view implementation because XML doesnt
allow you to define methods to be executed. It requires you to refer to a method, which should be defined in the
controller. Another aspect is that XML appears to be easier to read and write, as its a markup language. The SAPUI5
community prefers this type of view definition and in fact, most of the code samples in the documentation are using
XML.
Inside of the page declaration, we are making use of the VBox UI component. VBox is a vertical aligned Flexbox
element. Its essentially a way to arrange all the elements inside of the VBox in a vertical order. You can imagine it as
rows in a table. Every UI element will be placed below the earlier ones. In our case, we will place four Text elements
in a vertical order.
Another important aspect is the declaration of showNavButton and the related navButtonPress event. While the
first one enables the visibility of a back button, the latter one defines the function to be executed when this button
is pressed. In our case, we assign the function handleNavButtonPress as event handler. This function will be
implemented in the detail controller.
webapp/controller/Detail.controller.js
-
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("HelloWorld.controller.Detail", {
onInit: function () {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("appDetail").attachMatched(this._onRouteMatched, this);
},
_onRouteMatched : function (oEvent) {
var oArgs, oView;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
path : "/Products(" + oArgs.productId + ")",
events : {
dataRequested: function (oEvent) {
oView.setBusy(true);
},
dataReceived: function (oEvent) {
oView.setBusy(false);
}
}
});
},
handleNavButtonPress : function (evt) {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("appHome");
}
});
});
As mentioned already, we will implement one function to handle the back button press event. This method will use
the navTo method of the router and initiate the navigation via the appHome route. The onInit method is more
complex. The method expects a navigation parameter for the ProductId. It hooks into the appDetail route and
executes the _onRouteMatched method to extract the ProductId and set the data binding for the Detail view.
Further Reading:
-
Changes
webapp/i18n/i18n.properties
-
title=Product Overview
appTitle = App Title
appDescription=App Description
# Detail View
DetailTitle = Product Details
Further Reading:
-
Data-Binding:
http://help.sap.de/saphelp_uiaddon10/helpdata/en/91/f0f3cd6f4d1014b6dd926db0e910
70/content.htm
Internationalization (i18n):
http://help.sap.com/saphelp_hanaplatform/helpdata/en/b6/d1a9511f994b3a86e2f34a32
e40a34/content.htm
TTC: 5 - 10 min
Preview
Description
In this exercise we will replace a couple of controls one in the Vie1 and the other in the Detail view.
In the Master view, rather than the simple flat list item style presented by the StandardListItem control that is in
use currently, well present the overview of the products in a more appealing way by using the ObjectListItem
control instead.
In the Detail view, well make a similar change, replacing the simple layout (currently afforded by the VBox
control) with a more readable display thanks to the ObjectHeader control.
Along the way well add one more property to the i18n model (currency).
Changes
webapp/view/View1.view.xml
-
Please take a few minutes to evaliuate the text and state property ot the ObjectStatus declaration. We are using
Expression Binding to define a more readable UI. Check out this slides to get yourself familiar with this new SAPUI5
feature.
webapp/i18n/i18n.properties
title=Product Overview
appTitle = App Title
appDescription=App Description
currency=EUR
# Detail View
DetailTitle = Product Details
webapp/view/Detail.view.xml
Replace the VBox holding the texts with the more beautiful ObjectHeader control (which has almost the
same API as the ObjectListItem control but utilizes the space in a different way)
<mvc:View
controllerName="HelloWorld.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc" >
<Page
title="{i18n>DetailTitle}"
showNavButton="true"
navButtonPress="handleNavButtonPress" >
<ObjectHeader
title="{ProductName}"
number="{= ((${UnitPrice} * 100) / 100).toFixed(2) }"
numberUnit="{i18n>currency}" >
<statuses>
<ObjectStatus
text="{= ${Discontinued}? 'Discontinued' : 'Available' }"
state="{= ${Discontinued}? 'Error' : 'Success' }" />
<ObjectStatus
text="{= (${UnitsOnOrder} > 0)? 'Ordered' : '' }"
state="Success" />
</statuses>
<attributes>
<ObjectAttribute text="Product #{ProductID}" />
<ObjectAttribute text="Category #{CategoryID}" />
<ObjectAttribute text="Supplier #{SupplierID}" />
</attributes>
</ObjectHeader>
</Page>
</mvc:View>
Further Reading:
-
ObjectListItem:
https://sapui5.hana.ondemand.com/explored.html#/entity/sap.m.ObjectListItem/sam
ples
TTC: 5 min
Preview
Description
Were going to add a SearchField control to the initial page of the application. Well add it as a child within the
Pages subHeader aggregation which expects a Bar (sap.m.Bar) control.
To handle the search, well specify a handler for the search fields search event. This handler handleSearch is
defined in the views controller, and the search effect is achieved by adding a contains string filter to the binding
of the List controls items aggregation.
Changes
webapp/view/View1.view.xml
-
The search field is put into a bar that is placed in the sub header of the page
Do not forget to add an id to the list in order to access the list later on in the controller
webapp/view/View1.controller.js
-
Implement a new handler function in the View1 controller. Make sure to separate the function from
the other handler function with a ,
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("HelloWorld.controller.View1", {
handleListItemPress: function (evt) {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
var selectedProductId =
evt.getSource().getBindingContext().getProperty("ProductID");
oRouter.navTo("appDetail", {
productId: selectedProductId
});
},
handleSearch : function (evt) {
// create model filter
var filters = [];
var query = evt.getParameter("query");
if (query && query.length > 0) {
var filter = new sap.ui.model.Filter("ProductName",
sap.ui.model.FilterOperator.Contains, query);
filters.push(filter);
}
// update list binding
var list = this.getView().byId("list");
var binding = list.getBinding("items");
binding.filter(filters);
}
});
});
In case the search does not work, check whether you forgot to add the id for the List in View1.view.xml.
Further Reading:
-
SearchField:
https://sapui5.hana.ondemand.com/explored.html#/entity/sap.m.SearchField/samples
Model Filter: https://sapui5.hana.ondemand.com/docs/api/symbols/sap.ui.model.Filter.html
TTC: 5 min
Preview
Description
We need to add a footer bar (a Bar control within the footer aggregation of the Page) to our Detail view.
Well add a Button control to the right side of the footer bar in the Detail view, and in the corresponding
controller well define the function to be called (handleOrder) when the Buttons press event is fired. Well just
simulate the order process by displaying a MessageBox popup control and then showing a MessageToast. For
this, well need to show some texts. Hence, well add placeholders into the i18n.properties file we set up earlier.
Changes
webapp/i18n/i18n.properties
-
title=Product Overview
appTitle = App Title
appDescription=App Description
currency=EUR
# Detail View
DetailTitle = Product Details
OrderButtonText=Order
OrderDialogTitle=Order Product
webapp/view/Detail.view.xml
-
Add a footer to the Detail page which holds the button to trigger the order activity
<mvc:View
controllerName="HelloWorld.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc" >
<Page
title="{i18n>DetailTitle}"
showNavButton="true"
navButtonPress="handleNavButtonPress" >
<ObjectHeader
. . .
</ObjectHeader>
<footer>
<Bar>
<contentRight>
<Button
text="{i18n>OrderButtonText}"
type="Accept"
icon="sap-icon://accept"
press="handleOrder" />
</contentRight>
</Bar>
</footer>
</Page>
</mvc:View>
webapp/controller/Detail.controller.js
-
First, we need to register two more class on top of our Detail controller. This makes them accessible in
out controller definition without the need to instantiate them
On handling the order event, we first show a confirmation dialog (MessageBox)
If the user confirms, we only show a success message (MessageToast).
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageBox",
"sap/m/MessageToast"
], function(Controller, MessageBox, MessageToast) {
"use strict";
return Controller.extend("HelloWorld.controller.Detail", {
. . .
handleNavButtonPress : function (evt) {
. . .
},
handleOrder : function (evt) {
// show confirmation dialog
var bundle = this.getView().getModel("i18n").getResourceBundle();
MessageBox.confirm(
bundle.getText("OrderDialogMsg"),
function (oAction) {
if (MessageBox.Action.OK === oAction) {
// notify user
var successMsg = bundle.getText("OrderDialogSuccessMsg");
MessageToast.show(successMsg);
// TODO call proper service method and update model (not
part of this session)
}
},
bundle.getText("OrderDialogTitle")
);
}
});
});
Nicely done! This step concludes the guided part of the CodeJam. Now, take on some of the additional challenges on
the next page!
Further Reading
-
Footer: https://sapui5.hana.ondemand.com/explored.html#/sample/sap.m.sample.Page/preview
MessageBox: https://sapui5.hana.ondemand.com/docs/api/symbols/sap.m.MessageBox.html
MessageToast: https://sapui5.hana.ondemand.com/docs/api/symbols/sap.m.MessageToast.html
Additional Challenges
1.
2.
3.
4.
5.
6.