Develop a Full-Stack Application for Cloud Foundry Using SAP Web IDE
Note: Not all capabilities described in this blog post are available in SAP Web IDE Full-Stack. Some are still in development and will be available in future releases.
I’d like to help illustrate the new capabilities of the SAP Web IDE, full-stack version to build full stack applications for Cloud Foundry. Here’s how you would develop a simple book store application — the app is simple, but it illustrates many of the powerful new features of SAP Web IDE.
Main Topics
In describing how to build the bookstore app, and I’ll cover the following technical topics:
- Use SAP Web IDE to develop the entire application.
- Use HANA CDS views to create the data model for the application.
- Use HDBTableData to populate the tables with mock data.
- Use XSJS XSOData to expose the data model as an OData service.
- Use Node.js and SQL to add a new book to the database.
- Use Fiori Master-Detail to create the UI for the application.
- Use the Web IDE Layout Editor to enhance the UI of the application.
- Test and debug the application in SAP Web IDE.
- Deploy the finished application to Cloud Foundry.
The Application
The application we’ll build is designed for the owner of a book store who needs to manage the store’s inventory. The application should look something like this:
The app should do the following:
- List all books by author.
- In the author details field, show the books from that author. For each book, it should show the title, ISBN, price, and number of copies available in the store.
- Add a new book to the store’s inventory.
The user should be able to add books and update existing book details, and to update the inventory when a book is sold.
You can download the entire application source-code from GitHub.
Step 1: Create MTA Project
We’ll build the app as an MTA project, which enables full-stack development.
Go to File –> New –> Project from Template and select the Multi-Target Application Project template.
- Project Name: BookStore.
Click Finish.
Step 2: Add a HANA Database module to the MTA project
We’ll need a database for the app, so we’ll add an SAP HANA database module to the project.
Name the module booksdb, and complete the wizard.
Add CDS to the DB module by right-clicking the src folder located under the booksdb module and select New > CDS Artifact. Name it store and click Create.
Now that you have a CDS artifact, add the Author and Book.
Now let’s add some mock-data for testing purposes. Add the following files to the src folder of your HDB module:
File name: authors.hdbtabledata
File content:
{ “format_version”: 1, “imports”: [ { “target_table” : “bookstore.db::store.Author”, “source_data” : { “data_type” : “CSV”, “file_name” : “bookstore.db::authors.csv”, “has_header” : true }, “import_settings” : { “import_columns” : [“authorId”, “authorName”, “numberOfBooks” ] }, “column_mappings” : {“authorId”: 1, “authorName” : 2, “numberOfBooks” : 3 } } ] } |
File name: books.hdbtabledata
File content:
{ “format_version”: 1, “imports”: [ { “target_table”: “bookstore.db::store.Book”, “source_data” : { “data_type” : “CSV”, “file_name” : “bookstore.db::books.csv”, “has_header” : true }, “import_settings” : { “import_columns” : [ “bookId”, “bookName”, “authorId”,”isbn”,”price”,”priceCurrency” ] }, “column_mappings” : { “bookId” : 1, “bookName” : 2, “authorId”: 3, “isbn” : 4, “price”: 5, “priceCurrency” : 6 } } ] } |
File name: authors.csv
File content:
authorId, authorName, numberOfBooks 1, F. Scott Fitzgerald, 3 2, George Orwell, 2 3, J.D. Salinger, 1 4, Kurt Vonnegut, 4 |
File name: books.csv
File content:
bookId, bookName, authorId, isbn, price,priceCurrency 110, The Great Gatsby, 1, 0743273567, 11, USD 111, Tender Is the Night, 1, 1853260975, 9, USD 112, This Side of Paradise, 1, 0486289990, 5, USD 120, 1984, 2, 0451524934, 7, USD 121, Animal Farm, 2, 812911612X, 14, USD 130, The Catcher in the Rye, 3, 0316769487, 5, USD 140, Slaughterhouse-Five, 4, 0385333846, 10, USD 141, Cat’s Cradle, 4, 038533348X, 10, USD 142, Breakfast of Champions, 4, 0385334206, 10, USD 143, The Sirens of Titan, 4, 0385333498, 9, USD |
File name: authorSequence.hdbsequence
File content:
SEQUENCE “bookstore.db::authorseq” INCREMENT BY 1 START WITH 1000 MINVALUE 1000 MAXVALUE 1999999999 |
File name: bookSequence.hdbsequence
File content:
SEQUENCE “bookstore.db::bookseq” INCREMENT BY 1 START WITH 1000 MINVALUE 1000 MAXVALUE 1999999999 |
Save your work.
This is what the DB module should look like after you’re done:
Build the DB module.
Step 3: Add a Node.js module to the MTA project
We’ll need a node.js module to expose the data as an OData service.
Add a Node.js module to the project.
- Module Name: booksjs.
- Enable XSJS Support: Select this checkbox.
Complete the wizard.
Now add a new folder under the lib folder, and name it xsodata, and then create a new file in this folder as follows:
File name: service.xsodata
File content:
service { “bookstore.db::store.Author” as “Author” navigates (“AuthorBooks” as “books”); “bookstore.db::store.Book” as “Book” create using “xsjs:bookCreateMethod.xsjslib::createBook”;
“bookstore.db::BestSeller” as “BestSeller” keys generate local “objectId”; association “AuthorBooks” principal “Author”(“authorId”) multiplicity “1” dependent “Book”(“authorId”) multiplicity “*”; } |
In order to support CRUD operations (Create, Read, Update and Delete), we need to implement an XSJS extension. We do this by creating a new file here: booksjs module > lib > xsjs.
File name: bookCreateMethod.xsjslib
File content:
“use strict”;
// get the entry that was sent by the client function getEntry(afterTableName, connection) { var query = connection.prepareStatement(“select * from \”” + afterTableName + “\””); var queryResult = query.executeQuery(); query.close(); var record = null; while (queryResult.next()) { record = queryResult; } return record; }
function findAuthorByName(authorName, connection) { // TODO: change LIKE to equal var query = connection.prepareStatement(“SELECT * FROM \”bookstore.db::store.Author\” WHERE \”authorName\” LIKE” + “‘%” + authorName + “%'”); var queryResult = query.executeQuery(); query.close(); while (queryResult.next()) { return queryResult; }
return null; }
function findAuthorById(authorId, connection) { // TODO: change LIKE to equal var query = connection.prepareStatement(“SELECT * FROM \”bookstore.db::store.Author\” WHERE \”authorId\” =” + authorId); var queryResult = query.executeQuery(); query.close(); while (queryResult.next()) { return queryResult; }
return null; }
/** * This function will execute a query for adding a new author to the database * * */ function insertNewAuthor(authorName, connection) { var currentAuthorIdQuery = connection.prepareStatement(“SELECT \”bookstore.db::authorseq\”.NEXTVAL FROM DUMMY”); var currentAuthorIdQueryResult = currentAuthorIdQuery.executeQuery();
var nextAuthorId = 0;
while (currentAuthorIdQueryResult.next()) { nextAuthorId = currentAuthorIdQueryResult.getInt(1); break; }
var query = connection.prepareStatement(“insert into \”bookstore.db::store.Author\” values(?,?,?,?)”); query.setInt(1, nextAuthorId); query.setString(2, authorName); query.setInt(3, 0); query.setInt(4, 0); query.executeUpdate(); query.close();
return nextAuthorId; }
/** * This function will execute a query for adding a new book to the database * bookId property will be generated via the database sequence * after adding a new book we need to update the author number of books column * */ function insertNewBook(bookName, authorId,isbn,price, connection) {
var currentBookIdQuery = connection.prepareStatement(“SELECT \”bookstore.db::bookseq\”.NEXTVAL FROM DUMMY”); var currentBookIdQueryResult = currentBookIdQuery.executeQuery();
var nextBookId = 0;
while (currentBookIdQueryResult.next()) { nextBookId = currentBookIdQueryResult.getInt(1); break; }
var query = connection.prepareStatement(“INSERT INTO \”bookstore.db::store.Book\” VALUES(?,?,?,?,?,?,?)”);
query.setInt(1, nextBookId); query.setInt(2, authorId); query.setString(3, isbn); query.setString(4, bookName); query.setInt(5, price); query.setString(6,”USD”); query.setString(7,””); query.executeUpdate(); query.close();
incrementNumberOfBooksForAuthorWithId(authorId, connection); }
function incrementNumberOfBooksForAuthorWithId(authorId, connection) { // 1. Get the author by id from the database
var authorRecord = findAuthorById(authorId, connection); if (authorRecord) { var numberOfBooks = authorRecord.getInt(3); numberOfBooks = numberOfBooks + 1; var query = connection.prepareStatement(“UPDATE \”bookstore.db::store.Author\” SET \”numberOfBooks\” = ? WHERE \”authorId\” = ?”); query.setInt(1, numberOfBooks); query.setInt(2, authorId); query.executeUpdate(); query.close(); } }
function createBook(param) {
var entityToCreate = getEntry(param.afterTableName, param.connection); if (!entityToCreate) { throw “Invalid entry”; } // TODO: Change to more generic solution var isbn = entityToCreate.getString(3); var bookName = entityToCreate.getString(4); var price = entityToCreate.getString(5); var authorName = entityToCreate.getString(7);
if (!authorName || !bookName || !isbn || !price) { throw “Bad Request”; }
var authorId = -1;
var authorRecord = findAuthorByName(authorName, param.connection); if (!authorRecord) { authorId = insertNewAuthor(authorName, param.connection); } else { authorId = authorRecord.getInt(1); }
insertNewBook(bookName, authorId, isbn,price, param.connection); } |
Run the Node.js application.
Open the Run Console to see the progress.
Click the application URL in the run console to validate it’s running correctly.
The Node application will open a new browser tab.
This is what the node project should look like:
Step 4: Add a UI module to the MTA project
Of course, we’ll want a user interface, so let’s add a SAP Fiori Master-Detail module to the project.
Name the module booksui.
Select the Node.js service as the OData source of the UI module.
In the Template Customization step, select the Author as the Object Collection and books as the Line Item Collection.
Click Finish to generate the UI module.
Run the UI application.
Step 5: Enable adding book records
Now let’s add the ability to add a new book to the database.
Open the Master.view.xml using the SAP Web IDE Layout editor.
Add a button to the master page footer, and give it the following properties:
- Active Icon: sap-icon://add
- Icon: sap-icon://add
- Icon First: True
Add an event handler to the button Press event.
onCreateNewBook: function() { this._oCreateBookDialog = sap.ui.xmlfragment(“com.sap.demo.view.createBook”, this); this.getView().addDependent(this._oCreateBookDialog); var oModel = new sap.ui.model.json.JSONModel({ bookName: “”, authorName: “”, isbn: “”, price: 0, authorNamePlaceholder: “Enter the author name”, bookNamePlaceholder: “Enter the book name”, isbnPlaceholder: “ISBN”, pricePlaceholder: “Book Price (Numbers only)”, doneButtonActive: true });
this._oCreateBookDialog.setModel(oModel); this._oCreateBookDialog.open(); }, onCancelBookCreation: function(oEvent) { this._oCreateBookDialog.close(); }, onCreateBook: function(oEvent) { // get the JSON model var oModel = oEvent.getSource().getModel();
var payload = { “bookId”: 0, “authorId”: 0, “isbn” : oModel.oData.isbn, “bookName”: oModel.oData.bookName, “authorName”: oModel.oData.authorName, “price” : oModel.oData.price };
var self = this; this.getView().getModel().create(“/Book”,payload,{ success: function() { self.getView().getModel().refresh(); } });
this._oCreateBookDialog.close(); } }); |
Add a new fragment to the UI module by creating a new file here: storeui –> resources –> webapp –> view. Call the file createBook.fragment.xml.
File name: createBook.fragment.xml
File content:
<core:FragmentDefinition xmlns=”sap.m” xmlns:core=”sap.ui.core”> <Dialog title=”New Book”> <beginButton> <Button text=”Cancel” press=”onCancelBookCreation”/> </beginButton> <endButton> <Button text=”Done” type=”Emphasized” press=”onCreateBook” enabled=”{/doneButtonActive}”/> </endButton> <content> <VBox width=”100%” direction=”Column” displayInline=”true”> <items> <Input width=”100%” value=”{/bookName}” placeholder=”{/bookNamePlaceholder}”/> <Input width=”100%” value=”{/authorName}” placeholder=”{/authorNamePlaceholder}”/> <Input width=”100%” value=”{/isbn}” placeholder=”{/isbnPlaceholder}”/> <Input width=”100%” value=”{/price}” placeholder=”{/pricePlaceholder}”/> </items> </VBox> </content> </Dialog> </core:FragmentDefinition> |
Run the UI application and test the Add function.
Step 6: Deploy to CF
Build the MTA project.
The result of the MTA build is an MTA archive file (mtar).
Right-click the .mtar file and deploy it to Cloud Foundry.
For more information please read this great blog post about SAP Web IDE Full-Stack.
If you have any questions, ask our community , check out our documentation ,or contact us.
Register here to get the latest news and updates from SAP Web IDE.
Hi, Uri .
Very interesting blog.
I have some question. How can I transport my SAPUI5 project from SAP HCP Web IDE to Web IDE for Hana. When I doing export and import project, importing project in Web IDE for HANA do not running and run button is disabled. I need to change something?
IMHO you need a xs-app.json instead of a neo-app.json .
Thanks for idea, Wolfgang.
But when I replaced neo-app.json with xs-app.json nothing changed.
Run button remains disabled.
It would be nice to add a spring-boot based Java module in Hana 2.0 tutorials / sample like this one. It would be interesting show how different languages can cope and work together.
Hi Uri,
are the wizards to add the database, Node.js and UI5 module already available on the SCP Trial Multicloud Web IDE? Or when will they come available?
Best regards
Gregor
Hi Gregor,
I had the same question and I am a little bit disappointed that no support is there for modules (except for HTML5) modules in the multi-cloud version of SAP Web IDE on CP at the moment.
In post Announcing General Availability of SAP Web IDE, multi-cloud version there is a little sentence which indicates that.
Also in the comments of the same post it was confirmed that only HTML5 modules are supported at the moment. Other modules should follow in Q3.
As said I am little bit disappointed and was confused, because of that post here. I thought, that functionality which is described in that post would be available. But as it seems it is just a teaser for that what will be (maybe) available in Q3.
Regards,
Florian
Full stack development is not yet available in the Web IDE multi-cloud edition.
We are slowly adding features to the cloud edition.
Stay tuned, HANA and Java development are coming soon...
Hi, all.
When I download the entire application source code from GitHub and try to build him i getting error.
Can someone faced such a mistake?
Hi all,
I am having issue with the node.js odata service. When I start the node.js module, I got the following error.
Any thoughts on the error?