This article was originally posted to the SAP Developer Center on Jan 26, 2014

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.

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

Employee JDO Class

@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
// ...

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.

Employee Endpoint Class

@ApiMethod(name = "listEmployee")
public CollectionResponse 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.

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

Endpoint Loader – Custom Class

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];
  }
});

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.

Custom Application Class

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”.

openui5-meets-google-app-engine-01

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:

Creating an Employee

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();
      }
    }
  });
}

openui5-meets-google-app-engine-02

openui5-meets-google-app-engine-03

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);
  });
}

openui5-meets-google-app-engine-04

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

openui5-meets-google-app-engine-05

Deleting Employees

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.