2013-07-18

Develop an SAP UI5 Application

Overview

SAP has built a great library of controls for generating colorful, animated, snappy reports for the business.

Here’s a simple example of an SAP UI5 application built with Eclipse and published to an SAP BSP application. The report displays the results of an ‘Average Turn Time’ report in a pie chart. It offers inputs to filter the data to only Temporary changes or all changes and also lets the user adjust the date range.

image

Dependencies

SAP ERP software component UISAPUI5 (SAP UI5)

Eclipse (I’m using Version: 4.2.1)

SAP HTML5 SDK (I’m using version 1.12.1)

  • SAP core library (installed on a server)
  • client software (installed in Eclipse – helpful post)

JSON HTTP Handler

First I needed a way to convert the results from an SAP function module to JSON format to be consumed by the SAP UI5 components. I found a project on SAP SCN that converts function module results to JSON. I set it up in SICF.

https://cw.sdn.sap.com/cw/groups/json-adapter-for-abap-function-modules

clip_image002

I had to make one small change to use a different class for the actual JSON conversion.  In method SERIALIZE_ID of class ZCL_JSON_HANDLER, instead of calling the transformation, I’m using class CL_TREX_JSON_SERIALIZER.

   1: *          call transformation id options data_refs = 'embedded'
   2: *                                         initial_components = 'include'
   3: *                                 source (stab)
   4: *                                 result xml json_writer.
   5: *          o_string = cl_abap_codepage=>convert_from( json_writer->get_output( ) ).
   6:           CREATE OBJECT json_writer
   7:             EXPORTING
   8:               data = stab.
   9:           json_writer->serialize( ).
  10:           o_string = json_writer->get_data( ). 

SAP UI5 Application


In Eclipse, I created the SAP UI5 application zem_ui5_gcs01.  The application uses a very simple index.html.  However, be sure to include the additional library: sap.viz.



   1: <!DOCTYPE html>
   2: <html><head>
   3:     <meta http-equiv='X-UA-Compatible' content='IE=edge' />
   4:     <title>Average Turn Time</title>
   5:  
   6:     <script id='sap-ui-bootstrap' type='text/javascript'
   7:         src='http://[host-to-sap-ui5-core]/sapui5/resources/sap-ui-core-all.js'
   8:         data-sap-ui-theme='sap_goldreflection'
   9:         data-sap-ui-libs='sap.ui.commons, sap.viz'></script>
   1:  
   2:             
   3: <script>
   4:     sap.ui.localResources('avg_turn_time');
   5:     var view = sap.ui.view({
   6:         id: "main1",
   7:         viewName:"avg_turn_time.main",
   8:         type:sap.ui.core.mvc.ViewType.JS
   9:     });
  10:     view.placeAt('content');
</script>
  10:  
  11: </head>
  12: <body class='sapUiBody'>
  13:     <div id='content'></div>
  14: </body>
  15: </html>

It calls function module Z_E_RFC_GCS_AVG_TURN_TIME. This returns in the following format:


1:  {  
2: "et_actions": [
3: { "action":"00012", "display_order":"00002", "task":"Through Request",
4: "day_count":1, "record_count":99, "day_count_2":1, "record_count_2":99 },
5: { "action":"00046", "display_order":"00003", "task":"Through Analysis",
6: "day_count":1, "record_count":99, "day_count_2":0, "record_count_2":99 },
7: { "action":"00027", "display_order":"00004", "task":"Through Approval",
8: "day_count":2, "record_count":99, "day_count_2":1, "record_count_2":99 },
9: { "action":"00088", "display_order":"00005", "task":"Through Implementation",
10: "day_count":49, "record_count":91, "day_count_2":48, "record_count_2":91 },
11: { "action":"00000", "display_order":"00006", "task":"Through Archive",
12: "day_count":59, "record_count":91, "day_count_2":10, "record_count_2":91 }
13: ],
14: "e_record_count":100
15: }

The SAP UI5 javascript application contains just one view avg_turn_time/main.view.js, that contains the UI for the report.



   1: sap.ui.jsview("avg_turn_time.main", {
   2:  
   3:       getControllerName : function() {
   4:          return "avg_turn_time.main";
   5:       },
   6:  
   7:       createContent : function(oController) {
   8:           var controls = [];
   9:           
  10:           var btn1 = new sap.ui.commons.Button({
  11:               id: this.createId('btn1'),
  12:               text: 'press me', 
  13:               press: [ oController.button_press, oController ]
  14:           });
  15:                               
  16:           var lblTemp = new sap.ui.commons.Label(this.createId('lblTemp'), { 
  17:               text: "Temporary:" 
  18:           });
  19:           var cbTemp = new sap.ui.commons.CheckBox(this.createId('cbTemp'), { 
  20:               checked: true,
  21:               change: [ oController.checkbox_change, oController ]
  22:           });
  23:           
  24:           var lblDate = new sap.ui.commons.Label(this.createId('lblDate'), { 
  25:               text: "Date:"
  26:           });
  27:           var date1 = new sap.ui.commons.DatePicker(this.createId('date1'), { 
  28:               yyyymmdd: '20130101' ,
  29:               change: [ oController.date_change, oController ]
  30:           });
  31:           var date2 = new sap.ui.commons.DatePicker(this.createId('date2'), { 
  32:               yyyymmdd: '20130105',
  33:               change: [ oController.date_change, oController ]
  34:           });
  35:  
  36:           var dataset2 = new sap.viz.ui5.data.FlattenedDataset({
  37:              dimensions : [
  38:                { axis : 1, name : 'Task', value : '{task}' },
  39:              ],
  40:              measures : [
  41:                { name : 'Average', value : '{day_count_2}' }
  42:              ],
  43:              data : { path : '/et_actions'}
  44:           });
  45:           
  46:           var pie1 = new sap.viz.ui5.Pie(this.createId('pie1'), {
  47:               dataset: dataset2
  48:           });
  49:  
  50:           controls.push(
  51:                   lblTemp, cbTemp,
  52:                   lblDate, date1, date2,
  53:                   btn1,
  54:                   pie1
  55:                   );
  56:  
  57:  
  58:           return controls;
  59:       }
  60:  
  61: });

In the controller, we create a ‘refresh’ routine to call the web service with selected input parameters.



   1: sap.ui.controller("avg_turn_time.main", {
   2:  
   3:  
   4: /**
   5: * Called when a controller is instantiated and its View controls (if available) are already created.
   6: * Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
   7: */
   8:    onInit: function() {
   9:    },
  10:  
  11: /**
  12: * Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
  13: * (NOT before the first rendering! onInit() is used for that one!).
  14: */
  15:    onBeforeRendering: function() {
  16:    },
  17:  
  18: /**
  19: * Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
  20: * This hook is the same one that SAPUI5 controls get after being rendered.
  21: */
  22:    onAfterRendering: function() {
  23:        //var txt1 = this.byId('txt1');
  24:        //txt1.focus();
  25:        this.refresh(undefined);
  26:    },
  27:  
  28: /**
  29: * Called when the Controller is destroyed. Use this one to free resources and finalize activities.
  30: */
  31: //   onExit: function() {
  32: //
  33: //   }
  34:  
  35:     button_press: function(event) {
  36:         this.refresh(event);
  37:     },
  38:    
  39:     date_change: function(event) {
  40:         this.refresh(event);
  41:     },
  42:     
  43:     checkbox_change: function(event) {
  44:         this.refresh(event);
  45:     },
  46:     
  47:     refresh: function(event) {
  48:         sap.ui.getCore().setModel(undefined);
  49:         
  50:             var url = 'http://<sap-host>:<sap-port>/keg/rfc/Z_E_RFC_GCS_AVG_TURN_TIME/lc?I_TEMPLATE=00001';
  51:           
  52:             var cbTemp = this.byId('cbTemp');
  53:             if (cbTemp.getChecked() == true) {
  54:                 url += '&I_TEMPORARY_FLAG=X';
  55:             } 
  56:             
  57:             var date1 = this.byId('date1');
  58:             var date2 = this.byId('date2');
  59:             url += '&I_SEARCH_DATE=IBT' + date1.getYyyymmdd() + date2.getYyyymmdd();
  60:           
  61:           var oModel4 = new sap.ui.model.json.JSONModel(url);
  62:           sap.ui.getCore().setModel(oModel4);
  63:     }
  64: });

Create the SAP BSP Application


I understand that Eclipse has the ability to connect to the SAP host and generate the BSP application automatically.  Unfortunately, due to our system release, that feature is not available to my team. 


However, SAP has provided a very nice compromise.  Report /UI5/UI5_REPOSITORY_LOAD can be used to upload the Eclipse JavaScript application to SAP as a new BSP application.  Some items of note:



  • Our system has no default code page.  Use Cp1252.
  • Activate node /default_host/sap/bc/ui5_ui5 (and subnodes).
  • During the upload, nodes are generated in two places.  Make sure they are all activated prior to execution.


/default_host/sap/bc/ui5_ui5/sap/[z_bsp_name]

/default_host/sap/bc/bsp/sap/[z_bsp_name]

Now, you’ll have access to an application at the following location.



http://[sap-host]:[sap-port]/sap/bc/ui5_ui5/sap/zem_ui5_gcs01/index.html

The result is what you see above – a cool pie chart of average time through each stage of a process.  With some more coding in events of the pie chart control, I’m pretty sure we could build some fancy drill-down capabilities… haven’t explored that yet.


Enjoy!

2012-04-26

Visual Studio OneClick Publish - Target machine actively refused it 128.30.52.37:80

The project is a VSTO Excel Add-In that retrieves additional information from web services hosted on our ERP system.  The end of this is an unresolved issue that we were able to work around by taking SharePoint out of the scenario.

One requirement: provide a deployment package that automatically installs new versions without requiring the user to manually uninstall previous versions.  Simple enough.  In the past I had always included a Visual Studio “Setup” project with my solutions.  There’s lots of flexibility there, but this was just an Excel Add-In.  I considered trying (again) Microsoft’s OneClick publishing mechanism.

I’m relatively new to OneClick.  I scanned through the documentation and configured the Publish pane to store the data onto my local hard drive with a URL off of our local SharePoint server.  Something like the following.  Side note: I tried using a WebDav UNC path to the SharePoint site, but VS2010 choked so I gave it up. Okay – I can copy the files there manually.

SNAGHTML296d7680

My project is designed to generate a prerequisite setup program for .NET Framework 4.0 Client Profile and VSTO 4 Runtime. Our SharePoint site is configured to disallow .exe files, so I zipped up my setup.exe before placing the entire installation at the SharePoint location defined above.

Without a setup.exe, this process works great.  The .vsto file is fully accessible; clicking only the .vsto file functions fine to install the Excel Add-In.

image

There’s a problem, however, when trying to use the setup.exe.  When using the setup.exe to initiate the installation, the following exception occurs.

************** Exception Text **************
System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException:
No connection could be made because the target machine actively refused it 128.30.52.37:80
   at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
   at System.Net.Sockets.Socket.InternalConnect(EndPoint remoteEP)
   at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Int32 timeout, Exception& exception)
   --- End of inner exception stack trace ---
   at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.GetManifests(TimeSpan timeout)
   at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.InstallAddIn()

Researching online, I was able to find one reference to a deployment architecture similar to ours.  Here, someone on the Nintex forum discusses the same error within SharePoint.  Unfortunately no solution is available there.

Conjecture: It appears that while the .vsto file is being processed on the server an XML validation is failing.  Could the failure be due to security implemented on the server to disallow connections outside the firewall – in this case to hans-moleman.w3.org?  If that error happens under the watchful eye of the setup.exe, maybe the installation fails.  Working with our LAN team, we removed the “no external HTTP requests” restriction on the SharePoint server, but the problem was not resolved.

Supposing that SharePoint may be the culprit, I reconfigured the Publish pane to use a URL straight from an IIS server – no SharePoint involved.

SNAGHTML29947568

From this, I received two benefits.  First, I was able to move the entire installation to the web server share without having to first zip the setup.exe.  Second, and most important, the setup program functioned without error.  When SharePoint is not included and setup.exe accesses the .vsto file from IIS, then whatever caused the request to 128.30.52.37:80 either

  • does not occur or
  • does not fail or
  • the exception is not passed back to the setup program.

For us here, using IIS instead of SharePoint (essentially avoiding the problem) is a fine solution and we intend to make this the direction going forward for OneClick-published installation packages.  However – if anyone has additional information about the error or can propose a different solution, I would be interested in hearing it.

Jeff