0% found this document useful (0 votes)
34 views112 pages

DAT375 SupportingMaterial

Uploaded by

gsaikrishna
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views112 pages

DAT375 SupportingMaterial

Uploaded by

gsaikrishna
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 112

End-to-End Native Application Development for

SAP HANA
DAT375

Exercises / Solutions
Rich Heilman, SAP Labs, LLC.
Thomas Jung, SAP Labs, LLC.
Uri Nizan, SAP Labs Israel Ltd.
Sushma Prabhakar, SAP LABS INDIA PVT. LTD.
Nachshon Vagmayster, SAP Labs Israel Ltd.
DAT375

TABLE OF CONTENTS
BEFORE YOU START ......................................................................................................................................3
Getting Help .....................................................................................................................................................3
Source Code Solutions ....................................................................................................................................3
EXERCISE 1 – HELLO WORLD ........................................................................................................................4
EXERCISE 1 – SOLUTION................................................................................................................................5
Exercise 1.1: HTML5 Module - Hello World.....................................................................................................5
EXERCISE 2 –DATABASE ARTIFACT DEVELOPMENT................................................................................13
EXERCISE 2 – SOLUTION..............................................................................................................................14
Exercise 2.1: Database Content ....................................................................................................................14
Exercise 2.2: Create Tables & Views via Core Data Services ......................................................................20
Exercise 2.3: Table Data Configuration ........................................................................................................27
Exercise 2.4: Create Structured Privilege & Role .........................................................................................31
Exercise 2.5: Non-Container Schemas, User Provided Services & Synonyms...........................................34
Exercise 2.6: Cross Container Services & Synonyms .................................................................................43
Exercise 2.7: Creating a Graphical Calculation View ...................................................................................48
Exercise 2.8: Stored Procedure.....................................................................................................................56
EXERCISE 3 –XSJS AND XSODATA SERVICES ...........................................................................................61
EXERCISE 3 – SOLUTION..............................................................................................................................62
Exercise 3.1: XSJS and XSODATA ................................................................................................................62
Exercise 3.2: Creating an OData Service with Create Operation and XSJS Exit .........................................72
EXERCISE 4 – NODE.JS ................................................................................................................................75
EXERCISE 4 – SOLUTION..............................................................................................................................76
Exercise 4.1: Modules and Express ..............................................................................................................76
Exercise 4.2: HANA Database Access from Node.js ....................................................................................83
EXERCISE 5 – SAPUI5 USER INTERFACE....................................................................................................88
EXERCISE 5 – SOLUTION..............................................................................................................................89
Exercise 5.1: SAPUI5 as an XSA Service Broker ..........................................................................................89
Exercise 5.2: SAPUI5 User Interface .............................................................................................................92
EXERCISE 6 – PACKAGING FOR TRANSPORT.......................................................................................... 108
EXERCISE 6 – SOLUTION ............................................................................................................................ 109
Exercise 6.1: Package for Transport ........................................................................................................... 109

2
DAT375

BEFORE YOU START

System Host: wdflbmt0794.wdf.sap.corp


System Instance Number: 00
System User ID: DAT375
All Passwords: WelcomeSAP2018
XSA Organization: XS
XSA Development Space: DEV

Getting Help
If you need addition help resources beyond this document, we would suggest the following content:
• The Online Help at https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.01/en-US

Source Code Solutions

All source code solutions and templates for all exercises in this document can be found in the following
webpage.
https://github.com/jungsap/TechEd2018.DAT375/tree/snippets/

In some cases, it might be a little easier to copy and paste the coding instead of typing it all manually. If
copying/pasting, I would suggest that you make sure to understand what is being copied/pasted before moving
on.

You can also view the completed workshop with a sample solution here:
https://github.com/jungsap/TechEd2018.DAT375/tree/master

3
DAT375

EXERCISE 1 – HELLO WORLD

Objective

In this first exercise, we will connect to the remote system, run the new project wizard, and then create an HTML5
module to serve as the application endpoint and proxy all our services and client-side content. At the end of this
exercise you will be able to connect to your server via web browser and see a Hello World message.

4
DAT375

EXERCISE 1 – SOLUTION

Exercise 1.1: HTML5 Module - Hello World

Explanation Screenshot

1. Please click on the Remote


Login tile. You will be asked
for username and password –
please enter:
.\student
with password: Welcome18

2. Launch the SAP Web IDE


for SAP HANA at the
following URL in your web
browser.

https://wdflbmt0794.wdf.sa
p.corp:53075/

User: DAT375
Password:
WelcomeSAP2018

3. We will begin by creating a


new project from template.
Use File->New->Project
from Template

5
DAT375

4. Choose Multi-Target
Application Project and
then press Next.

5. Enter the project name


DAT375. Press Next.

6. You can optionally enter a


description for your new
application. Set the Space
to DEV (or your system
specific development space
chosen by your system
administrator). Then press
Next.

6
DAT375

7. Press Finish to complete


the wizard and generate
your new project.

8. Your empty project has


been created.

9. Next, we need to create the


HTML5 module to host and
serve out the frontend
content.

Begin by selecting your


project and then choosing
New -> Basic HTML5
Module

7
DAT375

10. Name the module web.


Press Finish.

11. This not only adds the


necessary metadata to the
web folder but also
maintains the module entry
in the project’s mta.yaml
file within the project root.

12. We need to create dat375-uaa


as a dependent
service/resource to the
mta.yaml file. This is because
we have user authentication
required to access our
application.

Click on the Resources tab.


Then click the plus button to
add a new resource entry. This
will make a new input field
appear below the plus button.
You can type the name of the
resource in here (dat375-uaa).

Then on the right-hand side you


can fill in the type
(com.sap.xs.uaa-space) and
parameters of the resource.

Save your file. Switch to the


Code Editor and then back to
the MTA Editor to refresh all the

8
DAT375

value helps.

13. Switch back to the Modules


tab and select your web
module. Click the plus
button in the Requires
section and choose the
newly created UAA
resources from the drop-
down list. This will create
the connection between the
web module and the UAA
resource.

14. You can also switch to the ID: DAT375


MTA editor code view via _schema-version: '2.0'
the button on the bottom description: SAP TechEd 2018 DAT375
left corner of the editor. version: 0.0.1
This will show you the modules:
complete view of the - name: web
content in text form. Use type: html5
this to double check your path: web
entries. requires:
- name: dat375-uaa
Note: if you don’t want to resources:
type this code, we - name: dat375-uaa
recommend that you cut type: com.sap.xs.uaa-space
and paste it from this web
address
https://github.com/jungsap/
TechEd2018.DAT375/blob/
snippets/ex1/ex1_1

9
DAT375

15. To simplify the exercise, we


have already pre-created
the dat375-uaa service for
you. But normally you
would create this from the
XS command line with the
following command:

xs create-service xsuaa
space <service name>

There is no action for you


to perform at this step.

16. The web folder in our


project contains the
resources that will be
served out by this HTML5
module. This HTML5
module manages all
HTML/client side UI
resources (in the resources
folder) and performs the
task of reverse proxy for all
other internal services. This
way you have a single
HTTP endpoint and avoid
any CORS issues.

The Add Module wizard


already placed a simple
index.html with “Hello
World” in the resources
folder.

Note: There is no task to


perform in this step. We
are just pointing out
content the wizard
created already.

17. The HTML5 module is {


configured via the file xs- "welcomeFile": "index.html",
app.json. In this file we "authenticationMethod": "route",
can map the routes to "routes": [ ]
destinations we defined in }
the mta.yaml. We can also
set authentication and
other options.

Let’s go ahead and change


the authenticationMethod

10
DAT375

to route and then save this


file.

Note: if you don’t want to


type this code, we
recommend that you cut
and paste it from this web
address
https://github.com/jungsap/
TechEd2018.DAT375/blob/
snippets/ex1/ex1_2

18. Our initial development is


done and we are ready to
deploy our application onto
the XS Advanced server.
Highlight the web folder
and press Run. This will
perform a build, then
deploy the service onto the
server. If successful it will
open a new browser tab to
the default page of this web
service.

19. It can take a minute or two


for the first build/deploy/run
operation to complete.
Upon completion, you
should see that the service
status has changed to
Running and there is a
hyper link to the run logs.

11
DAT375

20. Switch your browser tab


and you might see the
authentication prompt for
your application (if it has
been long enough for your
initial login to time out). If
so, login with the same
user credentials you used
to log into the SAP Web
IDE for SAP HANA (Hint:
You might not see this
screen if your
authentication is still
cached in the browser from
the login to the SAP Web
IDE for SAP HANA itself).

Authentication at the XS
level is now done by
referencing a user store.
This can be configured to
be the HANA database or it
can be an external user
directory.

21. After successful


authentication, you should
see your index.html with
the Hello World button.

22. Congratulations! You just


wrote your first XS
Advanced application.

12
DAT375

EXERCISE 2 –DATABASE ARTIFACT DEVELOPMENT

Objective

In this exercise, we will continue to develop our overall application. Applications in the HANA/XS Advanced
world, are often made up of multiple modules at design time which deploy to separate micro-services or
database container content. We created client-side UI application content in the first exercise using the HTML5
module. In this exercise, we will create database artifacts, such as database table, stored procedures and user
defined functions, using the HDB (HANA Database) module. We will then see how we build these database
artifacts using the new container-based, schema-less HDI (HANA Deployment Infrastructure) concepts.

Exercise Description

• Database Tables via HDBCDS


• Stored Procedures via HDBPROCEDURE
• User Defined Functions via HDBFUNCTION
• Initial table data load via CSV
• Deploy to HANA via HDI

13
DAT375

EXERCISE 2 – SOLUTION

Exercise 2.1: Database Content

Explanation Screenshot

1. New to HANA in SPS 11 is the


HANA Deployment
Infrastructure or HDI. The goal
of HDI is to manage database
artifacts from design time
objects but in a way that
allows multiple
copies/versions of the same
core objects to be used on the
same HANA database at the
same time.

HDI introduces the concept of


the container as an
abstraction of the Schema.
The container in turn
dynamically generates the
Schema, a container-specific
Database User who owns all
objects, and a password for
that database user. XS
Advanced based services then
only need access to the
container and never need to
know the actual Schema,
technical user, or password.
All that information is stored
within the container definition.

2. Choose View->Show Hidden


Files

14
DAT375

3. Begin by selecting your project


and then choosing New ->
SAP HANA Database
Module.

4. Name this new module db.


Then press Next.

5. The namespace for database


artifacts is optional in HDI and
we will choose not to use one.
Therefore, just clear the
Namespace field. Choose
the schema name
TECHED2018_DAT375. The
HANA Database Version
should default to the latest
version (2.0 SPS 03)
automatically. Then press
Finish.

15
DAT375

6. You will notice a new folder


called db which contains a src
folder with some files.

7. The wizard has created the db


folder as well as the hdi_db
resource and the db module in
the mta.yaml file for you.

8. A HANA database module


can also have more than
one database resource
associated with it.
However, it can only have
one primary resource which
is the target for all
database development
objects it will create. Later
we will add more resources
for cross container access.
This requires us to set this
resources as the

16
DAT375

TARGET_CONTAINER to
describe it as the primary
database resource for this
module.

properties:
TARGET_CONTAINER:
~{hdi-container-name}

Save the mta.yaml file.

9. Select the db module and


choose Build->Build.

10. The Run Console should


change to the Console.
You should see a message
that your build was
successful.

17
DAT375

11. Go to the Database


Explorer by choosing the
Database Explorer icon on
the left side of the SAP
Web IDE for SAP HANA.

12. You might be greeted by a


dialog asking if you want to
add a database connection.
If so, click Yes. If you don’t
receive this dialog, then
you can click the Add
button instead.

18
DAT375

13. Choose HDI Container as


your Database type.
Search for your container
based upon your User
Name. Set a display name
of DAT375-DB. Then click
OK.

14. You can now see your


container in which your
database artifacts will be
deployed.

19
DAT375

Exercise 2.2: Create Tables & Views via Core Data Services
In this exercise, you will create a Purchase Order header and item table as well as a view over those two tables
using Core Data Services(CDS).

Explanation Screenshot

1. Return to the SAP Web


IDE for SAP HANA,
Development perspective.

Create a new folder under


the src folder by right-
clicking it and choosing
New->Folder. Name the
folder data.

2. Now create the hdbcds artifact


via New->HDB CDS Artifact
to create the core database
tables and views in our
application.

3. Name the new CDS file


PurchaseOrder and press
Create.

20
DAT375

4. The CDS graphical editor


opens by default, however we
want to work with the code
based editor. Right mouse
click on the
PurchaseOrder.hdbcds and
choose Open With -> Code
Editor in order to switch.

5. HDBCDS contains the context PurchaseOrder {


table and view definitions. type BusinessKey : String(10);
We are creating a simple type SDate : LocalDate;
Purchase Order Header type CurrencyT : String(5);
and Item data model. type AmountT : Decimal(15, 2);
type QuantityT : Decimal(13, 3);
The syntax is the same as type UnitT : String(3);
CDS-based development type StatusT : String(1);
objects previously. type HistoryT {
@Comment : 'Created By'
Here is the code for CREATEDBY : BusinessKey;
PurchaseOrder.hdbcds @Comment : 'Created Date'
CREATEDAT : SDate;
Save the artifact after @Comment : 'Changed By'
entering this code. CHANGEDBY : BusinessKey;
@Comment : 'Change Date'
Note: if you don’t want to CHANGEDAT : SDate;
type this code, we };
recommend that you cut
and paste it from this web @Comment : 'Purchase Order Header'
address entity Header {
https://github.com/jungsap/ @Comment : 'Purchase Order ID'
TechEd2018.DAT375/blob/ key PURCHASEORDERID : Integer generated by default
snippets/ex2/ex2_1 as identity(start with 200000000 increment by 1 no minvalue
maxvalue 2999999999 no cache no cycle);
ITEMS : association[1, 0..*] to Item on
Header.PURCHASEORDERID = PURCHASEORDERID;
HISTORY : HistoryT;
@Comment : 'Notes'
NOTEID : BusinessKey null;
@Comment : 'Supplier'
PARTNER : BusinessKey;
@Comment : 'Currency'
CURRENCY : CurrencyT;
@Comment : 'Gross Amount'
GROSSAMOUNT : AmountT;
@Comment : 'Net Amount'
NETAMOUNT : AmountT;
@Comment : 'Tax Amount'

21
DAT375

TAXAMOUNT : AmountT;
@Comment : 'Lifecycle Status'
LIFECYCLESTATUS : StatusT;
@Comment : 'Approval Status'
APPROVALSTATUS : StatusT;
@Comment : 'Confirmation Status'
CONFIRMSTATUS : StatusT;
@Comment : 'Ordering Status'
ORDERINGSTATUS : StatusT;
@Comment : 'Invoicing Status'
INVOICINGSTATUS : StatusT;
}
technical configuration {
column store;
};

@Comment : 'Purchase Order Item'


entity Item {
key POHeader : association[*, 1..1] to Header {
PURCHASEORDERID };
@Comment : 'Product ID'
key PRODUCT : BusinessKey;
@Comment : 'Notes'
NOTEID : BusinessKey null;
@Comment : 'Currency'
CURRENCY : CurrencyT;
@Comment : 'Gross Amount'
GROSSAMOUNT : AmountT;
@Comment : 'Net Amount'
NETAMOUNT : AmountT;
@Comment : 'Tax Amount'
TAXAMOUNT : AmountT;
@Comment : 'Quantity'
QUANTITY : QuantityT;
@Comment : 'Quantity Unit'
QUANTITYUNIT : UnitT;
@Comment : 'Delivery Date'
DELIVERYDATE : SDate;
}
technical configuration {
column store;
// index PURCHASEORDER_ITEM_ID on
(POHeader.PURCHASEORDERID) asc;
};

@Comment : 'Purchase Order Item View'


define view ItemView as
select from Item
{
POHeader.PURCHASEORDERID as
"PurchaseOrderItemId",
POHeader.PARTNER as "PartnerId",
PRODUCT as "ProductID",

22
DAT375

CURRENCY as "CurrencyCode",
GROSSAMOUNT as "Amount",
NETAMOUNT as "NetAmount",
TAXAMOUNT as "TaxAmount",
QUANTITY as "Quantity",
QUANTITYUNIT as "QuantityUnit",
DELIVERYDATE as "DeliveryDate1"
}
with structured privilege check;
};

6. Let’s look at the syntax you context PurchaseOrder {


just entered into the hdbcds type BusinessKey : String(10);
file in more detail. type SDate : LocalDate;
type CurrencyT : String(5);
First, we need to define some type AmountT : Decimal(15,2);
reusable elemental types. type QuantityT : Decimal(13,3);
These will later be used to type UnitT: String(3);
define the data type of type StatusT: String(1);
individual columns in our
tables.

Within the PurchaseOrder


context, create element types
for BusinessKey, SDate,
CurrencyT, AmountT,
QuantityT, UnitT, and StatusT.

7. We can also create reusable type HistoryT {


structures with multiple fields. @Comment : 'Created By'
This is useful when the same CREATEDBY : BusinessKey;
sets of fields are repeated in
multiple tables. Create a @Comment : 'Created Date'
reusable structure for History CREATEDAT : SDate;
– with CREATEDBY,
CREATEDAT, CHANGEDBY, @Comment : 'Changed By'
and CHANGEDAT fields. CHANGEDBY : BusinessKey;

@Comment : 'Change Date'


CHANGEDAT : SDate;
};

8. The syntax for creating @Comment : 'Purchase Order Header'


Entities is like types. Entities entity Header {
will become database tables @Comment : 'Purchase Order ID'
when activating the hdbcds key PURCHASEORDERID : Integer generated by default
file. as identity(start with 200000000 increment by 1 no minvalue
maxvalue 2999999999 no cache no cycle);
ITEMS : association[1, 0..*] to Item on
Header.PURCHASEORDERID = PURCHASEORDERID;
HISTORY : HistoryT;

23
DAT375

@Comment : 'Notes'
NOTEID : BusinessKey null;

@Comment : 'Supplier'
PARTNER : BusinessKey;

@Comment : 'Currency'
CURRENCY : CurrencyT;

@Comment : 'Gross Amount'


GROSSAMOUNT : AmountT;

@Comment : 'Net Amount'


NETAMOUNT : AmountT;

@Comment : 'Tax Amount'


TAXAMOUNT : AmountT;

@Comment : 'Lifecycle Status'


LIFECYCLESTATUS : StatusT;

@Comment : 'Approval Status'


APPROVALSTATUS : StatusT;

@Comment : 'Confirmation Status'


CONFIRMSTATUS : StatusT;

@Comment : 'Ordering Status'


ORDERINGSTATUS : StatusT;

@Comment : 'Invoicing Status'


INVOICINGSTATUS : StatusT;
}
technical configuration {
column store;
};

24
DAT375

9. Save your file and build the db


module again. You can
choose to build just this one
file.

10.Switch over to the Database


Explorer and browse your
container, expand the Tables
and Views folders and explore
the new tables and view

11.Right-click on the
PurchaseOrder.Header table
and choose Open

25
DAT375

12.Explore the table definition,


notice the column names
where we used the complex
type definition called History

13.And you can query the data in


these objects via the Open
Data button. Although there
won’t be any yet. We will see
how we can load an initial set
of data in the next exercise.

14. If you try to access the data in


the view, however, even the
technical user of the container
doesn’t have access because
of the structured privilege we
placed upon it. We will solve
this later as well once we
setup some roles in our
container.

26
DAT375

Exercise 2.3: Table Data Configuration


In this exercise, you will create a table data configuration file which will load data into your tables during the
deploy process.

Explanation Screenshot

1. Return to the SAP Web IDE


for SAP HANA, Development
perspective. You may want to
deliver an initial set of data
within a table – a
configuration table. In this
exercise, we will learn how to
create automatic data load
configuration and the
accompanying CSV files for
just such a situation.

The data load for table


requires two files – 1. An csv
(comma separated) file which
holds the data you want to
load. 2. An hdbtabledata file
which specifies the target
table for a source csv file.

2. We also have the {


hdbtabledata development "format_version": 1,
object. The purpose is to allow "imports": [{
the loading of initial data from "target_table": "PurchaseOrder.Header",
CSV files it target tables "source_data": {
during their creation. "data_type": "CSV",
"file_name": "header.csv",
Create a file named "has_header": false,
Purchase.hdbtabledata "dialect": "HANA",
in the data folder and enter "type_config": {
this text into it. Don’t forget to "delimiter": ","
save the file afterwards. }
},
Note: if you don’t want to type "import_settings": {
this code, we recommend that "import_columns":
you cut and paste it from this ["PURCHASEORDERID",
web address "NOTEID",
https://github.com/jungsap/Tec "PARTNER",
hEd2018.DAT375/blob/snippet "CURRENCY",
s/ex2/ex2_2 "GROSSAMOUNT",
"NETAMOUNT",
"TAXAMOUNT",
"LIFECYCLESTATUS",
"APPROVALSTATUS",
"CONFIRMSTATUS",

27
DAT375

"ORDERINGSTATUS",
"INVOICINGSTATUS"]
},
"column_mappings": {
"PURCHASEORDERID": 1,
"NOTEID": 6,
"PARTNER": 7,
"CURRENCY": 8,
"GROSSAMOUNT": 9,
"NETAMOUNT": 10,
"TAXAMOUNT": 11,
"LIFECYCLESTATUS": 12,
"APPROVALSTATUS": 13,
"CONFIRMSTATUS": 14,
"ORDERINGSTATUS": 15,
"INVOICINGSTATUS": 16
}
},
{
"target_table": "PurchaseOrder.Item",
"source_data": {
"data_type": "CSV",
"file_name": "item.csv",
"has_header": false,
"dialect": "HANA",
"type_config": {
"delimiter": ","
}
},
"import_settings": {
"import_columns":
["POHeader.PURCHASEORDERID",
"PRODUCT",
"NOTEID",
"CURRENCY",
"GROSSAMOUNT",
"NETAMOUNT",
"TAXAMOUNT",
"QUANTITY",
"QUANTITYUNIT" ]
},
"column_mappings": {
"POHeader.PURCHASEORDERID": 1,
"PRODUCT": 3,
"NOTEID": 4,
"CURRENCY": 5,
"GROSSAMOUNT": 6,
"NETAMOUNT": 7,
"TAXAMOUNT": 8,
"QUANTITY": 9,
"QUANTITYUNIT": 10

28
DAT375

}]
}

3. We need some CSV files to 0500000000,0000000033,20120101,0000000033,20120101,9


hold some initial test data to 000000001,0100000000,EUR,13224.47,11113,2111.47,N,I,I,I,I
be loaded by the hdbtabledata 0500000001,0000000033,20120102,0000000033,20120102,9
configuration file. 000000001,0100000002,EUR,12493.73,10498.94,1994.79,N,I,
I,I,I
Enter this data into a file
named header.csv and save
it.

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex2/ex2_3

4. And data for the item table 0500000000,0000000010,HT-


named item.csv. Don’t forget 1000,,EUR,1137.64,956,181.64,1,EA,20121204
to save. 0500000000,0000000020,HT-
1091,,EUR,61.88,52,9.88,2,EA,20121204
Note: if you don’t want to type 0500000000,0000000030,HT-
this code, we recommend that 6100,,EUR,1116.22,938,178.22,2,EA,20121204
you cut and paste it from this 0500000000,0000000040,HT-
web address 1000,,EUR,2275.28,1912,363.28,2,EA,20121204
https://github.com/jungsap/Tec 0500000000,0000000050,HT-
hEd2018.DAT375/blob/snippet 1091,,EUR,92.82,78,14.82,3,EA,20121204
s/ex2/ex2_4 0500000000,0000000060,HT-
6100,,EUR,1116.22,938,178.22,2,EA,20121204
0500000000,0000000070,HT-
1000,,EUR,2275.28,1912,363.28,2,EA,20121204
0500000000,0000000080,HT-
1091,,EUR,61.88,52,9.88,2,EA,20121204
0500000000,0000000090,HT-
6100,,EUR,1674.33,1407,267.33,3,EA,20121204
0500000000,0000000100,HT-
1000,,EUR,3412.92,2868,544.92,3,EA,20121204
0500000001,0000000010,HT-
1100,,USD,213.96,179.8,34.16,2,EA,20121204
0500000001,0000000020,HT-
2026,,USD,35.69,29.99,5.7,1,EA,20121204
0500000001,0000000030,HT-
1002,,USD,3736.6,3140,596.6,2,EA,20121204
0500000001,0000000040,HT-
1100,,USD,213.96,179.8,34.16,2,EA,20121204
0500000001,0000000050,HT-
2026,,USD,71.38,59.98,11.4,2,EA,20121204

29
DAT375

0500000001,0000000060,HT-
1002,,USD,3736.6,3140,596.6,2,EA,20121204
0500000001,0000000070,HT-
1100,,USD,320.94,269.7,51.24,3,EA,20121204
0500000001,0000000080,HT-
2026,,USD,107.06,89.97,17.09,3,EA,20121204
0500000001,0000000090,HT-
1002,,USD,3736.6,3140,596.6,2,EA,20121204
0500000001,0000000100,HT-
1100,,USD,320.94,269.7,51.24,3,EA,20121204

5. Save all files and build your


hdb module again.

Return to the Database


Explorer perspective and right-
click on the
PurchaseOrder.Header table
and choose Open Data.

6. You will notice that your table


now has some data.

7. Repeat these steps to view


the data in the
PurchaseOrder.Item table as
well.

30
DAT375

Exercise 2.4: Create Structured Privilege & Role


In this exercise, you will create a structured privilege and role and assign it to you user.

Explanation Screenshot

1. Return to the SAP Web IDE


for SAP HANA, Development
perspective.

2. All access to our HDI


database objects from XSA is
done automatically by the HDI
container technical user.
However, if you want to allow
access via other database
users (for use cases such as
external reporting tools); you
must create a database role.
Also, if you want to grant
additional privileges to the
technical user (such as the
structured privileges for our
view), we will also need a
database role.

Return to the SAP Web IDE


for SAP HANA and create
another folder called roles by
right-clicking on the src folder
and choosing New->Folder.

3. First, we need to create a STRUCTURED PRIVILEGE


structured privilege. This is "PO_VIEW_PRIVILEGE"
closely related to the analytic FOR SELECT ON
privilege and allows us to "PurchaseOrder.ItemView"
perform instance filtering for WHERE "CurrencyCode" = 'EUR'
our CDS view we created
earlier.

Enter this code into the file


PurchaseOrder.hdbstructur
edprivilege in the roles folder
and save.

Note: if you don’t want to


type this code, we
recommend that you cut and
paste it from this web
address

31
DAT375

https://github.com/jungsap/Te
chEd2018.DAT375/blob/snip
pets/ex2/ex2_5

This will limit the access to


our view to only allow users
with this privilege to see
items for Euros.

4. Now create a role named {


admin.hdbrole and enter this "role":{
code. Don’t forget to save. "name": "admin",
"schema_privileges": [{
Note: if you don’t want to type "privileges": ["SELECT METADATA",
this code, we recommend that "SELECT CDS
you cut and paste it from this METADATA",
web address "SELECT",
https://github.com/jungsap/Tec "INSERT",
hEd2018.DAT375/blob/snippet "EXECUTE",
s/ex2/ex2_6 "DELETE",
"UPDATE",
"CREATE
TEMPORARY TABLE"
]
}],
"schema_analytic_privileges": [
{
"privileges":[ "PO_VIEW_PRIVILEGE" ]
}
]

}
}

5. We want this role to be {


automatically granted to our "role":{
technical user. Therefore, we "name": "default_access_role",
need to create a role with a "schema_roles": [{
special name – "names": ["admin"]
default_access_role. }]
}
Create a folder named }
defaults in src. Inside this
folder create a file named
default_access_role.hdbrole

Enter the following text. This


will grant the admin.hdbrole
we created in the previous
step to our technical user
automatically upon build.

Note: if you don’t want to type

32
DAT375

this code, we recommend that


you cut and paste it from this
web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex2/ex2_7

6. Save all files and build the db


module. Return to the
Database Explorer
perspective.

7. Try to access the data in the


view again and it should be
successful this time.

8. To access the structured


privilege based view or for a
database user other than the
technical user to access any
of the catalog objects; they
need the database role
granted to their user.

33
DAT375

Exercise 2.5: Non-Container Schemas, User Provided Services & Synonyms


In this exercise, we will create a synonym in order to access a table within a non-HDI container schema. This
exercise requires that you have the SFLIGHT catalog schema installed in your system and a database user who
has access to this schema. We will use the SFLIGHT schema to simulate an ERP schema in a typical scenario.

Explanation Screenshot

1. Right mouse click on the


database module and choose
Modeling Actions -> Add
External SAP HANA Service

1. A User Provided Service


allows our modules to
connect to existing
database schemas which
aren’t managed by HDI.
This would be the case for
replicated schemas or ERP
ABAP schemas for
instance.

Create a service named:


CROSS_SCHEMA_SFLIG
HT with host (localhost),
port (30015), User
(CUPS_SFLIGHT), and
Password (HanaRocks01).

This wizard will both create


the User Provided Service
for you and insert it as a
resource in your mta.yaml
file.

34
DAT375

2. Create a new folder called cfg


under the db folder.

3. Create a new file in the cfg


folder called
SFLIGHT.hdbgrants by right-
clicking on the cfg folder and
choosing New->File

4. Enter the following code. It is {


this object which will grant the "ServiceName_1": {
specified access from the
"object_owner" : {
SFLIGHT schema to the
container technical users. "schema_privileges":[
{
If you do not wish to type this "reference":"SFLIGHT",
code, you can reference the "privileges_with_grant_option":["SELECT", "SELECT
solution web page at METADATA"]
}
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet ]
s/ex2/ex2_10 },
"application_user" : {
"schema_privileges":[
{
"reference":"SFLIGHT",
"privileges_with_grant_option":["SELECT", "SELECT
METADATA"]
}
]
}
}
}

35
DAT375

5. Click Save

6. Go to the data folder within


the src folder and create a
new file called
sflight.hdbsynonym by right-
clicking on the data folder and
choosing New->File.

7. Click the Click to Add


hyperlink to add new entries.

8. Add the following entries as


shown.

36
DAT375

9. Click Save

10. Select the db module and


choose Build->Build. Check
to make sure the build is
successful.

11. Create another CDS artifact


in the data folder by right-
clicking on the data folder and
choosing New->HDB CDS
Artifact. Name it FLIGHT.

12. Insert the following code as


shown

If you do not wish to type this


code, you can reference the
solution web page at

https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex2/ex2_11

37
DAT375

13. Within the FLIGHT context,


insert the following code for
the view.

If you do not wish to type this


code, you can reference the
solution web page at

https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex2/ex2_11

14. After the SflightView


definition, enter the following
code for the SflightExt view.

If you do not wish to type this


code, you can reference the
solution web page at

https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex2/ex2_11

38
DAT375

15. The completed code should using SFLIGHT as FLIGHTTBL;


be very like the following. using SBOOK as BOOKINGS;

If you do not wish to type this context FLIGHT {


code, you can reference the define view SflightView as
solution web page at select from FLIGHTTBL
mixin
https://github.com/jungsap/Tec {
hEd2018.DAT375/blob/snippet SBOOKLink : association [ * ] to BOOKINGS on
s/ex2/ex2_11 SBOOKLink.MANDT = FLIGHTTBL.MANDT
and SBOOKLink.CARRID = FLIGHTTBL.CARRID
and SBOOKLink.CONNID = FLIGHTTBL.CONNID
and SBOOKLink.FLDATE = FLIGHTTBL.FLDATE;
}
into
{
MANDT as "Client",
CARRID as "CarrierId",
CONNID as "ConnectionId",
FLDATE as "FlightDate",
PRICE as "Price",
CURRENCY as "Currency",
PLANETYPE as "PlaneType",
SBOOKLink.BOOKID as "BookingId",
SBOOKLink.CUSTOMID as "CustomerId",
SBOOKLink.CUSTTYPE as "CustomerType",
SBOOKLink.PASSNAME as "PassengerName"
};

define view SflightExt as


select from SflightView
{
"Client",
"CarrierId",
"ConnectionId",
"FlightDate",
"BookingId",
"Price",
"Currency",
"PlaneType",
"CustomerId",
"CustomerType",
"PassengerName"
} with structured privilege check;

};

16. Click Save.

39
DAT375

17. Under the roles folder, create


a new structured privilege
called
SFLIGHT_PRIV.hdbstructure
dprivilege by right-clicking on
the roles folder and choosing
New->File.

18. Enter the following code as STRUCTURED PRIVILEGE


"FLIGHT_VIEW_PRIVILEGE"
shown. FOR SELECT ON
"FLIGHT.SflightExt"
If you do not wish to type this WHERE "Client" = '001' --JSON_VALUE((SELECT
code, you can reference the SESSION_CONTEXT('XS_CLIENT') FROM DUMMY), '$[0]')
solution web page at

https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex2/ex2_12

19. Click Save.

40
DAT375

20. Open the admin.hdbrole file in


the roles folder and add the
new structured privilege as
shown.

21. Click Save.

22. Select the db module and


choose Build->Build.

41
DAT375

23. Switch over to the Database


Explorer perspective. Right-
click on the FLIGHT.SflightExt
view in the Views folder and
choose Open Data.

24. The results are shown but


notice that only data from
Client 001 is displayed
because of the filter applied in
the Structured Privilege.

42
DAT375

Exercise 2.6: Cross Container Services & Synonyms


XSA embraces the concepts of a micro-service architecture. This means that different parts of the same overall
application might be separated into separate services. This is true also of the database services. So far, we
have purchase order data in our db module and access to the non-container schema, SFLIGHT. However, we
also want master data that already exists in a complete separate HDI container.

In this exercise, we will configure access and synonyms between our local project container and an already
existing container deployed on this system.

Explanation Screenshot

1. We want to add an existing


HDI service to our
container as well. Choose
Modeling Actions -> Add
External SAP HANA
Service.

2. Choose HDI Container


radio button at the top of
the dialog and then select
the service named
TechEd2018CentralDB.te
ched-2018-cc.

43
DAT375

4. Create a new file in the cfg


folder called
central.hdbgrants by
right-clicking on the cfg
folder and choosing New-
>File

5. Enter the following code. It {


is this object which will "ServiceName_2": {
grant the specified access
"object_owner": {
from the central master
data container to the local "container_roles":["admin", "core_db#"]
container technical users. },
"application_user": {
If you do not wish to type this "container_roles":["admin"]
code, you can reference the }
solution web page at
}
https://github.com/jungsap/Tec }
hEd2018.DAT375/blob/snippet
s/ex2/ex2_13

4. Create a file in the /cfg {


folder called .hdiconfig
and place following code in "file_suffixes":{
it to allow the deployment "hdbsynonymconfig":{
of hdbysnonymconfig files. "plugin_name":"com.sap.hana.di.synonym.config",
"plugin_version":"2.0.30.0"
If you do not wish to type
this code, you can }
reference the solution web }
page at }

https://github.com/jungsap/
TechEd2018.DAT375/blob/
snippets/ex2/ex2_13a

44
DAT375

4. Create another file in the


/cfg folder called
central.hdbsynonymconfi
g and then open it with the
Code Editor. Please paste
the following code in this
file.

If you do not wish to type


this code, you can
reference the solution web {
page at "Addresses": {
"target": {
https://github.com/jungsap/ "grantor" : "ServiceName_2",
TechEd2018.DAT375/blob/ "object" : "MD.Addresses"
snippets/ex2/ex2_13b }
},
"BusinessPartners": {
"target": {
"grantor" : "ServiceName_2",
"object" : "MD.BusinessPartner"
}
},
"Products": {
"target": {
"grantor" : "ServiceName_2",
"object" : "core.models::PRODUCTS"
}
}
}

5. Click Save

6. Go to the data folder within


the src folder and create a
new file called
central.hdbsynonym by
right-clicking on the data
folder and choosing New-
>File.

45
DAT375

7. Click the Click to Add


hyperlink to add new
entries.

8. Add the following entries as


shown.
Addresses =
MD.Addresses

BusinessPartners =
MD.BusinessPartner

Products =
core.models::PRODUCTS

9. Click Save

10. Select the db module and


choose Build->Build.
Check to make sure the
build is successful.

46
DAT375

14.You can then also return to


the Database Explorer and
choose the synonyms folder
and you should be able to see
the Addresses,
BusinessPartners, and
Products synonyms in the db
container. You can use Open
Data to view the data in any of
these “foreign” tables.

47
DAT375

Exercise 2.7: Creating a Graphical Calculation View

Explanation Screenshot

1) Now we are ready to create


our calculation view. From
the db/src folder, right click
and choose “New”, then
“Folder”.

2) Enter the name of the folder


as models and click OK.

3) Our db module currently {


doesn’t use namespaces. "name": "db.models",
Therefore, we will want to "subfolder": "ignore"
re-activate namespaces for }
just the models folder.

Create a file named


.hdinamespace in the
src/models folder. Enter
this text into the file.

48
DAT375

4) In the models sub-folder of


your project, create a new
calculation view based upon
the purchase order and
products. This will require
joining the
PurchaseOrder.ItemView
and Products synonym.

Right mouse click on the


models package, choose
New -> Calculation View

5) Enter the name as


POExpanded.
Choose CUBE as the data
category.

Click “Create”.

6) Add a Join Node to the


view.

49
DAT375

7) Click the Add Data Source


button on the join node.

8) Add
PurchaseOrder.ItemView
to the node and then press
Finish.

50
DAT375

9) Repeat the process and add


core.models::PRODUCTS
to the node and press
Finish.

51
DAT375

10) We now want to create a


join between the two data
sources on the ProductID to
the Product_Id column.
Drag the splitter line from
the right side of the editor to
reveal the Join Definition.

Drag and drop to connect


the two columns in the Join
Definition.

52
DAT375

11) Switch to the Mapping tab.


We can then select which
columns we want from this
part of the join.

Select
PurchaseOrderItemId,
ProductID, Amount,
Quantity, Product_Category,
Product_Description, and
Product_Name and then
choose Add To Output.

12) Connect the output of the


Join node to the
Aggregation node at the top
of the design window by
clicking and dragging the
arrow highlighted in the
screenshot to the
Aggregation node.

53
DAT375

13) In the Aggregation node and


Mapping tab, press the Auto
Map by Name button.

14) Select the Semantics node.


Change the Type on
PurchaseOrderItemId to
Attribute.

15) Save your model

16) Build the db module and


then return to the Database
Explorer perspective. Your
container will now have an
entry in the Column Views
folder for this new
Calculation View.

54
DAT375

17) For an initial test make sure


your output looks like the
following:

55
DAT375

Exercise 2.8: Stored Procedure


In this exercise we will create a small procedure “get_po_header_data” with two SELECT queries leveraging
tabular output parameters and intermediate table variables.

Explanation Screenshot

1. Right click on the db/src folder


and choose New->Folder

2. Enter the name of the folder


as “procedures” and choose
OK.

3. Right-click on the procedures


folder and choose New-
>Procedure.

56
DAT375

4. Enter the name of the


procedure as build_products
and click Create.

5. You will notice a new file has


been created in the
procedures folder with a
.hdbprocedure file extension.

6. The final procedure code PROCEDURE "build_products" (


should be very like the out ex_products table (PRODUCTID nvarchar(10),
following. CATEGORY nvarchar(60),
PRICE decimal(15,2) ),
The completed source code out ex_pc_productid nvarchar(10) )
can be found at the following LANGUAGE SQLSCRIPT
URL. SQL SECURITY INVOKER
--DEFAULT SCHEMA <default_schema_name>
https://github.com/jungsap/Tec READS SQL DATA AS
hEd2018.DAT375/blob/snippet BEGIN
s/ex2/ex2_16
declare lt_products table like :ex_products;
declare lv_index int = 0;
declare lv_del_index int array;
declare lv_array_index int = 0;

lt_products = select "Product_Id" as PRODUCTID,


"Product_Category" as CATEGORY, "Product_Price" as
PRICE from "Products";
:ex_products.INSERT(:lt_products);
:ex_products.INSERT(('ProductA', 'Software', '1999.99'), 1);
:ex_products.INSERT(('ProductB', 'Software', '2999.99'), 2);
:ex_products.INSERT(('ProductC', 'Software', '3999.99'), 3);

FOR lv_index IN 1..record_count(:ex_products) DO

57
DAT375

:ex_products.(PRICE).UPDATE((:ex_products.PRICE[lv_index
] * 1.25), lv_index);
END FOR;

FOR lv_index IN 1..record_count(:ex_products) DO


IF :ex_products.PRICE[lv_index] <= 2500.00 THEN
lv_array_index = lv_array_index + 1;
lv_del_index[lv_array_index] = lv_index;
END IF;
END FOR;

:ex_products.DELETE(:lv_del_index);

lv_index = :ex_products.SEARCH("CATEGORY", 'PC', 1);


ex_pc_productid = :ex_products.PRODUCTID[lv_index];

END

7. Now that your procedure code


is complete, click Save.

8. Select the db module, and


choose Build->Build.

9. If your build was completed


successfully, you should see a
success message in the
console below.

10. If not, then click on the


Problems button on the right
side. This will show the error
messages.

58
DAT375

11. Go to the Database Explorer


by clicking on the icon on the
left side of the screen.

12. Click on the Procedures


folder, and you should see
your new procedure in the box
below.

13. Right-click on the procedure


and choose CALL Procedure.

14. A new SQL Console tab will


be opened with a CALL
statement for your procedure.

59
DAT375

15. Click Run.

16. The results are then shown in


the table below.

60
DAT375

EXERCISE 3 –XSJS AND XSODATA SERVICES

Objective

For this exercise, we will now build the XSJS and XSODATA services used to expose our data model to the user
interface. Although XS Advanced runs on node.js, SAP has added modules to node.js to provide XSJS and
XSODATA backward compatibility. Therefore, you can use the same programming model and much of the same
APIs from XS, classic even within this new environment.

Exercise Description

• Node.js XSJS Bootstrap


• XSJS Services
• XSODATA Services

61
DAT375

EXERCISE 3 – SOLUTION

Exercise 3.1: XSJS and XSODATA

Explanation Screenshot

We will start by creating a


new module. New-
>Node.js Module

1. Name the module xsjs and


press Next.

62
DAT375

2. Be sure to check the box


Enable XSJS support. Then
press Finish.

3. Once again the mta.yaml file


has been extended to add the
xsjs module. Now we need to
add the dependency from the
web module to this new
Node.js module and to add a
destination route to it as well.

The complete section for the


web module should now look
like this:

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address
modules:
https://github.com/jungsap/Tec
- name: web
hEd2018.DAT375/blob/snippet
type: html5
s/ex3/ex3_1
path: web
requires:
- name: dat375-uaa
- name: xsjs_api
group: destinations
properties:
name: xsjs_api
url: '~{url}'
forwardAuthToken: true

63
DAT375

4. We also need to extend the


new xsjs module that was just
created. It will need a
dependency to both the uaa
and hdi resource. We also
need to make sure the module
is exposed for use in the
destination route.

The complete section for the


xsjs module should now look
like this:

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address - name: xsjs
https://github.com/jungsap/Tec type: nodejs
hEd2018.DAT375/blob/snippet path: xsjs
s/ex3/ex3_2 provides:
- name: xsjs_api
Later at deploy, the properties:
destination routing builds a url: ${default-url}
dependency and navigation requires:
ability between the two - name: hdi_db
services without ever having - name: dat375-uaa
to hard code the URLs or - name: db
ports. They are assigned at
deploy time and all references
are automatically updated.

5. If you remember back to {


exercise 1 – we maintained "welcomeFile": "index.html",
the xs-app.json of the App "authenticationMethod": "route",
Router (web module). Now we "routes": [{
can add rules for redirecting "source": "(.*)(.xsjs)",
certain requests to the web "destination": "xsjs_api",
module into other modules in "csrfProtection": true,
this project. This is where we "authenticationType": "xsuaa",
are configuring that any file "scope": {
request with the extension "GET": "$XSAPPNAME.Display",
.xsjs or .xsodata should be "POST": [
rerouted internally to the "$XSAPPNAME.Display",
Node.js destination that we "$XSAPPNAME.Create"
just defined in the mta.yaml. ],
"PUT": [
Note: if you don’t want to type "$XSAPPNAME.Display",
this code, we recommend that "$XSAPPNAME.Create"
you cut and paste it from this ],
web address "DELETE": [
https://github.com/jungsap/Tec "$XSAPPNAME.Display",
hEd2018.DAT375/blob/snippet "$XSAPPNAME.Delete"
s/ex3/ex3_3 ],

64
DAT375

"default": "$XSAPPNAME.Display"
}
}, {
"source": "(.*)(.xsodata)",
"destination": "xsjs_api",
"authenticationType": "xsuaa",
"scope": {
"GET": "$XSAPPNAME.Display",
"POST": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"PUT": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"DELETE": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Delete"
],
"default": "$XSAPPNAME.Display"
}
}]
}

6. Return to the xsjs folder that


we created in this exercise.
Like hdb and web modules,
this one also contains a
package.json file. Different this
time is the fact that the startup
script is not an SAP provided
node.js application, but one
that we’ve created via the
module creation wizard.

7. This server.js is the node.js /*eslint no-console: 0, no-unused-vars: 0*/


bootstrap for XSJS "use strict";
compatibility mode. It uses the
SAP provided xsjs module and var xsjs = require("@sap/xsjs");
starts it with a few basic var xsenv = require("@sap/xsenv");
parameters. However var port = process.env.PORT || 3000;
remember all the HANA
database connectivity options var options = {

65
DAT375

come from the HDI container // anonymous : true, // remove to authenticate calls
which we bound to this service redirectUrl : "/index.xsjs"
via the mta.yaml file. };

We want to make a few // configure HANA


changes to what the wizard try {
generated. We want options = Object.assign(options, xsenv.getServices({
authentication on our service, hana: {tag: "hana"} }));
so comment out the } catch (err) {
anonymous: true line. console.log("[WARN]", err.message);
}
The complete implementation
of server.js should look like // configure UAA
this now: try {
options = Object.assign(options, xsenv.getServices({
Note: if you don’t want to type uaa: {tag: "xsuaa"} }));
this code, we recommend that } catch (err) {
you cut and paste it from this console.log("[WARN]", err.message);
web address }
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet // start server
s/ex3/ex3_4 xsjs(options).listen(port);

console.log("Server listening on port %d", port);

8. In the lib folder, create a sub- service {


folder called xsodata. Create scopes (
a file named create ( "Create" ),
purchaseOrder.xsodata. read ( "Display" ),
Here is the source code for update ( "Edit" ),
this file. delete ( "Delete" ),
debug ( "Display" )
Note: If you receive a syntax );
error on Line 2, you can
ignore this. It is a bogus "PurchaseOrder.Header"
error because our client- as "POHeader" navigates ("Items" as "POItem");
side parser is missing some
of the new features of "PurchaseOrder.Item"
XSODATA. as "POItem";

Note: if you don’t want to type association "Items" principal


this code, we recommend that "POHeader"("PURCHASEORDERID")
you cut and paste it from this multiplicity "1" dependent
web address "POItem"("POHeader.PURCHASEORDERID") multiplicity "*";
https://github.com/jungsap/Tec }
hEd2018.DAT375/blob/snippet
s/ex3/ex3_5

Here we expose both the


Header and Item tables from
our HDI container as separate
entities and build a navigation
association between the two.

66
DAT375

We have also attached


automatic security checks via
the scopes section. This
matches scopes defined in the
UAA to certain operations in
the XSODATA service.

9. In the lib folder, create a sub- /*eslint no-console: 0, no-unused-vars: 0, no-shadow: 0, new-
folder called xsjs. Create a cap: 0*/
file named hdb.xsjs. Here is /*eslint-env node, es6 */
the source code for this file. "use strict";

Note: if you don’t want to type if (!$.session.hasAppPrivilege("Display")) {


this code, we recommend that $.response.setBody("Not Authorized");
you cut and paste it from this $.response.contentType = "text/plain";
web address $.response.status = $.net.http.UNAUTHORIZED;
https://github.com/jungsap/Tec } else {
hEd2018.DAT375/blob/snippet var conn = $.hdb.getConnection();
s/ex3/ex3_6 var query =
`SELECT FROM PurchaseOrder.Item {
This logic reads data from our POHeader.PURCHASEORDERID as
item table, formats it as text "PurchaseOrderId",
table delimited and then sends PRODUCT as "ProductID",
it to the frontend as JSON. It GROSSAMOUNT as "Amount"
also has security checks } `;
against the UAA Scope, var rs = conn.executeQuery(query);
Display. $.response.setBody(JSON.stringify(rs));
$.response.contentType = "application/json";
$.response.status = $.net.http.OK;
}

10.Create a second file named /*eslint no-console: 0, no-unused-vars: 0, dot-notation: 0*/


exercisesMaster.xsjs. Here "use strict";
is the source code for this file.
function fillSessionInfo(){
Note: if you don’t want to type var body = "";
this code, we recommend that body = JSON.stringify({
you cut and paste it from this "session" : [{"UserName":
web address $.session.getUsername(), "Language": $.session.language}]
https://github.com/jungsap/Tec });
hEd2018.DAT375/blob/snippet $.response.contentType = "application/json";
s/ex3/ex3_7 $.response.setBody(body);
$.response.status = $.net.http.OK;
It sends the current user and }
language back to the client for
filling in the header of the UI.
var aCmd = $.request.parameters.get("cmd");
switch (aCmd) {
case "getSessionInfo":
fillSessionInfo();
break;

67
DAT375

default:
$.response.status =
$.net.http.INTERNAL_SERVER_ERROR;
$.response.setBody("Invalid Request Method");
}

11. Save any unsaved/open files.


We can now run the xsjs
module.

12. You should see that the build


and deploy was successful.
The initial build can take a few
minutes.

13.So now run the web module. It


will need to rebuild and
redeploy due to the added
dependency to the xsjs
module.

14.In the running tab, you should


see the index.html from
earlier. We can change the url
to our xsjs service /index.xsjs
in the browser. You will see
that our xsjs service is
accessible via the HTML5
module runtime. The HTML5
module functions as a proxy
and performs the
authentication and routing to
the other service internally.

68
DAT375

15./xsjs/hdb.xsjs reads data


from our new Purchase Order
table we created in HANA in
the previous exercise and
exports it as JSON.

16. /xsjs/exercisesMaster.xsjs
shows the typical approach to
have multiple REST actions
in one XSJS. Change the
URL to
/xsjs/exercisesMaster.xsjs?
cmd=getSessionInfo. You
should see your User Name
returned from the XSJS
session layer.

17./xsodata/purchaseOrder.xso
data?$format=json gives you
access to a full OData service
for the Purchase Order header
and item tables we created in
the previous exercise.

69
DAT375

18.We can also see the metadata


about the OData interface we
created by changing the URL
to
/xsodata/purchaseOrder.xso
data/$metadata

19. Change the URL again to


point to the POHeader entity
and we will see the data
details.
/xsodata/purchaseOrder.xs
odata/POHeader?$format=j
son

70
DAT375

20. Associations can be an


excellent way to load child
elements on demand;
however, there is also an
option to expand the children
details in place so that all
levels can be retrieved with
one request.

Test the service again using


the same steps as in the
previous section of this
exercise.

This time add


&$expand=POItem to the
end of the URL.

You will then see that all the


items are embedded within
each header record.

21. Finally, we can debug


XSODATA requests if we add
the URL parameter &sap-ds-
debug=html. We can use
this to see internal request,
response, processing time, or
even the generated SQL
statements.

71
DAT375

Exercise 3.2: Creating an OData Service with Create Operation and XSJS Exit

Explanation Screenshot

1) Odata services aren’t service {


limited to read-only scopes (
operations. They can create ( "Create" ),
create, update and read ( "Display" ),
delete records as well. update ( "Edit" ),
We also have exit delete ( "Delete" ),
mechanisms to add debug ( "Display" )
custom code and );
validations to these
write operations. "PurchaseOrder.Header"
as "POHeader" navigates ("Items" as "POItem")
Return to create using "xsjs:poExits.xsjslib::po_create";
purchaseOrder.xsodat
a in the "PurchaseOrder.Item"
/xsjs/lib/xsodata as "POItem";
folder.
association "Items" principal "POHeader"("PURCHASEORDERID")
This time, also link the multiplicity "1" dependent
create operation to the "POItem"("POHeader.PURCHASEORDERID") multiplicity "*";
Server Side JavaScript }
Library (XSJSLIB)
xsjs:poExits.xsjslib
and the function
po_create. This will be
the exit code that
performs the insert of
the new record.

Note: if you don’t want


to type this code, we
recommend that you cut
and paste it from this
web address
https://github.com/jungs
ap/TechEd2018.DAT37
5/blob/snippets/ex3/ex3
_8

2) In the xsjs/lib/xsjs /*eslint no-console: 0, no-unused-vars: 0, no-shadow: 0, new-cap: 0,


folder create the file camelcase:0*/
poExits.xsjslib. Here /*eslint-env node, es6 */
is the code for this file. "use strict";

Note: if you don’t want function po_create(param) {


to type this code, we
recommend that you cut try {

72
DAT375

and paste it from this $.trace.error("Insert");


web address var after = param.afterTableName;
https://github.com/jungs
ap/TechEd2018.DAT37 //Get Input New Record Values
5/blob/snippets/ex3/ex3 var pStmt = param.connection.prepareStatement(`select * from
_9 "${after}"`);
var rs = null;
rs = pStmt.executeQuery();
var grossAmt = 0;
var currency = "";
$.trace.error("Before Read Partner");
while (rs.next()) {
grossAmt = rs.getDecimal(9);
currency = rs.getString(8);
let cur = new Intl.NumberFormat($.session.language, {
style: "currency",
currency: currency
});
$.trace.error(`Input Gross Amount:
${cur.format(grossAmt)}`);
}
pStmt.close();

pStmt = param.connection.prepareStatement(
`INSERT INTO "PurchaseOrder.Header"
("HISTORY.CREATEDAT",
"HISTORY.CHANGEDAT", "HISTORY.CREATEDBY",
"HISTORY.CHANGEDBY", PARTNER,
NOTEID, CURRENCY, GROSSAMOUNT,
NETAMOUNT, TAXAMOUNT, LIFECYCLESTATUS, APPROVALSTATUS,
CONFIRMSTATUS, ORDERINGSTATUS, INVOICINGSTATUS )
values(now(), now(), null, null, '100000000', null,
'EUR', 100, 100, 100, 'N', 'I', 'I', 'I', 'I' )`
);

pStmt.executeUpdate();
pStmt.close();

} catch (e) {
$.trace.error(e.toString());
throw e;
}
}

3) Save the open files and


re-run the xsjs module.
It should only take a few
seconds to run this time
as it just injects the
changes into the
already running service.
This is about as far as

73
DAT375

we can go with the


create OData service
for now. It is not as
easy to test a create
operation from the
browser URL as the
read operation. Later
we will build the web
user interface that will
allow us to call this
update operation.

74
DAT375

EXERCISE 4 – NODE.JS

Objective

In exercise 3 we created a Node.js module, but didn’t do much Node.js specific programming. We only were
using Node.js to run XSJS and XSODATA services. The support for XSJS and XSODATA is an important
feature for XS Advanced. It not only allows backward compatible support for much of your existing development;
but it provides a simplified programming model as an alternative to the non-block I/O event driven programming
model normally used by Node.js.

But we certainly aren’t limited to only the functionality provided by XSJS and XSODATA. We have access to the
full programming model of Node.js as well. In this exercise, we will learn how to extend our existing Node.js
module in the SAP Web IDE for SAP HANA.

You will learn about how to create and use reusable code in the form of node.js modules. You will use
package.json to define dependencies to these modules which make the installation of them quite easy. You will
use one of the most popular modules – express; which helps with the setup the handling of the request and
response object. You will use express to handle multiple HTTP handlers in the same service by using routes.

Exercise Description

• Modules
• Express Module and package.json
• Local Modules
• SAP HANA database access from node.js

75
DAT375

EXERCISE 4 – SOLUTION

Exercise 4.1: Modules and Express

Explanation Screenshot

1. Create a new Node.js


module called js without
the Enable XSJS support
option selected

2. Go to the mta.yaml editor and


the Modules tab. Select js
module. Add requires entries
for dat375-uaa, hdi_db, and
db.

- name: js

76
DAT375

type: nodejs
path: js
provides:
- name: js_api
properties:
url: '${default-url}'
requires:
- name: dat375-uaa
- name: hdi_db
- name: db

3. Now select the web module in


the MTA Editor. Add a
requires for the new js_api
provider with the shown
values. Save the mta.yaml file.

- name: js_api
group: destinations
properties:
name: js_api
url: '~{url}'
forwardAuthToken: true

4. Now navigate to the server.js /*eslint no-console: 0, no-unused-vars: 0, no-undef:0*/


in the js folder. Replace the /*eslint-env node, es6 */
code already in this file with
the following. "use strict";
var xsenv = require("@sap/xsenv");
We want to initialize the use of var port = process.env.PORT || 3000;
Express and inject our UAA var server = require("http").createServer();
service and HANA service global.__base = __dirname + "/";
middleware into Express. This
will allow express to enforce //Initialize Express App for XSA UAA and HDBEXT Middleware
our UAA requirements as well var passport = require("passport");
as create a HANA database var xssec = require("@sap/xssec");
client available to all express var xsHDBConn = require("@sap/hdbext");
route requests. var express = require("express");

This is like the options section //logging


of the server.js file we used to var logging = require("@sap/logging");

77
DAT375

set the configuration for the var appContext = logging.createAppContext();


XSJS module. We are doing var logger = appContext.getLogger("/Application");
essentially the same thing
here – we are telling the //Initialize Express App for XS UAA and HDBEXT Middleware
service how to lookup the var app = express();
connection and security
parameters. The xsHDBConn passport.use("JWT", new
is an instance of the xssec.JWTStrategy(xsenv.getServices({
@sap/hdbext module uaa: {
designed to expose pooled tag: "xsuaa"
database connections as a }
Node.js express middleware. }).uaa));
app.use(logging.expressMiddleware(appContext));
If you need help writing this app.use(passport.initialize());
code please refer to the var hanaOptions = xsenv.getServices({
solution at: hana: {
tag: "hana"
https://github.com/jungsap/Tec }
hEd2018.DAT375/blob/snippet });
s/ex4/ex4_1
app.use(
passport.authenticate("JWT", {
session: false
}),
xsHDBConn.middleware(hanaOptions.hana)
);

//Setup Routes
var router = require("./router")(app, server);

//Start the Server


server.on("request", app);
server.listen(port, function() {
logger.info(`HTTP Server: ${server.address().port}`);
console.info(`HTTP Server: ${server.address().port}`);
});

5. We want to create a good


project structure that can
accommodate many different
Express route requests. To
follow some Express best
practices, create a folder in js
called router with an index.js
file in it. This is the root route
handler we referenced back in
step 4 in our server.js.

For now only create a single


route for the path /node. The
handler for this route will be
called myNode.

78
DAT375

6. Notice that in the previous


additions we added a
reference to a module called
./routes/myNode. This is a
Node.js module which will be
local to our project.

Modules don’t all have to


come from central
repositories. They can also be
a way of modularizing your
own code (like XSJSLIB files).
That is exactly what we will do
here. Rather than filling up our
server.js with application
specific logic, we will break
that out into its own Node.js
module named myNode.js.

7. Create a new folder called


routes inside js/router. Then
create a file called
myNode.js.

8. Add the following code to your /*eslint no-console: 0, no-unused-vars: 0, no-shadow: 0, new-
myNode.js file. This will add a cap: 0*/
simple GET handler that /*eslint-env node, es6 */
returns a “Hello World” "use strict";
message when requested. var express = require("express");

Notice express in this module module.exports = function() {


has a root route handler. But var app = express.Router();
this will be relative to where it
is attached in router/index.js. app.get("/", (req, res) => {
Therefore, this logic will still res.type("text/plain").status(200).send("Hello
respond relative to the /node World Node.js");
route handler base we });
specified earlier.
return app;
If you need help writing this };
code please refer to the
solution at:

https://github.com/jungsap/Tec

79
DAT375

hEd2018.DAT375/blob/snippet
s/ex4/ex4_5

9. Look at the package.json file


in the editor. You will see the
dependencies section which
lists all required libraries and
their versions.

We manually added the


express Node.js module to our
project, so we also need to
extend the dependencies
section here. While here we
also need a few of the other
modules we will need later in
the exercise.

If you need help writing this


code please refer to the
solution at:

https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex4/ex4_6

10.Our Node.js module is {


essentially finished, however "welcomeFile": "index.html",
before we can run it we also "authenticationMethod": "route",
need to add a new route to the "routes": [{
xs-app.json of our HTML5 "source": "/node(.*)",
module. "destination": "js_api",
"csrfProtection": true,
If you need help writing this "authenticationType": "xsuaa",
code please refer to the "scope": {
solution at: "GET": "$XSAPPNAME.Display",
"POST": [
https://github.com/jungsap/Tec "$XSAPPNAME.Display",
hEd2018.DAT375/blob/snippet "$XSAPPNAME.Create"
s/ex4/ex4_7 ],
"PUT": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"DELETE": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Delete"
],
"default": "$XSAPPNAME.Display"
}

80
DAT375

},{
"source": "(.*)(.xsjs)",
"destination": "xsjs_api",
"csrfProtection": true,
"authenticationType": "xsuaa",
"scope": {
"GET": "$XSAPPNAME.Display",
"POST": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"PUT": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"DELETE": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Delete"
],
"default": "$XSAPPNAME.Display"
}
}, {
"source": "(.*)(.xsodata)",
"destination": "xsjs_api",
"authenticationType": "xsuaa",
"scope": {
"GET": "$XSAPPNAME.Display",
"POST": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"PUT": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Create"
],
"DELETE": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Delete"
],
"default": "$XSAPPNAME.Display"
}
}]
}

81
DAT375

11.We can now run the js


module.

12. You should see that the build


and deploy was successful.

13.So now run the web module. It


will need to rebuild and
redeploy due to the added
route.

14.In the running tab, you should


see the index.html from
earlier. We can add the url to
our xsjs service /index.xsjs in
the browser. This will test to
make sure that our XSJS and
XSODATA paths are still
accessible even though we
swapped out the root handler.

15.Now change the path in the


browser to /node/. You
should see Hello World
Node.js this is being returned
from the new route handler
and Node.js module we just
implemented in this exercise.

82
DAT375

Exercise 4.2: HANA Database Access from Node.js

Explanation Screenshot

1. In this exercise, we will look


at how to use the HANA
database access library to
send queries to the
database.

Return to the js module


and the myNode.js source
file. Let’s extend the logic
here with a URL handler for
/node/example1 which will
issue a database SELECT
statement against the
DUMMY table and return
the current database user.

2. Now add a new route for /*eslint no-console: 0, no-unused-vars: 0, no-shadow: 0, new-
/example1 that gets the cap: 0*/
database connection/client /*eslint-env node, es6 */
from the express request "use strict";
object (req.db). Then create a var express = require("express");
prepared statement for the
SELECT of SESSION_USER module.exports = function() {
from dummy. Execute the var app = express.Router();
statement and send the
results as JSON in the app.get("/", (req, res) => {
response object. return
res.type("text/plain").status(200).send("Hello World Node.js");
Note: if you don’t want to type });
this code, we recommend that
you cut and paste it from this app.get("/example1", (req, res) => {
web address var scope =
https://github.com/jungsap/Tec `${req.authInfo.xsappname}.Display`;
hEd2018.DAT375/blob/snippet if (req.authInfo &&
s/ex4/ex4_8 !req.authInfo.checkScope(scope)) {
return
res.type("text/plain").status(403).send("Forbidden");
}

let client = req.db;


client.prepare(
`SELECT SESSION_USER,
CURRENT_SCHEMA
FROM "DUMMY"`,
(err, statement) => {
if (err) {
return
res.type("text/plain").status(500).send(`ERROR:
${err.toString()}`);

83
DAT375

}
statement.exec([],
(err, results) => {
if (err) {
return
res.type("text/plain").status(500).send(`ERROR:
${err.toString()}`);
} else {
var
result = JSON.stringify({

Objects: results
});
return
res.type("application/json").status(200).send(result);
}
});
return null;
});
return null;
});

return app;
};

3. We can now run the js


module.

4. You should see that the build


and deploy was successful.

84
DAT375

5. Click on the web folder and


the run window should change
to the details of the running
HTML5 module. Since its
already running and we didn’t
make any changes to it, you
can just click on the
Application link in the top right
corner of the window to
reopen it in your web browser.

6. Now change the path in the


browser to /node/example1.
You should see the output of
your successful SELECT
statement.

7. Now return to //Simple Database Call Stored Procedure


js/router/routes/myNode.js. app.get("/products", (req, res) => {
Add a second route handler var client = req.db;
for the path /products that will var hdbext = require("@sap/hdbext");
call the stored procedure, //(client, Schema, Procedure, callback)
build_products, which was hdbext.loadProcedure(client, null,
created in Exercise 2 and "build_products", (err, sp) => {
return the results as a if (err) {
Microsoft Excel spreadsheet. return
res.type("text/plain").status(500).send(`ERROR:
Note: if you don’t want to type ${err.toString()}`);
this code, we recommend that }
you cut and paste it from this //(Input Parameters, callback(errors,
web address Output Scalar Parameters, [Output Table Parameters])
https://github.com/jungsap/Tec sp({}, (err, parameters, results) => {
hEd2018.DAT375/blob/snippet if (err) {
s/ex4/ex4_9 return
res.type("text/plain").status(500).send(`ERROR:
${err.toString()}`);
}
let out = [];
for (let item of results) {

out.push([item.PRODUCTID, item.CATEGORY,
item.PRICE]);
}
var excel = require("node-
xlsx");
var excelOut = excel.build([{

85
DAT375

name: "Products",
data: out
}]);
res.header("Content-
Disposition", "attachment; filename=Excel.xlsx");
return
res.type("application/vnd.ms-
excel").status(200).send(excelOut);
});
return null;
});
});

8. We can now re-run the js


module.

9. You should see that the build


and deploy was successful.

10.Click on the web folder and


the run window should change
to the details of the running
HTML5 module. Since its
already running and we didn’t
make any changes to it, you
can just click on the
Application link in the top right
corner of the window to
reopen it in your web browser.

86
DAT375

11.Now change the path in the


browser to /node/products.
You should be prompted to
download an Excel file.

87
DAT375

EXERCISE 5 – SAPUI5 USER INTERFACE

Objective

For this exercise, we will build a proper SAPUI5 user interface that consumes both the XSJS and XSODATA
services from our Node.js module.

Exercise Description

• SAPUI5 Entry Point


• SAPUI5 Component
• SAPUI5 Views
• SAPUI5 Controllers

88
DAT375

EXERCISE 5 – SOLUTION

Exercise 5.1: SAPUI5 as an XSA Service Broker

Explanation Screenshot

1. In Exercise 1 a simple
SAPUI5 Hello World
application was generated for
us by the module creation
wizard.

The bootstrap for SAPUI5


points to the publicly hosted
UI5 library. However, a
recent addition to XSA was to
deliver SAPUI5 as its own
service broker for local
consumption. Let’s now
adjust our project and this
index.html hello world
example to use the local
SAPUI5 service broker.

2. Let’s begin in the mta.yaml


and define a resource for the
UI5 central service.

Switch to the Code Editor and


then back to the MTA Editor
after filling in these new
values.

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
- name: dat375-ui5
s/ex5/ex5_1
type: org.cloudfoundry.managed-service
parameters:
service: sapui5_sb
service-plan: sapui5-1.52

89
DAT375

3. Next you need to add the UI5


resource as a dependency in
the web module.

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex5/ex5_2a

- name: web
type: html5
path: web
requires:
- name: dat375-uaa
- name: dat375-ui5
- name: xsjs_api
group: destinations
properties:
name: xsjs_api
url: '~{url}'
forwardAuthToken: true
- name: js_api
group: destinations
properties:
name: js_api
url: '~{url}'
forwardAuthToken: true

4. We don’t want to hard code


the url of the UI5 service in the
bootstrap in every html file in
our project. We can instead
use the replacement logic of
the App Router to insert the
runtime URL for us
dynamically.

For this we need to add a


route configuration to our xs-
app.json in the web module.

Note: if you don’t want to type


this code, we recommend that

90
DAT375

you cut and paste it from this


web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex5/ex5_2

5. The final step is to adjust the


index.html file in the
web/resources folder to use
this sapui5_sb.url
placeholder variable instead
of the external address for
the UI5 library in the
bootstrap.

Note: if you don’t want to


type this code, we
recommend that you cut and
paste it from this web
address
https://github.com/jungsap/Te
chEd2018.DAT375/blob/snip
pets/ex5/ex5_3

6. Save any open files. Run the


Web module.

If the change was successful,


the hello world index.html
should load as normal.

7. If you would like to confirm the


change you can perform a
view source in the web
browser or use browser
developer tools to view the
request.

91
DAT375

Exercise 5.2: SAPUI5 User Interface

Explanation Screenshot

1. Now we want to build a


proper SAPUI5 interface
that will consume our
XSJS, XSODATA and
Node.js services from
earlier in the workshop.

Let’s return to our web


module and create a new
folder named
resources/odataCreate
and a new html file in this
folder called index.html

2. Here is the complete coding of <!DOCTYPE html>


the index.html page. <html>
<head>
Note: if you don’t want to type <meta http-equiv="X-UA-Compatible"
this code, we recommend that content="IE=edge" />
you cut and paste it from this <meta http-equiv="Content-Type"
web address content="text/html;charset=UTF-8"/>
https://github.com/jungsap/Tec <meta name="viewport" content="width=device-width,
hEd2018.DAT375/blob/snippet initial-scale=1.0" />
s/ex5/ex5_4 <!-- <script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-
We are loading the SAPUI5 core.js" -->
bootstrap and then initializing <script id="sap-ui-bootstrap" src="{{{
the shell and component. sapui5_sb.url}}}/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize_plus"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-language="en"
data-sap-ui-resourceroots='{
"teched.odataCreate": "./"}'

data-sap-ui-
libs="sap.m,sap.ui.comp,sap.ui.core,sap.ui.layout"
data-sap-ui-frameOptions="trusted">
</script>
<script type="text/javascript" src="/common/error.js"
></script>
<script>
sap.ui.getCore().attachInit(function () {
var ComponentContainer =
new sap.ui.core.ComponentContainer({
height : "100%"
});

92
DAT375

new sap.m.Shell({
app: ComponentContainer,
showLogout: true
}).placeAt("content");

var oComponent = sap.ui.component({


id: "comp",
name: "teched.odataCreate",
manifestFirst: true,
async: true
}).then(function(oComponent){

ComponentContainer.setComponent(oComponent);
});
});
</script>
</head>

<!-- UI Content -->


<body class="sapUiBody" id="content">
</body>
</html>

3. There is a utility JavaScript


library we reference in this
html page and in our future
UI5 applications –
common/error.js for producing
error messages.

Create a common folder in


resources with error.js file in it.

4. Here is the coding of error.js /*eslint no-console: 0, no-unused-vars: 0, no-use-before-


define: 0, no-redeclare: 0*/
Note: if you don’t want to type function onErrorCall(jqXHR, textStatus, errorThrown) {
this code, we recommend that var page = sap.ui.getCore().byId("pageID");
you cut and paste it from this page.setBusy(false);
web address if (typeof jqXHR.status === "undefined") {
https://github.com/jungsap/Tec var errorRes =
hEd2018.DAT375/blob/snippet JSON.parse(jqXHR.response.body);
s/ex5/error.js sap.m.MessageBox.show(

errorRes.error.innererror.errordetail.DETAIL, {

93
DAT375

icon:
sap.m.MessageBox.Icon.ERROR,
title: "Service Call Error",
actions:
[sap.m.MessageBox.Action.OK],
styleClass:
"sapUiSizeCompact"
});
} else {
if (jqXHR.status === 500 || jqXHR.status ===
400) {

sap.m.MessageBox.show(jqXHR.responseText, {
icon:
sap.m.MessageBox.Icon.ERROR,
title: "Service Call Error",
actions:
[sap.m.MessageBox.Action.OK],
styleClass:
"sapUiSizeCompact"
});
return;
} else {

sap.m.MessageBox.show(jqXHR.statusText, {
icon:
sap.m.MessageBox.Icon.ERROR,
title: "Service Call Error",
actions:
[sap.m.MessageBox.Action.OK],
styleClass:
"sapUiSizeCompact"
});
return;
}
}
}

function oDataFailed(oControlEvent) {
sap.m.MessageBox.show("Bad Entity Definition", {
icon: sap.m.MessageBox.Icon.ERROR,
title: "OData Service Call Error",
actions: [sap.m.MessageBox.Action.OK],
styleClass: "sapUiSizeCompact"
});
return;
}

5. Next we need to create our /*eslint no-console: 0, no-unused-vars: 0, no-use-before-


Component.js file in define: 0, no-redeclare: 0*/
web/resources/odataCreate. sap.ui.define([
Here is the coding of this file. "sap/ui/core/UIComponent",
"sap/ui/Device",

94
DAT375

Note: if you don’t want to type "teched/odataCreate/model/models"


this code, we recommend that ], function(UIComponent, Device, models) {
you cut and paste it from this "use strict";
web address
https://github.com/jungsap/Tec return
hEd2018.DAT375/blob/snippet UIComponent.extend("teched.odataCreate.Component", {
s/ex5/ex5_5
metadata: {
Here we initialize the SAPUI5 manifest: "json"
component and in doing so we },
create an instance of the json
configuration model. init: function() {

We also load our first view jQuery.sap.require("sap.m.MessageBox");


which we will create in the
next step. Finally, you see that jQuery.sap.require("sap.m.MessageToast");
we make a call to
xsjs/exercisesMaster.xsjs to fill this.setModel(models.createDeviceModel(), "device");
the page header with the
current user id.
sap.ui.core.UIComponent.prototype.init.apply(
this, arguments);
this.getSessionInfo();
},

destroy: function() {
// call the base component's destroy
function

UIComponent.prototype.destroy.apply(this,
arguments);
},

getSessionInfo: function() {
var aUrl =
"/xsjs/exercisesMaster.xsjs?cmd=getSessionInfo";
this.onLoadSession(
JSON.parse(jQuery.ajax({
url: aUrl,
method: "GET",
dataType: "json",
async: false
}).responseText));
},

onLoadSession: function(myJSON) {
for (var i = 0; i <
myJSON.session.length; i++) {
var config =
this.getModel("config");

config.setProperty("/UserName",
myJSON.session[i].UserName);

95
DAT375

}
}
});

});

6. At the beginning of the


Component.js processing you
might notice the section that
refers to a manifest for the
metadata. This allows us to
separate out a lot of the
configuration of our application
into a separate manifest.json
file (as most modern Fiori
based UI5 applications are
designed).

Create this manifest.json file


in your odataCreate folder
and add the following content
to it.

This JSON file specifies


libraries and other technical
details, but more importantly
sets the initial view details as
well as creates the json and
Odata models.

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex5/ex5_6

96
DAT375

7. We want to put our text strings


into a text bundle. Create a
folder inside odataCreate
name i18n and then a file
named i18n_en.properties.

8. Add the following text strings


to this i18n_en.properties
file.

Note: if you don’t want to type


this code, we recommend that
you cut and paste it from this
web address
https://github.com/jungsap/Tec
hEd2018.DAT375/blob/snippet
s/ex5/ex5_7

97
DAT375

9. Create a folder called view


inside odataCreate. This is
where we will hold all our
views. Create the root view
named App.view.xml.

10.Here is the complete coding of <core:View xmlns="sap.m" xmlns:u="sap.ui.unified"


the App.view.xml file. xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
Note: if you don’t want to type controllerName="teched.odataCreate.controller.App"
this code, we recommend that class="sapUiSizeCompact">
you cut and paste it from this <u:Shell id="myShell" >
web address <u:user>
https://github.com/jungsap/Tec <u:ShellHeadUserItem image="sap-
hEd2018.DAT375/blob/snippet icon://person-placeholder" username="{config>/UserName}"/>
s/ex5/ex5_8 </u:user>
<u:content>
It creates the shell control and <IconTabBar
the overall flow of our page, class="iconTabBarPaddingTop" upperCase="true"
but then defers the design of expanded="true">
the header and table areas to <items>
an XML fragment we will <IconTabFilter
create next. text="{i18n>H1}">

<core:Fragment
fragmentName="teched.odataCreate.view.MRead"
type="XML"/>
</IconTabFilter>
</items>
</IconTabBar>
</u:content>
</u:Shell>
</core:View>

98
DAT375

11.Create a file named <core:FragmentDefinition xmlns:core="sap.ui.core"


MRead.fragment.xml in the xmlns="sap.m">
view folder. <Panel expandable="true" expanded="true"
headerText="{i18n>P1}">
Here is the complete coding of <List width="100%">
this file. <InputListItem label="{i18n>L1}">
<Input id="mPath"
Note: if you don’t want to type value="{/mPath}"/>
this code, we recommend that </InputListItem>
you cut and paste it from this <InputListItem label="{i18n>L2}">
web address <Input id="mEntity1"
https://github.com/jungsap/Tec value="{/mEntity1}"/>
hEd2018.DAT375/blob/snippet </InputListItem>
s/ex5/ex5_9 </List>
<Button press="callService" text="{i18n>B1}"/>
Here we build panel with our <Button press="callCreate" text="{i18n>B2}"/>
input fields. These fields will </Panel>
allow us to control settings for <core:Fragment
calling our XSODATA service. fragmentName="teched.odataCreate.view.MTableHead"
We then will have a table type="XML"/>
control for our purchase order </core:FragmentDefinition>
header and another for our
purchase order item. The
rendering of these two
sections if further separated
out into two JavaScript based
fragments.

12.Create the <core:FragmentDefinition xmlns:core="sap.ui.core"


MTableHead.fragment.xml xmlns="sap.m" xmlns:smartTable="sap.ui.comp.smarttable">
file. Here is the complete <VBox fitContainer="true">
coding for this file. <smartTable:SmartTable id="tblPOHeader"
tableType="Table" header="{i18n>POHeader}"
Note: if you don’t want to type showRowCount="true" enableAutoBinding="true"
this code, we recommend that
you cut and paste it from this showFullScreenButton="true"></smartTable:SmartTabl
web address e>
https://github.com/jungsap/Tec </VBox>
hEd2018.DAT375/blob/snippet </core:FragmentDefinition>
s/ex5/ex5_10

We are creating a table control


to display our Purchase Order
Header data which will be
returned by the XSODATA
service.

13.The next piece of our UI is the /*eslint no-console: 0, no-unused-vars: 0, no-use-before-


view controller named define: 0, no-redeclare: 0, no-undef: 0*/
App.controller.js. Create a //To use a javascript controller its name must end with
folder named controller in .controller.js
odataCreate and then the file sap.ui.define([

99
DAT375

named App.controller.js "teched/odataCreate/controller/BaseController",


"sap/ui/model/json/JSONModel"
Note: if you don’t want to type ], function(BaseController, JSONModel) {
this code, we recommend that "use strict";
you cut and paste it from this
web address return
https://github.com/jungsap/Tec BaseController.extend("teched.odataCreate.controller.App", {
hEd2018.DAT375/blob/snippet
s/ex5/ex5_11 onInit: function() {

There is some considerable var oConfig =


code in this file because here this.getOwnerComponent().getModel("config");
we have all the event handlers var userName =
for our application. oConfig.getProperty("/UserName");

We start with the onInit event var urlMulti =


which fires on startup of the "/xsodata/purchaseOrder.xsodata";
page. We set some initial
values into configuration this.getOwnerComponent().getModel().setProperty("/m
model so they appear in our Path", urlMulti);
input fields.
this.getOwnerComponent().getModel().setProperty("/m
callService handles the logic Entity1", "POHeader");
to call our OData service. We
are reading the metadata from },
the OData service to callService: function() {
dynamically create the var oTable =
columns in our table controls. this.getView().byId("tblPOHeader");

callCreate calls the OData var mPath =


service and attempts to create this.getOwnerComponent().getModel().getProperty("/mPath");
a new purchase order record. var mEntity1 =
this.getOwnerComponent().getModel().getProperty("/mEntity1"
);

var oParams = {};


oParams.json = true;
oParams.useBatch = false;
var oModel = new
sap.ui.model.odata.v2.ODataModel(mPath, oParams);
oModel.attachEvent("requestFailed",
oDataFailed);

function fnLoadMetadata() {
oTable.setModel(oModel);
oTable.setEntitySet(mEntity1);
var oMeta =
oModel.getServiceMetadata();
var headerFields = "";
for (var i = 0; i <
oMeta.dataServices.schema[0].entityType[0].property.length;
i++) {

100
DAT375

var property =
oMeta.dataServices.schema[0].entityType[0].property[i];
headerFields +=
property.name + ",";
}

oTable.setInitiallyVisibleFields(headerFields);
}

oModel.attachMetadataLoaded(oModel, function() {
fnLoadMetadata();
});

oModel.attachMetadataFailed(oModel,
function() {

sap.m.MessageBox.show("Bad Service Definition", {


icon:
sap.m.MessageBox.Icon.ERROR,
title: "Service Call
Error",
actions:
[sap.m.MessageBox.Action.OK],
styleClass:
"sapUiSizeCompact"
});
});
},

callCreate: function(oEvent) {
var oTable =
this.getView().byId("tblPOHeader");
var oModel = oTable.getModel();
// var result =
this.getView().getModel().getData();
var oEntry = {};
oEntry.PURCHASEORDERID =
"0000000000";
oEntry.PARTNERID = 100000000;
oEntry.CURRENCY = "EUR";
oEntry.GROSSAMOUNT = "1137.64";
oEntry.NETAMOUNT = "956.00";
oEntry.TAXAMOUNT = "181.64";
oEntry.DELIVERYDATE = new Date();
oEntry.APPROVALSTATUS = "I";
oEntry.CONFIRMSTATUS = "I";
oEntry["HISTORY.CHANGEDAT"] =
new Date();
oEntry["HISTORY.CHANGEDBY"] =
"1";
oEntry["HISTORY.CREATEDAT"] =
new Date();

101
DAT375

oEntry["HISTORY.CREATEDBY"] =
"1";
oEntry.INVOICINGSTATUS = "I";
oEntry.LIFECYCLESTATUS = "N";
oEntry.NOTEID = "9000000001";
oEntry.ORDERINGSTATUS = "I";

oModel.setHeaders({
"content-type":
"application/json;charset=utf-8"
});
var mParams = {};
mParams.success = function() {

sap.m.MessageToast.show("Create successful");
};
mParams.error = this.onErrorCall;
var mEntity1 =
this.getOwnerComponent().getModel().getProperty("/mEntity1"
);
oModel.create("/" + mEntity1, oEntry,
mParams);
},

onErrorCall: function(oError) {
if (oError.statusCode === 500 ||
oError.statusCode === 400 || oError.statusCode === "500" ||
oError.statusCode === "400") {
var errorRes =
JSON.parse(oError.responseText);
if (!errorRes.error.innererror) {

sap.m.MessageBox.alert(errorRes.error.message.valu
e);
} else {
if
(!errorRes.error.innererror.message) {

sap.m.MessageBox.alert(errorRes.error.innererror.toSt
ring());
} else {

sap.m.MessageBox.alert(errorRes.error.innererror.me
ssage);
}
}
return;
} else {

sap.m.MessageBox.alert(oError.response.statusText);
return;
}

102
DAT375

}
});
});

14.We can also separate certain /*global history */


controller functions out into a sap.ui.define([
reusable base controller that "sap/ui/core/mvc/Controller",
can be shared by multiple "sap/ui/core/routing/History"
controllers. Although this first ], function (Controller, History) {
simple example only has one "use strict";
controller, let’s go ahead and
setup this base controller return
(named BaseController.js in Controller.extend("teched.odataCreate.controller.BaseControll
the controller folder) for er", {
future exercises. /**
* Convenience method for accessing
Note: if you don’t want to type the router in every controller of the application.
this code, we recommend that * @public
you cut and paste it from this * @returns
web address {sap.ui.core.routing.Router} the router for this component
https://github.com/jungsap/Tec */
hEd2018.DAT375/blob/snippet getRouter : function () {
s/ex5/ex5_12 return
this.getOwnerComponent().getRouter();
},

/**
* Convenience method for getting the
view model by name in every controller of the application.
* @public
* @param {string} sName the model
name
* @returns {sap.ui.model.Model} the
model instance
*/
getModel : function (sName) {
return
this.getView().getModel(sName);
},

/**
* Convenience method for setting the
view model in every controller of the application.
* @public
* @param {sap.ui.model.Model}
oModel the model instance
* @param {string} sName the model
name
* @returns {sap.ui.mvc.View} the view
instance
*/

103
DAT375

setModel : function (oModel, sName) {


return
this.getView().setModel(oModel, sName);
},

/**
* Convenience method for getting the
resource bundle.
* @public
* @returns
{sap.ui.model.resource.ResourceModel} the resourceModel of
the component
*/
getResourceBundle : function () {
return
this.getOwnerComponent().getModel("i18n").getResourceBun
dle();
},

/**
* Event handler for navigating back.
* It there is a history entry we go one
step back in the browser history
* If not, it will replace the current entry
of the browser history with the master route.
* @public
*/
onNavBack : function() {
var sPreviousHash =
History.getInstance().getPreviousHash();

if (sPreviousHash !==
undefined) {
history.go(-1);
} else {

this.getRouter().navTo("master", {}, true);


}
}

});

}
);

104
DAT375

15. We can also have reusable


functions like formatters,
sorters, and grouping
functions. We’ve prepared
these model support tools
for you. You can download
the model.zip file from
here:
https://github.com/jungsap/
TechEd2018.DAT375/raw/s
nippets/ex5/model.zip

Please import this file into


the odataCreate folder.

16.So now run the web module

105
DAT375

17.In the running tab, you should


see the index.html from
earlier. Change this to
/odataCreate to test the new
UI we just created.

Test the Call Service button.

18.Return to the view fragment,


MRead.fragment.xml. Add a
third button to download
products to Excel.

19.In the controller,


App.controller.js; add a
JavaScript event handler for
callExcel – the button press
event you created in the
previous step.

20.Re-run the web module.

106
DAT375

21.You should see the new


Download Products button.
When you press it, the Node.js
Route (from exercise 4) that
calls the Stored Procedure
(from exercise 2) will be called
and you should download an
Excel file with products in it.

22.Press the Create PO button to


invoke the validation and
create exits in the XSODATA
service from Exercise 3.2.

If you return to the SAP Web


IDE run console, you should
see the trace messages which
were triggered inside our
xsjslib create exit in order to
track and simulate the creation
logic.

107
DAT375

EXERCISE 6 – PACKAGING FOR TRANSPORT

Objective

Now that we have a completed application we can package it for transport

Exercise Description

• Build all modules


• Build project to create MTAR
• Download MTAR

108
DAT375

EXERCISE 6 – SOLUTION

Exercise 6.1: Package for Transport

Explanation Screenshot

1. The build and run process


creates services on the
XSA server; but they
should be considered
“local” testing services only.
They are prefixed with your
user name and workspace.
We now want to package
our complete application so
that it’s installable in its
final form and ready to
transport to downstream
systems.

2. Perform a build at the project


level.

109
DAT375

3. After a few minutes, you


should now have a separate
mta_archives project in your
workspace. This is where the
built archives for all our
projects go.

4. You can now manually


download the MTAR file and
install it from the command
line using the XS DEPLOY
command.

110
DAT375

5. You can also directly deploy


the MTAR to another XSA
space on the same system or
directly to SAP Cloud Platform
Cloud Foundry.

111
DAT375

www.sap.com/contactsap

© 2018 SAP SE or an SAP affiliate company. All rights reserved.


No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of
SAP SE or an SAP affiliate company.
The information contained herein may be changed without prior notice. Some software products marketed by SAP SE and its
distributors contain proprietary software components of other software vendors. National product specifications may vary.
These materials are provided by SAP SE or an SAP affiliate company for informational purposes only, without representation
or warranty of any kind, and SAP or its affiliated companies shall not be liable for errors or omissions with respect to the
materials. The only warranties for SAP or SAP affiliate company products and services are those that are set forth in the
express warranty statements accompanying such products and services, if any. Nothing herein should be construed as
constituting an additional warranty.
In particular, SAP SE or its affiliated companies have no obligation to pursue any course of business outlined in this
document or any related presentation, or to develop or release any functionality mentioned therein. This document, or any
related presentation, and SAP SE’s or its affiliated companies’ strategy and possible future developments, products, and/or
platforms, directions, and functionality are all subject to change and may be changed by SAP SE or its affiliated companies a t
any time for any reason without notice. The information in this document is not a commitment, promise, or legal obligation to
deliver any material, code, or functionality. All forward-looking statements are subject to various risks and uncertainties that
could cause actual results to differ materially from expectations. Readers are cautioned not to place undue reliance on these
forward-looking statements, and they should not be relied upon in making purchasing decisions.
SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered
trademarks of SAP SE (or an SAP affiliate company) in Germany and other countries. All other product and service names
mentioned are the trademarks of their respective companies.
See https://www.sap.com/copyright for additional trademark information and notices.

112

You might also like