I have been playing around with Google App Engine recently and, because the SAPUI5 library has recently gone open source, I have decided to introduce the two together and share my experiences. In order to do so, I have created a basic CRUD application that will manage Employee information. You can view the entire example on GitHub, this blog post will just showcase the most relevant pieces to shorten the post.

Backend

Google App Engine is a cloud computing platform as a service for developing and hosting web applications through Google’s infrastructure.

Employee JDO Class

I am making use of Java and JDO to access the datastore (there are many other ways of achieving this on Google App Engine).


@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Employee {
// ...
// Private persistent class attributes
// ...
  public Employee(String firstName, String lastName, String phoneNumber, String cellNumber, String email, String idNumber, String country, String city) {
       this.key = KeyFactory.createKey(Employee.class.getSimpleName(), email);
       this.firstName = firstName;
       this.lastName = lastName;
       this.phoneNumber = phoneNumber;
       this.cellNumber = cellNumber;
       this.email = email;
       this.idNumber = idNumber;
       this.country = country;
       this.city = city;
  }
// ...
// Public Getters and Setters



// ...

Employee Endpoint Class

I have made use of the Google Cloud Endpoint Service to easily generate my web backend API and JavaScript client libraries. I have decided to only post the method and API access names here for readability and to shorten the blog, you can view the complete demo on Github.


@ApiMethod(name = "listEmployee")
public CollectionResponse<Employee> listEmployee(@Named("cursor") String cursorString, @Named("limit") Integer limit)
@ApiMethod(name = "getEmployee")public Employee getEmployee(@Named("email") String email)
@ApiMethod(name = "insertEmployee") // Update employee is the same
public Employee insertEmployee(
@Named("firstName") String firstName,
  @Named("lastName") String lastName,
  @Named("phoneNumber") String phoneNumber,
  @Named("cellNumber") String cellNumber,
  @Named("email") String email,
  @Named("idNumber") String idNumber,
  @Named("country") String country,
  @Named("city") String city )
@ApiMethod(name = "removeEmployee")
public void removeEmployee(@Named("email") String email)


By making use of the Google Plugin for Eclipse, I am easily capable of generating client libraries for iOS, Android and specifically JavaScript.

Connecting to the backend

In order to access my endpoints and use them in my OpenUI5 JavaScript application, I need to call the Google APIs JavaScript Client Library and load the endpoint.

Endpoint Loader – Custom Class

I have created a custom OpenUI5 class that will take care of loading the client library and allow easy access throughout the application:


jQuery.sap.declare("Endpoint");
jQuery.sap.require("sap.ui.base.ManagedObject");
sap.ui.base.ManagedObject.extend("Endpoint", {
       metadata: {
            properties: {
                 "root": {type: "string", defaultValue: "http://127.0.0.1:8888/_ah/api"},
                 "gapiObject": "object",
                 "library": "object",
             }
         },
         init: function() {
              // Gets called right after instantiation
         },
         successDialog: new sap.m.Dialog({
                 title: "Endpoint Client Libraries Successfully Loaded.",
                 leftButton:  new sap.m.Button({
                 text: "Close",
                 state: sap.ui.core.ValueState.Success,
                 press: function(oControlEvent) {
                         if( oControlEvent.getSource().getParent().isOpen() ) {
                                oControlEvent.getSource().getParent().close();
                           }
                 }   
       }),
  }),
    createLibrary: function() {
          var gapis = this.getGapiObject();
          var successDialog = this.successDialog;
          var simpleForm    = new sap.ui.layout.form.SimpleForm({
                   content: [ new sap.ui.core.Title({ text: "Success!" }) ]
         });
         successDialog.addContent(simpleForm);
          var response = function(endpointName) {
              console.log(endpointName + " has been loaded.");
              simpleForm.addContent( new sap.m.Label({ text: "Successfully Loaded" }) );
              simpleForm.addContent( new sap.m.Text({ text: endpointName }) );
               if( !successDialog.isOpen() ) {
                   successDialog.open();
              }
         };
          for( k = 0; k < gapis.length; k++ ) {
              // Load the client libraries through googles api object (Connects to the endpoint classes)
              gapi.client.load(gapis[k].name, gapis[k].version, response(gapis[k].name), this.getRoot());
              // Set the google api client to the library object for easy access
               this.setLibrary(gapi.client);
          }
          return this.getLibrary();
    },
    getEndpoint: function(name) {
          return this.getLibrary()[name];
    }


Custom Application Class

The project follows the MVC principles as discussed in the developers guide. I have made a few changes to the application class and the way it is loaded in the index.html file.


jQuery.sap.declare("Application");
jQuery.sap.require("sap.ui.app.Application");
jQuery.sap.require("Endpoint");
sap.ui.app.Application.extend("Application", {
       init: function() {
            // Set global models...
       },
       main: function() {
            // Create app view and set to the root html element
            var root = this.getRoot();
            var application = sap.ui.jsview("application", "js.Application");
            application.placeAt(root);
            // Connect to Google App Engine Endpoints
            var library = new Endpoint({
                 id: "clientEndpoint",
                 gapiObject: [
                     { name: "employeeendpoint", version: "v1" }  // Allows for multiple endpoints to be loaded
                 ]
            }).createLibrary();  // Create and return the loaded client library
            // Attach the client library to the application for easy access
            application.setClientEndpointLibrary( library );
       }
});


Index.html


<!DOCTYPE html>
<html lang="en">
<head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
       <title>OpenUI5 On Google App Engine</title>
      <script id="sap-ui-bootstrap"
            src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
            data-sap-ui-theme="sap_bluecrystal"
            data-sap-ui-libs="sap.ui.commons, sap.m"></script>
      <script>
            // Application Content...
            sap.ui.localResources("js");
            // Register Module Paths
            jQuery.sap.registerModulePath("Application", "Application");
            jQuery.sap.registerModulePath("Endpoint", "Endpoint");
      </script>
      <script type="text/javascript">
            var initializeGapi = function() {
                 // Launch Application...
                 jQuery.sap.require("Application");
                 var oApp = new Application({ root: "content" });
            }
      </script>
      <script type="text/javascript" src="https://apis.google.com/js/client.js?onload=initializeGapi"></script>
</head>
<body class="sapUiBody">
      <div id="content"></div>
</body>
</html>




I have incorporated the loading of the endpoint classes within the OpenUI5 application in order to reduce the time the user has to wait for the client library to load. If I had not done so, the user interface would load first and, depending on where the Google API load function is called, the data would not be available to query, leaving the user with an application that contains empty data.

Frontend – The Application

When the application is started and the main function is called, the endpoint classes are loaded and a message is displayed to the user. In this case I have just called a dialog to show that the endpoints have been loaded, but I have not included a check to see whether or not it was actually successful, despite calling it the “successDialog”.

/wp-content/uploads/2014/01/05_372012.png

Creating an Employee

In the CreateEmployee.controller.js I am assigning the loaded employeeEndpoint to the controller and calling a method that will use the InsertEmployee method of the endpoint class:


onBeforeRendering: function() {
     // Assign the employee endpoint to this controller to use
      this.employeeEndpoint = this.getView().getParent().getParent().getParent().library.employeeendpoint;
},




I have called the endpoint library from the application by navigating through the relationships. There might be a better method of achieving this and I hope to improve on this solution in time, I welcome any suggestions and feedback.


save: function() {
       var resultDialog = this.resultDialog;
       // Inputs
       var firstName   = this.getView().firstName;
       var lastName    = this.getView().lastName;
       var city        = this.getView().city;
       var country     = this.getView().country;
       var phoneNumber = this.getView().phoneNumber;
       var cellNumber  = this.getView().cellNumber;
       var email       = this.getView().email;
       var clearInputs = function() {
            firstName.setValue("");
            lastName.setValue("");
            city.setValue("");
            country.setValue("");
            phoneNumber.setValue("");
            cellNumber.setValue("");
            email.setValue("");
       }
       var employee = {
            firstName: firstName.getValue(),
            lastName: lastName.getValue(),
            city: city.getValue(),
            country: country.getValue(),
            phoneNumber: phoneNumber.getValue(),
            cellNumber: cellNumber.getValue(),
            email: email.getValue()
       };
       this.employeeEndpoint.insertEmployee(employee).execute(function(response) {
            var resultMessage = new sap.m.Text({});
            if( !response.code ) { // No Error
                 if( !resultDialog.isOpen() ) {
                      resultDialog.setTitle("Success");
                      resultDialog.setState(sap.ui.core.ValueState.Success);
                      resultMessage.setText("Successfully saved the employee to the datastore.");
                      resultDialog.addContent(resultMessage);
                      resultDialog.open();
                      clearInputs();
                 }
            } else  { // Error
                 if( !resultDialog.isOpen() ) {
                      resultDialog.setTitle("Error");
                      resultDialog.setState(sap.ui.core.ValueState.Error);
                      resultMessage.setText(response.message);
                      resultDialog.addContent(resultMessage);
                      resultDialog.open();
 
                      clearInputs();
                 }
            }
       });
  }



/wp-content/uploads/2014/01/03_372009.png

/wp-content/uploads/2014/01/04_372010.png

Reading Employees


listEmployees: function() {
       var controller = this;
       var listEmployee = this.getView().listEmployee;
       this.employeeEndpoint.listEmployee().execute(function(response) {
            var jsonModel = new sap.ui.model.json.JSONModel(response);
            listEmployee.setModel(jsonModel);
            var template = new sap.m.ObjectListItem({
                 title: "{key/name}",
                 type: sap.m.ListType.Navigation,
                 press: [controller.toGetEmployee, controller]
            });
            listEmployee.bindItems("/result/items", template);
       });
  }



/wp-content/uploads/2014/01/06_372013.png

The data is stored in Google App Engines Datastore as shown below:

/wp-content/uploads/2014/01/07_372014.png

Deleting Employee


  deleteEmployee: function() {
       var employee = {
            email: this.getView().email.getText()
       };
       this.employeeEndpoint.removeEmployee(employee).execute(function(response) {
            // Response...
       });
  }


The OpenUI5 Library is still so young and yet so feature-rich, it excites me to see what other people are, and have already, come up with. This is my first blog post on SCN and as I have mentioned before, I welcome all feedback and suggestions and even if it is just a discussion on other technologies, I would love to engage in them with you.

Complete solution can be found on GitHub.

Kind regards

Miki von Ketelhodt

To report this post you need to login first.

9 Comments

You must be Logged on to comment or reply to a post.

    1. Mikael von Ketelhodt Post author

      Hi Maximilian,

      It is an awesome platform with great tools 🙂 . Hopefully you can share some of your experiences afterwards as well.

      Thanks for your comment.

      Kind regards

      Miki

      (0) 
  1. Simon Kemp

    Excellent blog Miki and thank yo for sharing it here with the community and for posting the source on GitHub. This was your first blog on SCN and I know it’s not your last, keep up the great work!

    (0) 

Leave a Reply