Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
stefan_heitzer
Participant

Hi everybody,

during my internship for the German SAP-consulting company T.CON (www.team-con.de), I had the chance to build a prototype for a customer portal that is built on SAP HANA Cloud Platform (HCP) and the Cloud Portal, and is integrated with SAP ERP. It is not meant to be used by T.CON itself, but rather by its customers which are mainly SMEs and also bigger companies in various industries.

T.CON identified this application as a very much suitable use case for the HANA Cloud Platform, as companies are very easily able to build a modern, mobile enabled external facing portal without having to provide the infrastructure for it.

In my blog post I would like to give you a short technical overview of the main steps that were necessary to build the customer portal:

  1. SAPUI5 views to display data from the ERP-System, via the Cloud Connector
  2. Custom user authentication
  3. Custom widgets for the HANA Cloud Portal
  4. Custom theme for the Portal

1. SAPUI5 view to display data from the ERP-system, via the Cloud Connector

The centerpiece of the HANA Cloud Portal are the widgets. Inside those widgets you have the opportunity to display a huge variety of content like normal text, videos, documents, images and also linkings to other websites. This means that you are also able to create applications on the HANA Cloud Platform and display them inside such a widget. And this is exactly what we are going to do.

The first step is that we'll create a new dynamic web project. Afterwards we have to add a new "index.html" file. This will be the entrance and also the global storage for general objects which we need for our whole application. Due to the fact that we also want to make our portal available on mobile devices we're going to develop with the mobile library of SAPUI5. In our "index.html" we only declare a new view and a new application just like you can see below:



var myPage = new sap.ui.view({
     id: "myOrderView",
     viewName: "content.myOrderView",
     type: sap.ui.core.mvc.ViewType.JS
});
var myApp = new sap.m.App({
     initialPage: "myOrderView"
});
myApp.addPage(myPage);
myApp.placeAt("content");

Now we can come over to our view. We just want to have a simple list which shows us our recent orders for example. Therefore we have inside of our "myOrderView.view.js" the function "createContent":



createContent : function(oController){
     //First of all we're defining a header which should represent the top of our orders list
     var myHeader = new sap.m.Bar("myHeader", {
          contentLeft: [
               new sap.m.Label({
                    text: "Current orders"              
               })
          ],
          contentRight: [
               new sap.m.Button({                                             //This button brings us to the main list which shows all the orders
                    icon: "sap-icon://shortcut",                            //here in this list we only want, as I already said, to see the recent orders (e. g. 5)
                    text: "Main list",
                    press: oController.onButtonPressed
               }),
               new sap.m.Button({                                             //This button is for changing the settings. Later on we want to customize the amount of visible orders
                    icon: "sap-icon://settings",                          //You will read more about this action in point 3 (Custom widgets for the HANA Cloud Portal)
                    press: oController.onSettingsPressed              
               })
          ],
     });
    //When you press on the button for settings a popover is going to be opened. Therefore we're defining here a header too
     var mySettingsHeader = new sap.m.Bar("mySettingsHeader", {
          contentLeft: [
               new sap.m.Button({
                    text: "Cancel",
                    press: oController.onCancelPressed
               })
          ],
          contentMiddle: [
               new sap.m.Label({
                    text: "Settings"              
               })
          ],
          contentRight: [
               new sap.m.Button({
                    text: "Save",
                    pres: "oController.onSavePressed
               })
          ]
     });
     //Now we need to define the popover itself
     var myPopover = new sap.m.Popover({
          id: "myPopover",
          placement: sap.m.PlacementType.Left,
          title: "Settings",
          content: [
               new sap.m.List({
                    items: [
                         new sap.m.InputListItem({
                              label: "Number of entries:",
                              content: [
                                   new sap.m.Input({
                                        id: "myInput",
                                        type: sap.m.InputType.Text
                                   })
                              ]
                         })
                    ]
               })
          ],
          customHeader: mySettingsHeader
     });
     //The last step is here now to define the list
     var myList = new sap.m.List({
          id: "myList",
          columns: [
               new sap.m.Column({
                    header: new sap.m.Label({
                         text: "Order number"
                    })
               }),
               new sap.m.Column({
                    header: new sap.m.Label({
                         text: "Date of order"
                    })
               })
          ]
     });
     return new sap.m.Page({
          customHeader: myHeader,
          content: [
               myList
          ]
     });
}

Now we have defined what we want to see. The next step is to fill the list with the received OData. We just need to bind the result to our created list. The coding looks like the following:



myListe.bindAggregation("items", {
     path: "/",
     template: new sap.m.ColumnListItem({
          cells: [
               new sap.m.Text({
                    text: "{Ordernumber}"
               }),
               new sap.m.Text({
                    text: "{Orderdate}"
               })
          ]
     })
}

Afterwards when you run the application you should see something similar like:


The question which is left right now is only how you can receive the data. Therefore you first have to understand how you can build up connections to a SAP system. As already said you are able to run, inside an existing widget, standard SAPUI5 applications. In my case, all the applications are receiving their data from a remote system provided as OData through the SAP Netwaver Gateway Service. The only problem is that you can't build up a direct connection between the application and your SAP system. Therefore you have to use the so-called "Destination Service". This service builds up a connection with the SAP Cloud Connector and this, on the other hand, builds up the connection with the SAP system:


For handling the OData in your SAPUI5 application you need to enter the following code inside your "index.html":



var myODataModel = new sap.ui.model.odata.ODataModel(
     "/proxy/YOUR_DESTINATION";
);

Some of you may have already seen this "proxy" call. Normally you would directly call a specific URL which provides you the OData. Due to the same origin policy this isn't possible in the SAP HANA Cloud Platform but there exists the so-called "ProxyServlet". This servlet is responsible for translating the destination und building up the connection to the SAP HANA Cloud Connector. The destinations themselves are defined in the SAP HANA Cloud Platform at the corresponding application. These are virtual URLs which means that within the cloud connector the virtual URLs have to be translated into the original ones that point to the real SAP system. In the SAP HANA Cloud Connector you are also able to define the FMs and the services which should be accessable from applications running on the Cloud Platform.

2. Custom user authentication

Some of you may have asked themselves whether it is possible to make a custom user authentification. The standard behavior of the Cloud Portal is that you have the choice between three different access levels:

  • public

     Everybody can access the Cloud Portal

  • restricted

     Only authorized users have access to the Cloud Portal

  • private

     Only invited users have access to the Cloud Portal

When you switch to restricted or private you always have the problem that you must sign on with your SCN user for example. So what are you going to do when you want a user authentication but not all of your, for example customers, do have a SCN user? The solution is very easy. Just switch to public and create your own one! There are only 3 main things that you have to include:

  • A login HTML-File
  • A login servlet
  • A login filter

In our recently created dynamic web project we have to create a HTML-File which only has the following structure:


<form method="POST" action="LoginCheck" target="_top">
     <table align="center" style="font-size: 10pt;">
          <tr>
               <td>User:</td>
               <td><input type="text" name="user" size="60"></td>
          </tr>   
          <tr>
               <td>Password:</td>
               <td><input type="password" name="password" size="60"></td>
          </tr>
     </table>
</form>

What are we doing here? We only create a simple form which has two input fields. One for the user and the other one for the password. Due to the fact that we have a dynamic web project we also have a "web.xml" file. In this file we need to enter the following code-snippet:


<welcome-file-list>
     <welcome-file>login.html</welcome-file>
</welcome-file-list>

With this short code we can make it that our project always looks for the "login.html" instead of an "index.html". In other words, we're just setting a new homepage. As you can see I'm setting as an action "LoginCheck". This action refers actually to a servlet which is responsible for the login mechanism, the so-called "LoginServlet.java". For making this servlet running we have to work again with the "web.xml". By inserting the following code we can achieve that the servlet always responses to requests for "LoginCheck":


<servlet>
     <description></description>
     <display-name>LoginServlet</display-name>
     <servlet-name>LoginServlet</servlet-name>
     <servlet-class>LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
     <servlet-name>LoginServlet</servlet-name>
     <url-pattern>/LoginCheck</url-pattern>
</servlet-mapping>

In the servlet itself we are now able to implement code which makes for us a user authentification. Via JCO we have the opportunity to call a self-made FM which, for example, takes the username and the passwort and checks whether the credentials are right or not. If they're correct just save for example the user into a session variable and afterwards you only have to test if the session variable exists or not. If you want to implement such a method you have to do the following:


protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
     String username = request.getParameter("user");
     String password = request.getParameter("password");
     //Here you could also encode the password
     try{
          JCoParameterList myImportList;
          JCoParameterList myExportList;
          JCoDestination myDestination = JCoDestinationManager.getDestination("YOUR_DESTINATION");
          JCoRepository myRepository = myDestination.getRepository();
          JCoFunction myFunction = myRepository.getFunction("YOUR_FUNCTION");
          myImportList = myFunction.getImportParameterList();
          myImportList.setValue("USER", username);
          myImportList.setValue("PASSWORD", password);
          myFunction.execute(myDestination);
          myExportList = myFunction.getExportParameterList();
          //Here you would get the values of your exporting parameters
          //You would also make here a query if the users exists like:
          if (YOUR_RETURN_VALUE == true){
               //Build up a session and set the user credentials
               request.getSession().setAttribute("user", username);
          }else{
               //redirect to an error page like:
               request.getRequestDispatcher("YOUR_ERROR_PAGE").forward(request, response);
          }
     }catch(AbapException myAbapException){
          //redirect to an error page like:
          request.getRequestDispatcher("YOUR_ERROR_PAGE").forward(request, response);
     }
     catch(JCoException myJCoException){
          //redirect to an error page like:
          request.getRequestDispatcher("YOUR_ERROR_PAGE").forward(request, response);
     }
}

Now we come over to the login filter. It's a possible scenario that our dynamic web project has more files (e. g. some other  SAPUI5 views). We want now that we can't access all these files if we aren't authenticated. And this work makes the filter. This means that we are able to store all the files which shouldn't be accessable without a valid user authentication in special folder e. g. "Secured". The filter can now be configured that it's going to be activated if a request for one of the files in this folder comes in. Here we also have to configure the "web.xml":


<filter>
     <filter-name>LoginFilter</filter-name>
     <filter-class>LoginFilter</filter-class>
</filter>
<filter-mapping>
     <filter-name>LoginFilter</filter-name>
     <url-pattern>/Secured/*</url-pattern>
</filter-mapping>

Important here is the mapping. As already mentioned we tell our new "LoginFilter" that every request for a special file in "Secured" should be checked. And this testing is been done by the following coding:


public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{
     //This here depends on which attributes you have set in your session. If you have set only a username than check someting like this:
     if(((HttpServletRequest)  request).getSession().getAttribute("user") != null){
          //Just redirect to the page which you want to open         
          chain.doFilter(request, response);    
     }else{
          //Redirect to the login page because no valid user is set in the session
          ((HttpServletResponse) response).sendRedirect("../login.html");
     }
}

What has this to do now with our Cloud Portal? First of all as a small recapture:

We created a dynamic web project. We are able to upload this dynamic web project to the HANA Cloud platform. We set the startpage to the "login.html". Everytime when we start our cloud application we will be redirected to the "login.html" and have to enter our user credentials. By clicking submit the entered data will be checked. If we make a request to another page which is stored in our special folder "Secured" then our filter is going to be activated and it checks whether we are authenticated. If so we're going to be redirected to the requested site otherwise we're going to get back to our "login.html".

And now we get back to our connection with the HANA Cloud Portal. We are now able to tell our cloud application that if the user authentication was successfull we'll be redirected to the Cloud Portal. For this we only have to edit the "LoginServlet":


//In the section where you check whether the return value is true just add:
response.sendRedirect("YOUR_URL_TO_HANA_CLOUD_PORTAL");

This means that when we want to display for example content within a widget which has it's origin inside the "Secured" folder and we are not authenticated we can't view the content. This is equals to the case when a user knows the URL for accessing the Cloud Portal directly.


Addition to the SAPUI5 views:

If you want to achieve that the requests to the SAP system are going to be checked by the "LoginFilter" too you do have to add another filter entry:



<filter-mapping>
     <filter-name>LogingFilter</filter-name>
     <url-pattern>/proxy/*</url-pattern>
</filter-mapping>

Now every request for something with "proxy/..." will be checked by the filter too and if you're not authenticated you will be redirected back to the "login.html".


3. Customized Widgets

As you already know we have the following scenario: You want to create a widget on the startpage of your HANA Cloud Portal which shows a customizable number of orders. Customizable because you have a second page which provides you a complete list of all your orders. You have now the choice between existing widgets and custom widgets.

At first I'm going to show you how you're able to realize this situation with existing widgets.

We can use the existing dynamic web project of point one. We have created a simple SAPUI5 application and placed it in the "Secured" folder. The application consists only out of a standard list which will display for example at the beginning 5 orders. The list may look like the following:

On the right site of the screenshot you can see a gear-wheel. By clicking on it you get the following view:

Here you can see the settings for the listing. "Anzahl Listen..." stands for the number of entries you want to see. In this case 5. You can now enter another number e. g. 20. By clicking "Speichern" the shown list is going to be updated. It's now very important to understand what's happening in the background. In the SAP system there is a table which stores the amount of list entries for a specific category and a specific user. So at first we need to store the new amount inside our table. Therefore we create a JSP-File which receives the new amount as a GET parameter. If you click now the "Speichern" button you'll be redirected to this JSP by the following code:


onSpeichernPressed: function(){
     var myNumberEntries = sap.ui.getCore().getControl("NUMBER_ENTRIES");
     window.open("saveNumber.jsp?number=" + myNumberEntries.getValue()", "_parent");
}

Now we will be redirected to the "saveNumber.jsp". This JSP-File builds up a connection with a SAP system and tries to save the new amount. If the call is successfull you're going to be redirected and the list is updated. You only have to create another JCO-call like you've already done for checking the user credentials. You can use this one as an orientation.

When your update for the amount was successfull you will be, as already mentioned, redirected to HANA Cloud Portal. Now it's essential that the list is going to be updated to the correct amount. Every request for reading OData has a "$top" parameter. This parameter tells the collection how many entries should appear. Due to the fact that we only call "/proxy/YOUR_DESTINATION" we have to configure the request. This is going to be done in the "ProxyServlet". Therefore we need the following method:


protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
}

Here we have the variables "pathInfo" and "queryString". These are the deciding ones for the destination. We can now overwrite the "queryString". We only have to add the "$top" parameter and of course the amount we want to have. This number is going to be received by another JCO call. This one is similar to the one for saving the amount so I'm not going to write it down here.

Afterwards you should be able to save and load a list with a customized amount of entries.

Now let's switch to the same situation but with self-made widgets.

With these widgets you have the advantage of saving a lot of coding work if you want to personalize widgets. The disadvantage of them is that you can only use them when your HANA Cloud Portal is switched to private or restricted. You will soon see the reason for that.

The main part of a own widget is a central XML-File. This file contains a general description of the widget and consists of three main parts:

  • content

     Here you can find the definition of what the widget itself should display. This can either be a link to an external HTML-File or the HTML-Content will be directly      created in this area.

  • user preferences

     This is the area where the customizing takes place. You can define own parameters (=keys) and assign them specific values.

  • widget preferences

     This is something like a general area where you can provide specific information like who is the author, what is the title of the widget etc. Sometimes you      also need additional APIs. This is the right place for integrating them.

As mentioned we want to realize the same situation. The structure of the SAPUI5 application is the same, the only thing which is different is that we can forget all the queries and inserts into the specific table for the amount. Instead of the queries and inserts we can use the so-called "Gadget API". This API enables us to set and read unique values. The condition for making this to work is that we have definded the corresponding keys at the user preferences.

Here is the code snippet which shows how to load the right amount of orders for a specifig user:


var preferences = new gadgets.GadgetPrefs();
var orders = preferences.getPreference('YOUR_KEY_VALUE');

Now you can build the URL for your OData call with the right "$top" parameter.

If you want to save now a new value for the amount of orders, you have to add the following:


var orders = sap.ui.getCore().getControl("NUMBER_ENTRIES").getValue();
var preferences = new gadgets.GadgetPrefs();
preferences.setPreference("YOUR_KEY_VALUE", orders);

As already mentioned you have to define the right key at the user preferences. Therefore you have to enter the following code into your XML-File of your widget:


<UserPref name="YOUR_KEY_VALUE"
                    display_name="YOUR_KEY_VALUE"
                    datatype="string"
                    default_value="5" />

As you can see you don't know for which user the settings are stored or better said you can't take influence for which user it should be saved. This is the main problem with the "Gadget API". So if you switch the portal to public because you want to use your own custom user authentication you have to problem that the "Gadget API" doesn't know who is currently working with it. This was also the main reason why I switched to the version of existing widgets and build in my own customization.

4. Custom theme for the Portal

Some customers do also want to have specific designs for the HANA Cloud Portal website. The Portal itself has its own administration surface. There you have a range of different settings like general site settings,user administration, site administration but also an area for customizing the design. At this area you have the opportunity to set special layouts for your site but also take influence to the settings of these layouts. The HANA Cloud Portal comes with a number of precasted designs. The easiest way to create your own one is to download an existing one, customize it and re-upload it to the administration surface of the HANA Cloud Portal. Behind such a layout lies a so-called LESS-File. LESS is an extension for the conventional CSS. Thereby you have the advantage that you can define variables, complete functions and you also can create complete nestings and develop an intelligent structure and so your able to avoid code repetitions. As soon as you have uploaded your new design / layout you're able to select it within the design settings. A custom made design could for example look like the following:

If you want to receive further information about the HANA Cloud Portal and of course the HANA Cloud Platform or LESS check the following links:

I hope you enjoyed reading my blog post and if you have further questions, don't hesitate to contact me.

Thanks for reading!

Kind regards,

Stefan

7 Comments
Labels in this area