Skip to Content

Add Button To Record Using Client Script And User Event Script: SuiteScript

Add Button To Record Using Client Script And User Event Script: SuiteScript

How can you add a button to a record that can be used to trigger a process regardless of whether the record is being viewed or edited?

Creating a button on a client script does have the problem where it can only be accessed when the record is in EDIT mode which doesn’t help when you want to trigger a process when the record is in VIEW mode.

Recently I had a project which required a button to be placed on the Customer record to generate an Estimate based on other joining records and fields. A requirement of the estimate generation was to display to the user warnings and errors if certain pieces of information were missing.

Due to the requirement a client script was the best fit as it gave access to the N/ui/message and N/ui/dialog modules which allow you to check whether a user is ready to initiate a process and can provide specific issues on why the desired result was not produced.

Unfortunately, creating everything in a client script did not work as a client script has the limitation of its custom button being available only when the record is in EDIT mode. As users are creating records and inserting others at various times in the Customer creation process this functionality wouldn’t work.

Therefore, the only other option available was to create a User Event Script and to use the standard beforeLoad function which would create a button for the record and if clicked would call the client script function (all wasn’t lost on the code and time spent on the client script!).

Here’s how this all ended up coming together:

Create Client Script

First, create your client script. This doesn’t need to contain all the standard functions of a regular client script with pageInit, fieldChanged, saveRecord (etc) functions. You can just have it contain the functions you would like for the script to run when the button is clicked.

Here’s how this could look:

/**
 * @NApiVersion 2.x
 * @NScriptType ClientScript
 * @NModuleScope SameAccount
 */
define(['N/record', 'N/ui/message', 'N/url'], 
/**
 * @param {record} record
 * @param {message} message
 * @param {url} url
 */
(record, message, url) => {
    const createEstimate = (custId) => {
        const r = record.load({type: record.Type.CUSTOMER, id: custId});
        // ... etc ...
        if (somethingWrong) {
            const errorMsg = message.create({
                title: 'Something wrong',
                message: `Could not find something`,
                type: message.Type.ERROR
            });
            errorMsg.show();
            return null;
        }
        // ... etc ...
        const estId = createEstimate(data);
        const urlLink = url.resolveRecord({
            isEditMode: false,
            recordId: estId,
            recordType: record.Type.ESTIMATE
        });
        const successMsg = message.create({
            title: 'Estimate created',
            message: `Estimate has been successfully created <a href=${urlLink} target="_blank">here</a>.`;
            type: message.Type.CONFIRMATION
        });
    }
    return {createEstimate}
});

The above client script imports a few libraries, namely the N/record, N/ui/message and the N/url modules.

The N/record is needed to help load the contents of the record being operated on to obtain all the necessary information to process. (Another means is to pass all the values needed in the client script for the record – see the fields section further below as this can reduce the cost of using the N/record module by loading the record a second time.)

The N/ui/message is used to help warn the end user of insufficient or incorrect values satisfying the requirement mentioned previously.

And the N/url module is used to create a link to the resulting estimate record which the end user could click on if they needed to inspect it after its generation.

Notice also that the client script just exposes the sole function createEstimate within its record. This script will be uploaded into the File Cabinet but will not have a subsequent Script Record in NetSuite.

If somebody does try to create a script record from this client script in the File Cabinet they will be met with an abrupt error:

SuiteScript 2.1 entry point scripts must implement one script type function.

NETSUITE ERROR

This should be deliberate by design as this client script separates it from the other standard client scripts that will operate on records directly as this one has other purposes.

Create User Event Script

Next create the User Event Script that will create the button on the record and then once uploaded create a NetSuite script record for this script.

Here’s an example of what I created:

/**
 * @NApiVersion 2.x
 * @NScriptType UserEventScript
 * @NModuleScope SameAccount
 */
define([],
    () => {
        const beforeLoad = (context) => {
            try {
                const custId = context.newRecord.id;
                context.form.addButton({
                    id: "custpage_customer_create_estimate",
                    label: "Create Estimate",
                    functionName: `createEstimate(${custId})`
                });
                context.form.clientScriptModulePath = "SuiteScripts/XYZ/cs_create_estimate.js";
            } catch(e) {
                log.error({title: 'UE Error', details: e});
            }
        }
        return {beforeLoad};
    }
);

This script uses the standard beforeLoad function and inside it contains a try/catch block to capture any problems.

Inside the try/catch I simply capture the internal id of the customer record and then create a button using the form.addButton method giving a unique name with prefix custpage and a label that the user will see on the record along with the name of the function.

Notice the function is written with a parameter (I inserted the custId value), be mindful if you do not have parentheses after the function name and you don’t provide a value to be passed to the client script nothing from this user event script is sent through.

Also, be mindful of what you are sending through. If you are sending something complex try to wrap everything into a JavaScript object and use JSON.stringify(obj). In my case I just sent through the id of the record the user is viewing.

Lastly, you then need to point to where the client script is located using a path string, or if you prefer you could swap out this line for the following if you happen to know the internal id of the client script file in the File Cabinet:

context.form.clientScriptFileId = 123456;

Once you’ve uploaded the script you will need to create a User Event Script Record in NetSuite and you need to Deploy the script on the records you want it loaded.

In my case it was attached to the Customer record.

Once the script record is created you can then hop on over to your record and you should see your new shiny button, which once clicked will invoke the process you have connected in the client script.

Easy!

How To Get fields Property of Record In User Event beforeLoad

One other aspect I came across when accessing the fields of a record is that should you try to access these values using context.newRecord.fields.subsidiary (for example) it doesn’t capture the variable value, even though when you inspect the context.newRecord through log.debug you can see the property exists.

A way around this is to JSON.stringify(o) the context.newRecord object and then to JSON.parse(s) the stringified object to return it to a Javascript object. This seems to expose the fields property of the record.

Here’s how this looked:

const beforeLoad = (context) => {
    try {
        const r = JSON.parse(JSON.stringify(context.newRecord));
        const custId = r.id;
        const subId = r.fields.subsidiary;
        context.form.addButton({
            id: "custpage_customer_create_estimate",
            label: "Create Estimate",
            functionName: `createEstimate(${custId},${subId})`
        });
        context.form.clientScriptModulePath = "SuiteScripts/XYZ/cs_create_estimate.js";
    } catch(e) {
        log.debug({title: 'UE Error', details: e});
    }
}

As you can see from the above code the context.newRecord object is wrapped by the JSON.stringify() function which is then wrapped with the JSON.parse() function to transform the string back into an object.

Upon adding this line I was able to access the subsidiary field on the record which I could then subsequently pass through to the client script as a second parameter.

Here’s how the updated client script looked:

/**
 * @NApiVersion 2.x
 * @NScriptType ClientScript
 * @NModuleScope SameAccount
 */
define(['N/ui/message', 'N/url'], 
/**
 * @param {message} message
 * @param {url} url
 */
(record, message, url) => {
    const createEstimate = (custId, subId) => {
        // ... etc ...
        if (somethingWrong) {
            const errorMsg = message.create({
                title: 'Something wrong',
                message: `Could not find something`,
                type: message.Type.ERROR
            });
            errorMsg.show();
            return null;
        }
        // ... etc ...
        const estId = createEstimate(data);
        const urlLink = url.resolveRecord({
            isEditMode: false,
            recordId: estId,
            recordType: record.Type.ESTIMATE
        });
        const successMsg = message.create({
            title: 'Estimate created',
            message: `Estimate has been successfully created <a href=${urlLink} target="_blank">here</a>.`;
            type: message.Type.CONFIRMATION
        });
    }
    return {createEstimate}
});

As you can see from the above refactored client script I removed the N/record module as all values needed from the original Customer record for the client script are passed through as parameters into the createEstimate function. This negates the need of loading the record a second time and reduces the governance cost of your script.

In addition, I added a new second parameter subId to capture the subsidiary value needed from the Customer record.

Summary

A button can be created on a record in NetSuite using both a custom client script and a user event script that points to the client script. By using the form.addButton method in the user event script as well as form.clientScriptModulePath (or form.clientScriptFileId) to direct which client script to run you can easily achieve what you need with a button that is available in viewing or editing modes.

Finally, if you do want to send through some parameters to your client script (as defined in the functionName property) remember to append a set of parentheses with your parameter values. If you don’t insert any parameters nothing will be passed through from the user event script to the client script.