You are on page 1of 6

Javascript Extensibility example: zip / unzip library integration

This documentation shows how to use extensibility scenarios that are experimentally deployed
in update 9. The APIs described here are minimal and will evolve in the future. They are
manually deployed, which currently prevents installation in the Cloud. They are usable only in
an Early adopter program context.

This article explains how to use asynchronous 'node.js' API in JavaScript bundles.

This article shows how to implement a bundle that allows 4GL code to call JavaScript zip and
unzip functions.

Creating our zlib bundle

As explained in the previous article the first step is to create a bundle for the extension. This
bundle has the following structure:

xa1-zlib/

package.json

lib/

zlib-helper._js

The package.json file contains:

CODECODE CODE json

"name": "xa1-zlib",

"description": "Crypto helper for Sage X3 4GL",

"version": "1.0.0",

"author": "ACME Corp.",

"private": true,

"sage": {

"x3": {

"extensions": {

"4gl-apis": [{
"module": "./lib/zlib-helper"

}]

The 4gl-apis extension key indicates that the package contains a 4GL API extension. The
module gives the relative path to the JavaScript modules that implements the extension.

Asynchronous JavaScript

Notice that our zlib-helper._js JavaScript file has a ._js extension

instead of the usual .js extension.

This is because 'node.js' makes heavy use of asynchronous APIs and SAFE X3 uses a special tool
called streamline.js for asynchronous programming. This tool uses the ._js extension in place of
the usual .js extension.

In 'node.js', any function that performs I/O (reads or write files, communicates over a network)
must be asynchronous. Asynchronous functions do not return their result directly, as a normal
synchronous function; instead, they return it through a callback. Here is a typical example:

CODECODE CODE javascript

var fs = require('fs');

console.log("before read");

fs.readFile("spleen.txt", "utf8", function(err, text) {

console.log("read completed");

if (err) throw new Error(err); // readFile failed

console.log("file contents = " + text)

// do something with text

});

console.log("read in progress ...");

This program will print:


before read

read in progress

read completed

file contents = Quand le ciel bas et lourd pèse comme un couvercle ...

As this little example demonstrates, callbacks impose a special structure to the code. It often
forces the developer to decompose its processes into very small functions and the order in
which these functions are called can be surprising at first (the fact that "read in progress ..." is
printed before "read completed" in the example above).

Streamline.js was designed to ease asynchronous programming by making it look like


synchronous code. The example above becomes:

CODECODE CODE javascript

var fs = require('streamline-fs');

console.log("before read");

var text = fs.readFile("spleen.txt", "utf8", _);

console.log("read completed");

console.log("file contents = " + text)

// do something with text

The callback has been replaced by _ and the result of the contents of the file is now returned
by

the fs.readFile call, as if this call were synchronous.

'Streamline.js' is a public Open Source project. You can find documentation on its GitHub
README page, as well as tutorial and a FAQ.

Writing our zlib helper

Next, you can create our zlib-helper._js JavaScript module. Here is the code:

CODECODE CODE javascript

var zlib = require('zlib');

exports.zip(text, _) {
// our input is text - convert it to a node.js Buffer

var buf = new Buffer(text, 'utf8');

// buf is a node.js Buffer, zip it (asynchronously)

var zipped = zlib.zip(buf, ~_);

// zipped is a node.js Buffer, return it

return zipped;

exports.unzip(data, _) {

// data is a node.js Buffer, unzip it (asynchronously)

var unzipped = zlib.unzip(buf, ~_);

// unzipped is a node.js Buffer, convert it to text

var text = unzipped.toString('utf8');

return text;

Note: SAFE X3 uses 'streamline.js' in fast mode. This option produces more efficient code, but
it requires a bit of extra care when calling asynchronous functions. The rule is the following:

If you call an asynchronous function that is implemented with 'streamline.js', you must pass _
as callback.

If you call an asynchronous function that belongs to the core 'node.js' API, or to a third party
package that was not implemented with 'streamline.js', then you must pass ~_ as callback.

In the example above, zlib.zip and zlib.unzip are native 'node.js' APIs. So you call them with ~_.

Deploying the extension on the 'node.js' web server

Deployment is easy: you just need to add your extension directory under the node_modules
directory of the SAFE X3 'node.js' server, and restart the 'node.js' server.

Do not forget to restart the 'node.js' server everytime you make a change to your bundle.
Otherwise it will not pick up the latest code.

Calling asynchronous JavaScript functions from 4GL

Now you can write a small 4GL API that delegates its work to these asynchronous functions:
Funprog ZIP(TEXT)

Clbfile TEXT

Local Integer STATUSCODE

Local Clbfile RESHEAD

Local Clbfile RESBODY

STATUSCODE = func ASYRWEBSER.EXEC_JS(

"xa1-zlib/lib/zlib-helper", # MODULE

"zip", # FUNCTION

"wait", # MODE: asynchronous, wait for result

'"' + escjson(TEXT) + '"', # ARGUMENTS

"0", # ENCODINGS: input is not encoded

-1, # CALLBACK: at the end of argument list

"", # RETURNS: empty

"1", # RETURNS_ENC: result is encoded in base 64

RESHEAD, # RESHEAD: unused

RESBODY) # RESBODY: the result, as a base 64 string

If STATUSCODE<>200 : End "" : Endif

# RESBODY is a base 64 string, return it as a Blbfile

End fromBase64(RESBODY)

Funprog UNZIP(DATA)

Blbfile DATA

Local Integer STATUSCODE

Local Clbfile RESHEAD

Local Clbfile RESBODY

STATUSCODE = func ASYRWEBSER.EXEC_JS(

"xa1-zlib/lib/zlib-helper", # MODULE
"unzip", # FUNCTION

"wait", # MODE: asynchronous, wait for result

'"' + toBase64(DATA) + '"', # ARGUMENTS: DATA as a base 64 string

"1", # ENCODINGS: input is encoded

-1, # CALLBACK: at the end of argument list

"", # RETURNS: empty

"0", # RETURNS_ENC: result is not encoded

RESHEAD, # RESHEAD: unused

RESBODY) # RESBODY: the result, as a string

If STATUSCODE<>200 : End "" : Endif

End RESBODY

Now you can use this 4GL API to zip and unzip texts:

Subprog TESTZIP()

Local Blbfile ZIPPED

Local Clbfile UNZIPPED

ZIPPED = func ZIP("Hello world!")

UNZIPPED = func UNZIP(ZIPPED);

Infbox UNZIPPED : # Should be "Hello world!"

End

Links

ASYRWEBSER API Guide

node.js API

streamline.js

You might also like