Tutorial of Using Promise Object in SOY Portlet to Access Liferay's web service.

In this tutorial, I am going to talk about how to utilize promise object to access web service in Liferay DXP.
 
Before we get into more detail, please take a note that the version Liferay I am using is Liferay DXP DE-15, since there's big soy development experience improvement in this patch.
 
 
In this tutorial, we are going to put all these knowledge together to make a portlet that can visualize data from a web service. We have talked about making a Service Builder remote services. Liferay DXP serves these services as a service provider. And now we are using Liferay as a service consumer to call these services. These services is not limited in Liferay platform, they can be served by any system. This is a way of integrating with 3rd party system. The 3rd party system is good at their own job in their business domain. Liferay is good at providing a connected and consistent user experience regardless where the data comes from and how users choose to engage with the business.
 
Matching to real world requirement, the approach in this tutorial can satisfy requirement like:
  • User Dashboard that the data comes from 3rd part systems.
  • Responsive(Liferay's bootstrap)
  • Plug and play(Microservice)
  • Abundant technologies option(NPM)
 
Let's Go!
 
 

Step 1 - User the knowledge you already have to create a chart JS portlet

 
As we have done in the previous articles, let's create a new chartjs soy portlet project called monthly-trading-web. But this time, we can modify package.json a little bit.
Previously the metal-cli npm plugin has some CRLF compile issue. This issue has been resolved in the latest release of metal-cli. So the good news is you don't need to replace the CRLF with LF anymore. You just need to make sure your metal-cli's version is 4.0.1.
{
 "dependencies": {
  "metal-component": "^2.10.0",
  "metal-soy": "^2.10.0",
  "chart.js": "^2.4.0"
 },
 "devDependencies": {
  "liferay-module-config-generator": "^1.2.1",
  "metal-cli": "^4.0.1"
 },
 "name": "monthly-trading",
 "version": "1.0.0"
}
Thanks to my friend Chema Balsas and Liferay UI team for the effort again!
 

Step 2 - Find your service and mockup data

As we have done in the previous tutorial, we can add some mockup data through /api/jsonws.
The service context is Banking. The method name is add-monthly-trading.
 
After adding mockup data. You can click on the get-monthly-trading-by-year and then input the year. Then click Invoke button, and then click on the URL Example tab to get the service URL.
 
 
Take a note of the p_auth. This is an auth token for authentication.
 

Step 3 - Pass the Service URL to JS

In the render method of portlet class, we can put the url into the template so that the soy and es.js can receive the variable.
 
public void render(
   RenderRequest renderRequest, RenderResponse renderResponse)
  throws IOException, PortletException {

   String tradingYear = "2017";
   String pauth = AuthTokenUtil.getToken(PortalUtil.getHttpServletRequest(renderRequest));

   String portletNamespace = renderResponse.getNamespace();

   template.put("remoteURL", "/api/jsonws/banking.monthlytrading/get-monthly-trading-by-year/year/" + tradingYear + "?p_auth=" + pauth);
   template.put("tradingYear", tradingYear);
   template.put("portletNamespace", portletNamespace);
   super.render(renderRequest, renderResponse);
  }
 
In the es.js, we can receive the variable in constructor method.
 
constructor(opt_config) {

  super(opt_config);

  let remoteURL = opt_config.remoteURL;
  let tradingYear = opt_config.tradingYear;
  this.portletNamespace = opt_config.portletNamespace;
  this.createRemoteChart_(remoteURL, tradingYear); // Hasn't been defined yet.
 }
 

Step 4 - Make a promise

What is a Promise?
" A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future. " --MDN
 
In SOY portlet we can utilize ES6 to use Promise project.
In our es.js file this is how we define and return a promise object.
First you need to import the Promise object:
 
import { CancellablePromise } from 'metal-promise/src/promise/Promise';
This is using metaljs' cancellable promise.
 
And then you can define a method to define and return the promise object:
 
/**
  * Get remote trading data
  * @protected
  * @param {String} remoetURL
  * @return {CancellablePromise} A promise that will resolve save permise
  */
 getChartData_(remoteURL) {
  let promise = new CancellablePromise((resolve, reject) => {
   let requestConfig = {
    contentType: false,
    dataType: "json",
    processData: false,
    type: "GET",
    url: remoteURL
   };

   AUI.$.ajax(requestConfig)
    .done((data) => resolve(data))
    .fail((jqXHF, status, error) => reject(error));
  });

  return promise;
 }
Take note that we are using the out of the box jQuery in Liferay. In Liferay we have sandboxed the jQuery(v2.1.4) in to AUI object. When you need to call jQuery method just use AUI.$.... And you are free to use your prefered jQuery as well without naming conflict.
 
 
 

Step 5 - Fulfill your promise

Next we will write the UI logic based on the data from the web service.
/**
  * Create Chart with data url
  *
  * @param {String} remoteURL
  * @protected
  */
 createRemoteChart_(remoteURL, tradingYear) {
  this.getChartData_(remoteURL).then(data => {

   let chartcanvas = document.getElementById(this.portletNamespace + "monthly-trading-chart");

   let labels = Array.from(data, d => d.month);
   let bgColor = this.getPreferedColors_(data.length, 0.3);
   let borderColor = this.getPreferedColors_(data.length, 0.8);
   let dataValue = Array.from(data, d => d.volume);

   let chartData = {
    labels: labels,
    datasets: [
     {
      label: "Monthly Trade of " + tradingYear,
      backgroundColor: bgColor,
      borderColor: borderColor,
      borderWidth: 1,
      data: dataValue,
     }
    ]
   };

   let options = {
    scales: {
     xAxes: [{
      stacked: true
     }],
     yAxes: [{
      stacked: true
     }]
    }
   };

   let myBarChart = new Chart(chartcanvas, {
    type: 'bar',
    data: chartData,
    options: options
   });
  });

 }
I have another method that lets the bar chart only uses my preferred color.
/**
  * Get Bar background colors from prefered colors
  * @protected
  * @param {int} length
  * @param {string} opacity
  * @return {Array} a color array
  */
 getPreferedColors_(length, opacity=1) {

  let colorsRepo = [
   "255, 99, 132",
   "54, 162, 235",
   "255, 206, 86",
   "75, 192, 192",
   "153, 102, 255",
   "255, 159, 64"
  ];

  let colors = new Array();

  for (let i = 0; i < length; i++) {
   let index = i % colorsRepo.length ;
   let color = "rgba(" + colorsRepo[index] + "," + opacity + ")";
   colors.push(color)
  }

  return colors;
 }
 
Hope you enjoy.