Writing Custom Modules and Formatters (NodeJS)

This works. Thanks. :slightly_smiling_face:

An update on the output issue.
I added a ā€œnameā€ parameter and I started seeing the output from my module.

image

image

Specifically, optionName: 'name'. With any value other than name, the output disappears again.

The UI still shows my dataScheme variable path as the output data item. But, the name img64 is what appears in the response. This is confusing.
What I was expecting is something like img64.path to have the data. Also, does this mean a property variable with optionName: 'name' is compulsory if I need to access an output from the custom module?

Yes optionName: ā€˜nameā€™ is required if you want output.

Thanks for clearing that up.

One last clarification remains for now - for the string output.
Giving this a bit of a though, I realize that if the output is a direct string, it would be available as a variable, and not via some nested param like img64.pathā€¦ similar to how set value works. If the value set is an object, we can expect {{var1.key1}}, but if its a simple text, {{var1}} is used.

I think this wraps up my custom image to base64 module for NodeJS while giving a brief understanding of how it works. Plan to make a new post in detail tomorrow. :slight_smile:

Hope to delve into more complex stuff soon. This is really really cool. :ok_hand:
Wappler: A super powerful and extensible tool, with easy custom UI components - unheard of.

3 Likes

@George - On an existing NodeJS (Docker) project, I cannot get the extensions folder to appear, and Iā€™m not sure if just manually creating it would work? Iā€™ve tried opening targets and re-saving as in another post - but no luck.

Please advise?

You can create them manually. No problem with that.

1 Like

I have a module, it is working great except that I am returning a value before a file write is finished.

The module is this:

exports.write_binary = function (options) {
    //for converting file paths provided in UI
    const { toSystemPath } = require('../../../lib/core/path');

    //uuid package
    const { v4: uuidv4 } = require('uuid');

    //required for retrieving and saving file
    const http = require('https');
    const fs = require('fs');

    //convert the user local_path provided in the API action to useable in the module
    let path = toSystemPath(this.parseRequired(this.parse(options.local_path), 'string', 'fs.exists: path is required.'));

    //evaluate the data binding for remoteURL
    let remoteURL = this.parseRequired(options.file_url, 'string', 'parameter value is required.');

    //generate uuid
    let uuid = uuidv4(); // ā‡Ø '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'

    //set the filename
    let file_name = path + '/' + uuid;

    //open up the stream for writing the file
    const file = fs.createWriteStream(file_name);



    async function fetchURL() {

        await http.get(remoteURL, async function (response) {
            response.pipe(file);
            await new Promise((r, j) => {
                file.on('close', r);
            });

        })
            .on('error', function (err) { // Handle errors
                fs.unlink(file_name); // Delete the file async. (But we don't check the result)

            });
    }

    fetchURL();

    return { "file_path": uuid };

};

I am attempting to retrieve a file from a URL and return the file name (currently a uuid) for use within standard Wappler actions.

I thought my async function fetchURL would block the return from happening until it was finished, but Iā€™m clearly doing something wrong.

@patrick does this.exec help me here?

If itā€™s not obvious, Node and async are very new to me! :laughing:

Add await before the fetchURL function call :slight_smile:

1 Like

Like this?

async function fetchURL() {

        await http.get(remoteURL, async function (response) {
            response.pipe(file);
            await new Promise((r, j) => {
                file.on('close', r);
            });

        })
            .on('error', function (err) { // Handle errors
                fs.unlink(file_name); // Delete the file async. (But we don't check the result)

            });
    }

    await fetchURL();

    return { "file_path": uuid };

await has to be within an async function, so that wouldnā€™t work???

Yes exactly so the execution is blocked till it completes otherwise it will return immediately indeed.

To use tge await indeed make also the main function async

Editā€¦okay, main function async. I forgot about that one.

Thanks!

1 Like

As the example from Patrick indeed, so should be:

exports.write_binary = async function (options) {

@patrick Based on the above, my code works well for me on a custom remote target, but Iā€™m getting invalid results when used on remote docker.

For example I get this error:

{
  "status": "500",
  "code": "ENOENT",
  "message": "ENOENT: no such file or directory, stat '/opt/node_app/public/avatars//public/avatars/94459807-a717-46d1-9407-3478871c2685'",
  "stack": "Error: ENOENT: no such file or directory, stat '/opt/node_app/public/avatars//public/avatars/94459807-a717-46d1-9407-3478871c2685'"
}

When using this code:

   let path = toSystemPath(this.parseRequired(this.parse(options.local_path), 'string', 'fs.exists: path is required.'));

Obviously things are going wrong here: /public/avatars//public/avatars/

The exact same attempt on the non-docker remote works fine.

Any ideas?

Iā€™m struggled with output options in NodeJS custom module.

custom.hjson

[
{
type: ā€˜KIOSK toolsā€™,
module: ā€˜kiosk_pingā€™,
action: ā€˜ping_kiosksā€™,
groupTitle: ā€˜File Managementā€™,
groupIcon: ā€˜fas fa-lg fa-file comp-filesā€™,
title : ā€˜Ping KIOSKS @@var(name)@@ā€™,
icon : ā€˜fad fa-lg fa-network-wired comp-filesā€™,
dataScheme: [
{name: ā€˜animalsā€™, type: ā€˜textā€™}
],
dataPickObject: true,
properties : [
{
group: ā€˜KIOSK Dataā€™,
variables: [
{ name: ā€˜ip_listā€™, optionName: ā€˜ip_listā€™, title: ā€˜IP list*ā€™, type: ā€˜textā€™, required: false, defaultValue: ā€˜ā€™, serverDataBindings: true}
]
}
]
}
]

kiosk_ping.js content:

exports.ping_kiosks = async function (options) {
var animals=[ā€˜pigsā€™, ā€˜goatsā€™, ā€˜sheepā€™];
return animals;
}

My Server action looks like this:

Iā€™m getting empty response.
My idea is just display animals array as an output just to test basic stuff.
Could you please help me?

Try this:

exports.ping_kiosks = async function (options) {
    var animals=[ā€˜pigsā€™, ā€˜goatsā€™, ā€˜sheepā€™];
    return {"animals": animals};
}

Thank for trying - but this wonā€™t helped.
Same empty output.

Iā€™ve even simplified my output to this JavaScript and still getting empty response even I set my output to be checked:

exports.ping_kiosks = async function (options) {
    var test = 'asdasdasd';
    return test;
}

If the action has no name it will not output data, in your hjson add a name property to your properties

1 Like

Variable with name Name should be set.

{ name: 'name', optionName: 'name', title: 'Name', type: 'text', required: true, defaultValue: ''},

Thank you @patrick

Hiā€¦ Did you ever manage to get a video on how to install this?