Index of contents
problem faced
It is requested that from sage x3, there is the possibility of generating labels containing QR codes to identify items through a scanner.
Proposed solution
The proposed solution is the following: outsource the functionality so that it is done in a package or bundle programmed in Node JS, a technology that optimizes development times and facilitates work thanks to its wide community and versatility.
Bundle file structure
The files are included in a directory whose name will be the same as our module, since only one is included. In this example it is “xeax-qr-generator”.
Inside is the “lib” directory, where all the code that is going to be executed on the server side will go.
- xeax-qr-generator
- lib
- services
- eax-qr-generator-service._js
- index._js
- services
- package.json
- lib
How 4GL and NodeJS communicate
The way in which 4GL and Node JS communicate is through the "ASYRWEBSER" API, which allows executing the Javascript functions that are published in the modules belonging to the bundles, by configuring the file package.json.
How to configure the “package.json” file
To configure the “package.json” file, a series of mandatory properties must be taken into account, which are:
- "yam": It is important to specify the name of the module, since it will be the way to identify it from 4GL.
- “module”: It is also necessary to specify the file where the functions to expose are located. In this case it is the “index._js” file.
Note that the sage > x3 > extensions > 4gl-apis >module tree structure must be maintained.
- “dependencies”: This property holds the dependencies used by the bundle. In this case, the qrcode library is used with the version from 1.4.4 to 2.0.0.
{ "name": "xeax-qr-generator", "version": "1.0.0", "description": "Emiral's Sage QR generator", "author": "Emiral Freelance Group", "sage": { " x3": { "extensions": { "4gl-apis": [ { "module": "./lib/index" } ] } } }, "dependencies": { "qrcode": "^1.4.4" } }
xeax-qr-generator/package.json
exposing the function
The file where the functions to be executed from 4GL are published is “index._js”, so they must be exported:
"use strict"; /** * Endpoint called by 4GL to generate QR codes. * @param _ - This param is automatically filled and it's a callback reserved for async functionality * @param strData - Parameters to send */ exports.generateQR = function (_, qrParams) { let res = ""; try { const EAXQRGeneratorService = requireUncached("./services/eax-qr-generator-service"); const qrGeneratorSvc = new EAXQRGeneratorService(); // Generating QR code res = qrGeneratorSvc.generateQR(_, qrParams); } catch (error) { console.log(error); res = JSON.parse(error); } return res; }; /** * Does the same as require function forcing to read the file every time it's accessed. * @param module * @returns */ function requireUncached(module) { delete require.cache[require.resolve(module)] return require(module) }
index._js
Files ending with the _js extension are intended to be processed by the streamline.js library, which is used by Syracuse because of how simple it makes handling asynchronous code. Like HTTP requests or file reading, the generation of QR is not immediate; that's why it works asynchronously.
function is exported generateQR, which receives two parameters:
- The first is the callback function used by the streamline.js library, which must be passed as a parameter to the functions that support it (such as the then of promises). This way, when the result is obtained, 4GL will be notified.
- “qrParams”: The JSON that contains the desired configuration for the generation of the QR code.
What the function does is delegate the QR generation functionality to the service called "".
Speeding up development
The functionality of the “index” input file has been separated for reasons of optimization of times and comfort at the time of development. By isolating itself, the programmer can read the file from its location and not from the cache.
All Node JS packages are read and stored in memory when the Syracuse server starts up. To perform readings from the file system, it can be forced by deleting the data stored in memory, as can be seen in the "requireUncached" function, which once removed from the cache, and calling the "require" function is forcing the server to to go look for it on the physical route.
This type of reading should be removed from deployments in production environments for performance reasons.
QR code generation
As mentioned before, we are making use of the library of qrcode. This library allows you to configure the way it is generated in a simple way.
In this example, a specific format of the parameters has been created to cover the needs of the development:
- "strData": Content to encode.
- "quality": Quality of the image to generate (defined in the library page). By default it is high (“H” for “high”).
- width: Side size (height/width) of the image to generate.
- scale: Pixel density per module (the atomic unit that makes up the QR code).
The library in question is imported and thanks to the "toBuffer" function we can perform the asynchronous generation of the QR code. To obtain the result, it is important to collect it in a variable and place the function that generates the promise to the right of the equal, followed by “.then(_,_)”, which makes programming in a similar way to synchronous (similar to not so modern async/await). As mentioned before, this is thanks to the streamline.js library.
Once the buffer has been obtained, it is encoded in base64 to be processed by 4GL.
module.exports = class EAXQRGeneratorService { constructor() { } generateQR(_, qrParams) { const strData = qrParams.strData; const quality = qrParams.quality || 'H'; const width = qrParams.width; const scale = qrParams.scale; var QRCode = require('qrcode'); let qrBuffer = QRCode.toBuffer(strData, { errorCorrectionLevel: quality, width: width, scale: scale }).then(_, _); const result = qrBuffer.toString('base64'); return result; } }
JavaScript function execution from 4GL
For this example, a script programmed in 4GL has been developed.
First, the parameters to be passed are stored in a variable of type clob in JSON format.
The function is executed EXEC_JS from the api ASYRWEBSER passing the following parameters:
- Module (MODULE): Integer path from the package name to the input file.
- Function (FUNCTION): Desired function, published in the exposed module or file (index).
- Mode (MODE): To execute asynchronously and have X3 embed the callback in the parameters of the published function.
- Parameters (DATA): Parameters to pass to the published function.
- Base 64 input (B64_IN): Not applicable, pass “0”
- Callback position (CALLB): Indicates the position in the parameters where to inject the callback function. In the example it is 0 so that it is passed as the first parameter.
- Return (RETURNS): Used to specify the property of the object to extract. In this case, since it is not a JS object, but a string, it is not necessary to pass "".
- Base 64 output (B64_OUT): Not applicable, pass “0”.
- Response headers (RESHEAD): Not applicable, leave blank.
- Response Body (RESBODY): Variable that is filled with the result emitted by the bundle function. In this example it will contain the base64 code of the image associated with the text that was passed to it in the “Test Text” parameters.
Local Clbfile DATA DATA = '{' Append DATA, '"strData": "Test Text"' # QR Text Append DATA, ',"quality": "H"' # L, M, Q, H - (L = Low, H = High [default]) Append DATA, ',"width": null' # Width of image Append DATA, ',"scale": null' # Number of pixels per module (4px by default) Append DATA,' }' Local Clbfile RESHEAD Local Clbfile RESBODY Local Integer STATUSCODE ### Module, function and element to be returned Local Char MODULE(100) : MODULE = 'xeax-qr-generator/lib/index' Local Char FONCTION(30) : FONCTION = ' generateQR' #'getUserProfile' Local Char RETURNS(50) : RETURNS = ""#default" ### The function is asynchronous Local Char MODE(4) : MODE = 'wait' ### The callback argument is located in the first place for this function Local Integer CALLB : CALLB = 0 ### No Base 64 in or out coding, the arguments can remain empty Local Char B64_IN(20): B64_IN = "0" Local Char B64_OUT(20): B64_OUT = "0" ### No a dditional arguments Local Clbfile ARGUMENTS : ARGUMENTS = DATA # Let's call the function STATUSCODE = func ASYRWEBSER.EXEC_JS(MODULE, FONCTION, MODE, DATA, B64_IN, CALLB, RETURNS, B64_OUT, RESHEAD, RESBODY) Infbox(RESBODY) End