Advanced Widget Techniques with REST API and Angular Providers

featured

Perhaps the most important skill to have as a service portal widget developer is the ability to create reusable script components. Some call it cleanliness, some call it poetry… but whatever you call it, developing easily reusable scripts results in more fun coding. Furthermore, it leads to scalability and stability in your work.

In this post, you will learn how to:

  • Use Scripted REST API in an Angular Provider service script
  • Use deferred promises with the $http and $q services for asynchronous REST API calls
  • Modularize a reusable message retriever for dynamic message translations
  • Pull it all together in a Service Portal widget

Perhaps you joined us for Cerna’s first live podcast on July 20. In it, Will Smith, Tanner Kibler, and I demonstrated a Service Portal method for retrieving dynamic message translations. It builds on a concept outlined by Chuck Tomasi in a community post, where he showed us how to use the second parameter of gs.getMessage().

See Chuck’s post here.

As you may know, the second parameter method only works in a server side script. That in itself isn’t a terrible limitation. Especially considering it is easy to define the message server side and pass it to the client controller script. Ie:

Server Script:

var values =[‘Some Value’, ‘Another Value’];
data.message = gs.getMessage(‘someMessageKey’, values);

Client Script:

c.message = data.message;

However, this could involve a bit of complexity if you need to start the message definition client side. You’ll need to request an update from the server, then wait for the response to reach the client script. While that’s not awful, it’s not ideal if you have lots of custom widgets to translate. Consequently, you will end up repeating this code over and over.

For example:

Client Script:

var values =[‘Some Value’, ‘Another Value’];
c.server.get({action: ‘getMessage’, key: c.someMessageKey, values: values})
   .then(function(response){
      c.message = response.data.message;
   });


Server Script:

if (input && input.action == ‘getMessage’) {
   data.message = gs.getMessage(input.key, input.values);
}


I strongly dislike repetitive code. Hence, I set my mind to developing a more modular solution that didn’t require requesting a server script update. As a result, my team collaborated on a reusable pattern. Here it is for you to learn from.

Note: Maybe message translation isn’t the use case you are trying to solve for. Regardless, the pattern demonstrated here can be used in any scenario that requires teamwork between client side and server side. All scripts are repeated at the end of the post for copy/paste purposes.

Step 1: The Scripted REST API

First, we create a scripted REST API. All we do at the header level is give the API its name.

Next, create a REST resource that will accept parameters from whatever script is calling it. This does the actual message translation, and returns it to the requesting script via the response body.

Step 2: Angular Provider Service

You’ll want a reusable function that will communicate with the REST API. This angular provider can be injected into any widget controller. With this, we can pass to the REST API all the required parameters for message translation. The REST API takes a small moment to process, therefore we use the AngularJS $q service to handle the function promise. Otherwise, our client controller won’t wait for us, resulting in an undefined value.

Step 3: Widget Client Controller

The widget’s client controller invokes the provider service’s getDynamicMessage function. Remember, you have to attach this to the widget via related list, in addition to injecting it by name. The important part of this script is the getDynamicMessage call. The other stuff is simply to support the demo.

Step 4: HTML Template

We need a way to collect message parameters. I’ve created a simple form with several inputs to demonstrate this.

Step 5: UI Message

Finally, we create the message itself. The first parameter sent to the REST API is our message key. The second is an array of values that can be injected by index number. As a result, we can construct translations that support the grammar rules of any language.

The Result

Let’s see it in action, starting with a blank form.

The user tells us their name and favorite topping. Then they choose their dish.

The final result is the dynamically built confirmation message.

In Summary

The reusable parts of this whole thing are the scripted REST API and the angular provider service. The usage is quite easy. From any widget controller (that injects the portalMessages service), simply call the ‘getDynamicMessage’ function provided by the ‘portalMessages’ service.

portalMessages.getDynamicMessage('messageKey', ['some value', 'some other value'])
   .then(function(message){                                         
      //Do something with your translated message
   });

Seems like a lot going on here. So I’ll summarize the whole process again in a nutshell:

  • The HTML template takes inputs from the user.
  • The client controller passes these inputs to the angular provider service.
  • The angular provider passes the inputs to the scripted REST API.
  • The REST API translates and returns the message.
  • The angular provider service returns message to the client controller.
  • The client controller patiently waits for the message.
  • The HTML template is updated.

As you can see, it’s not always necessary to use the server script of a widget. This is particularly true when you have a common evaluation that might get used in any given widget.

Hopefully I’ve helped nudge you a step closer to service portal widget mastery, now that you understand how to use REST API in tandem with angular providers.

Aloha!
Jeff
Connect with me on LinkedIn

 

Text versions of the scripts for your copy/paste pleasure:

Scripted REST API Resource

(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {

   //Data is received from the Angular Provider service that makes the REST call
   var data = request.body.data;

   //Get the message translation
   var message = gs.getMessage(data.key, data.values);

   //Prepare the response body
   var body = {data: message};

   //Set the response body
   response.setBody(body);

})(request, response);

Angular Provider Service

function($http, $q) {
   //$http service to enable the REST API call
   //$q service to handle deferred promises to aid with asynchronous message retrieval

   return {

      getDynamicMessage: function(key, values) {
         //key and values are received from the client controoler

         //Enable a deferred promise
         var def = $q.defer();

         //This object gets sent to the scripted REST API
         var data = {
            key: key,
            values: values
         };

         //Set up the REST API call
         var messageGetter = '/api/116638/myportalmessages/getdynamicmessage';
         $http.post(messageGetter, data)
            .success(function(response){
               def.resolve(response.result.data);
            })
            .error(function() {
              def.reject("Failed to get message");
            });

         return def.promise;
      }
   };
}

Widget Client Controller

function(portalMessages, spModal) {
   var c = this;

   c.orderFood = function(food) {
      portalMessages.getDynamicMessage('confirmationMessage', [c.name, c.topping, food])
         .then(function(message){
            c.confirm(message); 
         });
   };

   c.confirm = function(message) {
      spModal.open({
         size: 'lg',
         title: '${confirmationModalTitle}',
         message: message,
         buttons: [
            {label:'${Ok}', primary: true}
         ]
      });
   };

}

Widget HTML Template

<form>

   <div class="form-group">
      <label for="nameInput">${Your name}</label>
      <input ng-model="c.name" type="text" class="form-control" id="nameInput" aria-describedby="nameHelp" placeholder="${ie, Fred}">
   </div>
   <div class="form-group">
      <label for="toppingInput">${Your favorite topping}</label>
      <input ng-model="c.topping" type="text" class="form-control" id="toppingInput" aria-describedby="toppingHelp" placeholder="${ie, cheese}">
   </div>

   <div class="btn-group" role="group">
      <button type="button" class="btn btn-primary" ng-click="c.orderFood('pizza')">${Order Pizza}</button>
      <button type="button" class="btn btn-primary" ng-click="c.orderFood('taco')">${Order Taco}</button>
      <button type="button" class="btn btn-primary" ng-click="c.orderFood('curry')">${Order Curry}</button>
   </div>

</form>