Flexible Handlebars Templates

6 4 2,244

Summary

When you first read this article, you may see a useful way to create templated SOAP requests, mashups, or other types of content that lend themselves to macro substitution.

However, I hope you will also recognize a reusable design pattern that you can employ with other JavaScript libraries, whether they are something your find on the web or some of your own code.

By making use of JavaScript policy Properties, you can essentially turn custom code into easily reused, lightweight configurations.

==================

Edge does a great job of providing configuration where possible while still allowing you to write code when necessary. In this article, we introduce a design pattern that blurs the line between the two and effectively broadens the set of problems that can be solved through simple configuration.

handlebars.js

Handlebars is a simple, yet powerful JavaScript library that makes macro substitution easy. The first step is to add handlebars.js to your proxy. If you anticipate using Handlebars in multiple proxies, it makes sense to add it to your org, rather than to your proxy. This will make reusing this pattern very easy.

To add Handlebars to your org, download the Handlebars JavaScript file and use the Edge management APIs to add a resource file to an org. You can upload the file or paste the source. Use the filename handlebars.js and the resource type jsc. (You can use any filename you like -- you might include the version number, for example -- just make sure you remember it later when you need to reference the file.)

populateTemplate.js

Similarly, you will need to add the attached file, populateTemplate.js, to the proxy or org in exactly the same way that you did with handlebars.js. This file contains the function that makes use of Handlebars and returns the final result. (NOTE: The attached file has a .txt extension due to requirements of the Apigee Community software. It is a JavaScript source file and must have a .js extension when uploaded to Edge.)

Use case 1: SOAP Template

Now that your org has these two resource files available, you are ready to build a flow that makes use of them. Let's suppose you wanted to call a SOAP service, but you did not have a WSDL available for use by the API Proxy wizard. You would have no choice but to build the SOAP envelope yourself. Now, we can use a Handlebars template to populate the SOAP body in one simple step.

The first step is to add a JavaScript callout policy to the target endpoint preflow. [NOTE: The only reason for putting this step into the Target flow is because there is a later reference to a target variable which is only accessible from within the Target flow. Use of a Handlebars template will work in any Proxy or Target flow.] In the configuration of the JavaScript policy, you will add a Property named hbr, so named because it is the standard extension for a Handlebars template. Because a SOAP Envelope is XML, it is necessary to wrap the entire hbr property in a CDATA block. Next, you templatize the SOAP Envelope by adding in any Handlebars expressions you choose wrapped in double-braces like {{expression}}.

Here is the final JavaScript policy configuration to retrieve the weather. Note that the variable zip is wrapped in double-braces.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="Weather-SOAP-Request">
	<DisplayName>Weather SOAP Request</DisplayName>
	<Properties>
		<Property name="hbr">
<![CDATA[    
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetCityWeatherByZIP xmlns="http://ws.cdyne.com/WeatherWS/">
      <ZIP>{{zip}}</ZIP>
    </GetCityWeatherByZIP>
  </soap:Body>
</soap:Envelope>      
]]>
		</Property>
	</Properties>
	<ResourceURL>jsc://weatherSOAP.js</ResourceURL>
	<IncludeURL>jsc://handlebars.js</IncludeURL>
	<IncludeURL>jsc://populateTemplate.js</IncludeURL>
</Javascript>

Note that you must also use IncludeURL elements to include the handlebars.js and populateTemplate.js resource files.

The last step is to write the code to be executed in this JavaScript callout policy step. At a minimum, you must call the function populateTemplate() to run Handlebars against the hbr template. The following four code blocks show possible scenarios for weatherSOAP.js:

var result = populateTemplate(properties.hbr)
context.setVariable('request.content', result)

You can also provide an optional data object to populateTemplate() from which Handlebars can extract values:

var result = populateTemplate(properties.hbr, {'zip': 56789})
context.setVariable('request.content', result)

If you are processing a POST whose contents are a JSON payload, you can pass that as the data object for populating the template:

var result = populateTemplate(properties.hbr, context.getVariable('request.content'))
context.setVariable('request.content', result)

Finally, if you want to make more sophisticated use of Handlebars, you can register helpers that will be called when the name of a helper appears in your hbr template:

Handlebars.registerHelper("zip", function() {
  context.setVariable('target.copy.queryparams', false)
  context.setVariable('request.verb', 'POST')
  context.setVariable('request.header.Content-Type', 'text/xml; charset=utf-8')
  context.setVariable('request.header.SOAPAction', 'http://ws.cdyne.com/WeatherWS/GetCityWeatherByZIP')
  return context.getVariable('request.queryparam.zipcode')
})
////////////////////////////////////////////////////////////////////////////////
var result = populateTemplate(properties.hbr)
context.setVariable('request.content', result)

In this case, when {{zip}} is processed in the hbr template, the entire SOAP request is prepared by the Handlebars helper in a few lines of JavaScript, including changing the HTTP verb to POST and setting the SOAPAction header.

Use case 2: Mashup

Another way to make use of Handlebars is when formatting a response that takes data from multiple sources. In this example, we want to return a response that includes the incoming request, as well as the response from the target. In addition, we will take this opportunity to show off some other Handlebars capabilities.

Again, we need to add a JavaScript callout policy, this time on the response flow. Again, we will provide a Handlebars template inside of a Property named hbr. And, again, we will include the two org resource files, handlebars.js and populateTemplate.js.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="Mashup-Response">
    <DisplayName>Mashup Response</DisplayName>
    <Properties>
      <Property name="hbr">
{
	"request": {{{request}}},
   	"response": {{{response}}},
	{{#each myArray}}
		"arrayElem{{this}}": {{this}},
	{{/each}}
	"array": [{{myArray}}],
    	"uuid": '{{uuid}}'
}
      </Property>
  </Properties>
  <ResourceURL>jsc://mashupResponse.js</ResourceURL>
  <IncludeURL>jsc://handlebars.js</IncludeURL>
  <IncludeURL>jsc://populateTemplate.js</IncludeURL>
</Javascript>

There are several things to note in this template. First of all, the template is not XML and does not contain characters that could be confused with XML, so the CDATA block is not necessary.

Secondly, the first two Handlebars expressions are wrapped in {{{ triple-braces}}} rather than {{double-braces}}. This so-called triple-stash tells Handlebars not to escape the contents of the string. Since the request and response variables contain JSON, the triple-stash prevents unwanted escaping of the JSON syntax.

Thirdly, we are showing off Handlebars' ability to pull off tricks like looping with the {{#each}}..{{/each}} syntax. Refer to the Handlebars documentation to learn all the cool things you can do with this templating system. You have access to the entire feature set with this approach.

Finally, we are demonstrating a more complex helper function with {{uuid}}.

As with the SOAP sample above, we need to now provide the code for the JavaScript policy that will call populateTemplate(), as well as provide any helper functions referenced within the template.

// called when the template contains {{uuid}}
Handlebars.registerHelper("uuid", function() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
    s4() + '-' + s4() + s4() + s4();
});
// called when the template contains {{request}}
Handlebars.registerHelper("request", function() {
  return context.getVariable('request.content')
})
// called when the template contains {{response}}
Handlebars.registerHelper("response", function() {
  return context.getVariable('response.content')
})
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// populate the template, passing in an object that includes myArray
var result = populateTemplate(properties.hbr, {'myArray': [1,2,3,4,5]})
// set the response content to be the template populated by Handlebars
context.setVariable('response.content', result)

The result of this template looks like this:

{
	"request": {},
	"response": {},
		"arrayElem1": 1,
		"arrayElem2": 2,
		"arrayElem3": 3,
		"arrayElem4": 4,
		"arrayElem5": 5,
	"array": [1,2,3,4,5],
	"uuid": 'ae0a282c-3cca-b244-824c-928415734f28'
}

Assets - Two API bundles which implement the above two use cases

https://github.com/leeatapigee/handlebars-templates-in-Edge

Comments
Not applicable

Thanks @Lee Grey. Smart way to use JavaScript properties within scripts. Also, handlebars seem a nice substitute of XSLT to generate JSON or XML. Have you seen customers leveraging Handlebars for this purpose?

DChiesa
Staff

Cool!

Hey @Lee Grey - what's in weatherSOAP.js ? What does that file do?

i think i understand the purpose of the other files in your first example.

Not applicable

Thanks for bringing this point of confusion to my attention, @Dino. Code blocks 2, 3, 4, and 5 above contain possible implementations of weatherSOAP.js. It contains the JavaScript code that calls populateTemplate(), as well as any associated helper functions. I added a clarification in the text.

Not applicable

Hi, @Diego Zuluaga. Since I'm pre-sales, I rarely get to see what customers implement in Edge. I would imagine you have much better insight into what customers do in production scenarios. What approaches have you seen or implemented?

Version history
Last update:
‎12-09-2015 03:19 PM
Updated by: