Chidi Okwudire IT Professional. ERP Enthusiast. NetSuite Certified Administrator, SuiteCloud Developer II, and ERP Consultant. Passionate About Empowerment Through Knowledge Sharing. Always Eager to Learn.

NetSuite Input Dialog

8 min read

What do you do when you need to capture user input just before they submit a form or right after they click that "Approve" or "Reject" button? The answer is simple: Use the NetSuite Input Dialog! Click To Tweet

TL;DR

NetSuite Input Dialog in Action
NetSuite Input Dialog in Action
  • NetSuite’s N/ui/dialog module does not include an input dialog although there are situations where capturing user input based on dynamic criteria is necessary.
  • Existing solutions compromise on user experience, are rigid, and/or are too tightly-coupled with NetSuite’s internal dependencies which may change without notice.
  • The NetSuite Input Dialog illustrated above is built on NetSuite’s dialog module, thereby providing a native, elegant, and easy-to-integrate answer to the input dialog problem.
  • This module could be yours right now, at no cost, for use in your non-commercial and commercial projects; grab it here!

Common Use Cases Requiring a (Modal) Input Dialog

NetSuite’s entry forms are the primary means of interacting with users and capturing input. Fields on forms can be shown conditionally, made optional or mandatory, and so on. This can be done using static configuration at the form level, via workflows, and/or scripting. However, there are scenarios where these options do not suffice. Let’s consider a few examples. (Feel free to skip to the next section if the need for an input dialog is already sufficiently evident to you.)

Rejection Reasons in Approval Workflows

In approval processes, it is typical that when a user rejects an entry, they are required to provide a rejection reason. Since we do not know upfront if a user will approve or reject the entry, we cannot use a preconfigured mandatory “Rejection Reason” field on the record.

This is a classical example of where a modal input dialog to capture the rejection reason would be logical: Upon pressing the “Reject” button, the input dialog is shown and the user is required to enter a rejection reason before being able to reject the entry. Because the modal input dialog is shown over the entry to be approved, the user does not need to transition between screens, producing a much better user experience.

I’ve seen several NetSuite approval workflows over the years and all of them approach the rejection reason requirement in an awkward, non-intuitive way: When the user clicks the “Reject” button, they are redirected to a new page where they should enter the rejection reason. Upon entering the rejection reason and saving (what is actually a separate record), they are redirected back to the original entry being approved. Talk about poor user experience!

As an aside, I recall when I first got exposed to NetSuite and had to approve vendor bills. I can’t tell you how often I ended up thinking I had rejected bills but not quite because I clicked the “Reject” button and went my way without anticipating the separate rejection reason screen. I’ve even seen less prudent implementations where the entry is actually rejected before being redirected to the rejection reason screen! This offers a convenient backdoor for “lazy” managers to circumvent entering a rejection reason entirely. But I’m digressing now…

I strongly believe that one of the reasons why most approval workflows have resorted to the awkward flow described above is the absence of a native input dialog in NetSuite. Well, now that there’s one, I look forward to seeing approval workflows that leverage this approach to produce a smoother user experience. To anyone ready to take up the challenge, this article by “NetSuite Developer”[I]NetSuite Developer (April 21, 2019). Trigger Client Scripts before Workflow Actions fire. Available at http://www.netsuiterp.com/2019/04/trigger-client-scripts-before-workflow.html [Accessed November … Continue reading might come in handy.

3-Way Match Validation

In my case, this was the scenario that led me to tackle the Input Dialog challenge. 3-way matching is a common accounts payable control process in which vendor bills, item receipts, and purchase orders are validated based on predefined thresholds. If the variance exceeds those thresholds, the outcome is a “no-match”, typically leading to additional approvals. Similar to the approval workflow scenario, we do not know upfront what the outcome of a 3-way match execution will because the user may make changes to the transaction which could affect the results. However, if there’s a “no-match”, we want to prompt the user to enter an explanation that could be passed on to approvers or simply stored for reference.

I was glad to see a solution by SquareWorks Consulting that captures the 3-way match failure reason using a modal input dialog with native look-and-feel. Unfortunately, as at the time of writing, their implementation is not publicly available. I do not know how they tackled the problem, but I will tell you how I did next.

Solution: The NetSuite Input Dialog

In this section, I’ll outline the logic behind NetSuite Insights’ input dialog solution, providing some sample code. As a reminder, we’re offering a full-fledged version that is based on this logic for free here. This section is ideal for those seeking to deepen their understanding of the technique, allowing them to apply it to other use cases e.g. capturing other forms of user input than free-form text.

The solution consists of two crucial parts:

  1. Express your dialog in HTML. NetSuite’s N/u/dialog module accepts HTML in the message field. This offers us nearly limitless possibilities in terms of what we can add to a dialog. By building on NetSuite’s dialog module using “raw” HTML for the specification, we get a native look-and-feel essentially for free while having a great degree of flexibility.
  2. Use jQuery or DOM manipulation to capture user events and realize the logic needed to make the dialog do what we want. I must warn you that this solution is technically a “hack” because NetSuite officially does not encourage the use of jQuery or DOM manipulation[II]NetSuite (March 5, 2011). UI Objects Overview. Available at https://netsuite.custhelp.com/app/answers/detail/a_id/10666 [Accessed November 17, 2020]. However, that does not make it completely out-of-bounds. There is some risk in towing this line but, in several cases such as this one, that risk is acceptable in my opinion. Please refer to my previous “NetSuite Stick Headers” article where I discuss this topic in some more detail.

Direct DOM manipulation or jQuery usage is not officially supported by NetSuite. Understand the potential risks of solutions based on these approaches and make informed decisions.

Learning By Example

Let’s walk through an example to help make the solution outlined above more concrete. For the sake of brevity, the following sample code creates a simple input dialog with a single “OK” button, captures whatever input the user enters, and writes it to the “memo” field on the form over which the dialog was opened. To keep things simple, the dialog is added to a client script and is shown unconditionally when the save button is pressed. In real life, you’ll need some logic to determine when the dialog should be shown.

The code sample below is simply for illustration purposes and not intended for Production use. Use the Input Dialog module instead.

/**
 * @NApiVersion 2.0
 * @NScriptType ClientScript
 * @NModuleScope SameAccount
 */
define(['N/ui/dialog'],

function(dialog) {
    /**
     * Validation function to be executed when record is saved.
     */
    function saveRecord() {
        var htmlMsg = '<script>'+
            'function onClick(id){' +
                'nlapiSetFieldValue("memo", jQuery("#textarea").val());' +
                'jQuery(`:button[value="${id}"]`).click();' +
            '}; '+
            '(function($){' +
                '$(function($, undefined){' +
                    '$(".uir-message-buttons").last().hide();' +
                    '});' +
                '})(jQuery);'+ 
            '</script>'+
            '<textarea id="textarea" rows="5" cols="40">Enter details here...</textarea>\
            <div class="uir-message-buttons"><button value="shadow-1" onclick="onClick(1)">OK</button></div>';
        
        var options = {
            title: 'Basic Input Dialog',
            message: htmlMsg,
            buttons: [{label: 'OK', value: 1}]
        };

        dialog.create(options)
        .then(function(result) {
            console.log('Input dialog closed with button ' + result);
            // TODO: Trigger saving the form
        })
        .catch(function(reason) {
            console.log('Failure: ' + reason);
        });
    }
 
    return {
       saveRecord: saveRecord
    };
});
  • Line 33: Notice that we use NetSuite’s native dialog.create() function to which we pass HTML in the message parameter (line 29).
  • Line 24: In HTML, we define a standard textarea field to capture the user input. If that were all we did, we’d have an input dialog that includes a text field but we will not be able to retrieve the text entered by the user because the only information NetSuite passes back to us (line 35) is the ID of the button that was clicked.
  • Line 25: To access the user input, we basically inject our own shadow “OK” button and define an onClick() function (lines 14-17) that gets fired when the user clicks the shadow button.
  • Without lines 18 – 22, we’ll have two rows of “OK” buttons: The “real” one automatically added by NetSuite (outside of our control) and the “shadow” button we added. What this piece of code does is to hide NetSuite’s buttons as soon as the dialog loads.
  • Line 15: The onClick() event handler uses NetSuite’s nlapiSetFieldValue() to persist the contents of the text field to the “memo” field on the form. Remember this is just for illustration; we could use any other field or skip this step entirely. We use jQuery to grab the value of the text area field.
  • Line 16: We use jQuery to essentially click the original ‘OK’ button automatically added by NetSuite (although we hid the button upon loading the dialog, we can still interact with it). This allows us to leverage NetSuite’s existing logic for closing the dialog and doing whatever else happens behind the scenes. In that way, if NetSuite changes their internal logic, the risk of our code breaking is low.

An Easier Way: Integrating the NetSuite Input Dialog

Without some HTML/jQuery knowledge, the above code might be pretty challenging to understand. The good news is that you do not have to understand every line of that code. We’ve packaged the solution for you in an easy-to-use yet powerful Input Dialog module which you can integrate into your script without worrying about the nitty-gritty details.

The best part is that the Input Dialog‘s API is very much aligned with NetSuite’s dialog.create(), thereby allowing you to leverage existing patterns for creating your dialogs. Here’s a complete example of the Input Dialog in action.

NetSuite Input Dialog Example
NetSuite Input Dialog Example

Let’s quickly review the code that produced the above results.

/**
 * @NApiVersion 2.0
 * @NScriptType ClientScript
 * @NModuleScope SameAccount
 */
define(['NSI_CM_InputDialog'],

function(nsiInputDialog) {
    /**
     * Validation function to be executed when record is saved.
     */
    function saveRecord() {
        var options = {
            title: 'Input Dialog',
            message: 'Please enter your rejection reason below:',
            buttons: [{
                label: 'Reject', 
                value: 1
            },{
                label: 'Cancel',
                value: 2
            }],
            textarea: {
                rows: 6,
                cols: 48,
                isMandatory: true,
                caption: 'Rejection Reason',
                fieldId: 'memo',
                actionButtons: [1]
            }
        };

        var failure = function (reason) {
            console.log('Failure: ' + reason);
        }
        
        var success function (result, value) {
            alert('Input dialog closed by clicking button ' + result + ' | value: ' + value);
            // Do something with the actual result here.
        }
        
        nsiInputDialog.create(options, success, failure);
    }
 
    return {
       saveRecord: saveRecord
    };
});
  • Line 6: We now import the NetSuite Insights’ Input Dialog module NSI_CM_InputDialog instead of N/ui/dialog. This module is available for free here.
  • Lines 23-30: We define the text area configuration. The rest of the options variable is identical to what we would specify if we were using N/ui/dialog! This makes integrating the Input Dialog simple as it leverages existing patterns.
  • Line 37: Notice that our success callback takes an additional parameter, namely the value entered by the user. This allows you to do whatever you want with the input in case writing it to a text field on the form (line 28) is not sufficient for your use case.

That’s all there is to it. However, before you we go, I’ll like to highlight alternative solutions that you might want to consider.

Alternative Solutions

Our approach is not the only way to solve this problem. Here are pointers to other solutions out there in the wild, along with my take on when they might be viable alternatives.

Community member Adolfo Garza, in his 2016 blog post “SuiteScript 2.0 Dialog Prompt + On SaveRecord”[III]Adolfo Garza (December 29, 2016). SuiteScript 2.0 Dialog Prompt + On SaveRecord. Available at https://ursuscode.com/netsuite-tips/suitescript-2-0-dialog-prompt-on-saverecord [Accessed November 17, … Continue reading, captures the two common alternative solutions (the screenshots below come from that article).

  1. Use the prompt() method which is implemented by all standard browsers. While this will get the job done, if you care about a native look-and-feel, it is not really an option. Moreover, the user can suppress future popups by checking the “Prevent this page from creating additional dialogs” option. This could potentially derail your solution. Also, there’s no way to make the input mandatory.
    JS Prompt Dialog
  2. Hack deeper and directly use the Ext JS library that NetSuite uses internally to produce dialogs. In my opinion, it is not wise to build a solution that depends on NetSuite’s internal logic. NetSuite might move away from the library or upgrade to a new version (they’re currently still on a very old version so an upgrade scenario is realistic). I’m not afraid to hack as you can tell, but I feel this is the wrong level to hack at. Moreover, the look-and-feel, while closer to the NetSuite dialogs’, is still noticeably different. For example, there’s an “X” button which allows you to close the dialog possibly without entering any input.
    Ext JS Input Dialog

My greatest concern with both solutions above though is that, should you want to capture other kinds of inputs, e.g. checkboxes or multi-select options, you’ll pretty much be out of luck. On the other hand, the approach used by the Input Dialog can be easily extended to capture other inputs. (Depending on demand/needs, I might implement such extensions in the future. Drop a comment below if you have interesting use cases not currently covered. Also, if you implement extensions before me, be sure to share!)

Parting Words

Undoubtedly, others have solved the input dialog problem in the past but have decided not to share their solution with the community (or I was not skilled enough to find them during my extensive googling). Here’s my take: It is bad enough that NetSuite does not provide basic utilities like an input dialog. It is equally sad that those who have solved such basic problems do not see the need to share with the community, often trying to monetize their solutions instead. NetSuite Insights is dedicated to making sure that common utilities (or at least the logic behind them) are put out there for the benefit of all. And I call on other community leaders to emulate! It is obviously a business environment and money must be made. But some things are so basic that they need not be monetized…


Do you have utilities lying around that you know would be useful to the community? I encourage you to clean them up and share them. If you need some help or inspiration, feel free to reach out to us at stories[at]netsuite-insights.com


There are more NetSuite Insights and (free) utilities in the works so be sure to subscribe to our no-nonsense mailing list to get notified when new NetSuite Insights and freebies are published (approximately 2-3 mails per month).

Other Interesting Utilities

Further Reading[+]

Chidi Okwudire IT Professional. ERP Enthusiast. NetSuite Certified Administrator, SuiteCloud Developer II, and ERP Consultant. Passionate About Empowerment Through Knowledge Sharing. Always Eager to Learn.

14 Replies to “NetSuite Input Dialog”

  1. Hello!

    This is an awesome code snippet that we can use for rejection notes for different types of records that require approval. I noticed that in your code, followed by your example, this only functions in Edit mode. I was hoping you would know of a way in which this can be used in View mode? We would like to implement this where users can mark a record as rejected without being able to go in and edit. If the user can edit the record, they could just put in their rejection note straight in the field without the use of the Input Dialog.

    1. Hi Alejandro,

      Thanks for reaching out. I demonstrate this solution in Edit mode but there’s no reason why it won’t work in View mode. There are techniques for adding buttons in view mode e.g. as explained here. That should do the trick.

      Cheers.

  2. Hello! I tried to use this with Save button but did not succeed. Use case is that when user edits a transaction and then saves it, if certain criteria are true user would need to add additional note explaining the changes. I tested this so that I added the setValue to few different places in the script, but no luck. Every time the text from input dialog is lost in the save. Is there a way to do this with NetSuite Input Dialog? Thanks!

    1. Hi Markus,

      Please share the relevant piece of your code and how you’re triggering it (I suspect the client-side saveRecord event). Without seeing what you’re doing, it’s hard to tell what might be going wrong.

      Cheers

      1. Hi!

        Here is the code I used. Very simple, I just added the setValue there.



        /**
        * @NApiVersion 2.0
        * @NScriptType ClientScript
        * @NModuleScope SameAccount
        */
        define(['NSI_CM_InputDialog'],

        function(nsiInputDialog) {
        /**
        * Validation function to be executed when record is saved.
        */
        function saveRecord() {
        var options = {
        title: 'Input Dialog',
        message: 'Please enter your rejection reason below:',
        buttons: [{
        label: 'Reject',
        value: 1
        },{
        label: 'Cancel',
        value: 2
        }],
        textarea: {
        rows: 6,
        cols: 48,
        isMandatory: true,
        caption: 'Rejection Reason',
        fieldId: 'memo',
        actionButtons: [1]
        }
        };

        var failure = function (reason) {
        console.log('Failure: ' + reason);
        }

        var success function (result, value) {
        alert('Input dialog closed by clicking button ' + result + ' | value: ' + value);
        // Do something with the actual result here.
        newRecord.setValue({
        fieldId: 'custbody_edit_notes',
        value: value
        });
        }

        nsiInputDialog.create(options, success, failure);
        }

        return {
        saveRecord: saveRecord
        };
        });

        1. Hey Markus,

          Your code has some issues. Please open the debugger (F12) and you’ll likely see errors. Here are the ones I spotted:

          1. var success function() should be var success = function.
          2. newRecord is not defined anywhere. Your saveRecord callback should have a parameter on which you can access currentRecord (not newRecord). You’ve not defined that parameter in the saveRecord and so nothing will work.

          With these fixes, I was able to run your code in my demo account and it worked.

          Here are some more points to take into account. Since you’re intercepting the native save button, the save action will not proceed (your saveRecord is implicitly returning false which tells NetSuite not to save and that’s what you want since you’re evaluating criteria). To trigger a save after your done, you can make this *undocumented* call: getNLMultiButtonByName(‘multibutton_submitter’).onMainButtonClick(this); as described at https://www.abaci-us.com/netsuite-suitescript-2-dialog-confirm-workaround/

          Let me know how it goes.

      2. Oh and sure there is also the load of the record, that is missing from previous example. So this is the part I added:

        var newRecord = context.currentRecord;

        newRecord.setValue({
        fieldId: ‘custbody_edit_notes’,
        value: value
        });

  3. Hello again! Sorry, my previous example was not so good. It was quick copy paste because I didn’t have my actual code with me at that moment. Here is an actual example I tested in my SB.

    —-
    /**
    * @NApiVersion 2.0
    * @NScriptType ClientScript
    * @NModuleScope SameAccount
    */
    define([‘./NSI_CM_InputDialog’],

    function(nsiInputDialog) {
    /**
    * Validation function to be executed when record is saved.
    */
    function saveRecord() {
    var options = {
    title: ‘Input Dialog’,
    message: ‘Please enter your edit reason below:’,
    buttons: [{
    label: ‘Ok’,
    value: 1
    },{
    label: ‘Cancel’,
    value: 2
    }],
    textarea: {
    rows: 6,
    cols: 48,
    isMandatory: true,
    caption: ‘Rejection Reason’,
    // fieldId: ‘memo’,
    actionButtons: [1]
    }
    };

    var failure = function (reason) {
    console.log(‘Failure: ‘ + reason);
    }

    var success = function (result, value) {
    alert(‘Input dialog closed by clicking button ‘ + result + ‘ | value: ‘ + value);
    // Do something with the actual result here.

    if (result == 1) {

    var currentRecord = context.currentRecord;

    currentRecord.setValue({
    fieldId: ‘custbody_edit_notes’,
    value: value
    });

    getNLMultiButtonByName(‘multibutton_submitter’).onMainButtonClick(this);
    }

    }

    nsiInputDialog.create(options, success, failure);
    }

    return {
    saveRecord: saveRecord
    };
    });

    So the result I would like to have:
    – User does changes to record
    – User selects Save
    – Pop up will show and user will add note
    – Record will be saved right after pop up is closed with “Ok”.

    Issues:
    – With this code the value is not stored to the custbody_edit_notes field
    – Record saving process will not proceed.

    If you were able to make it work, could you share your script example? Thanks!

    1. Hey Markus,

      1. Updating the notes field didn’t work because your code has an error: You’re referencing undefined variable ‘context’. If you looked at the console (F12), you’d have seen this error as well.
      2. The logic you’ve implemented to retrigger the save does not match the pattern described in the article I referenced. You need at least one variable to track your state. Please review and understand that article.

      Here’s working code (validated in my test account). Cheers!


      /**
      * @NApiVersion 2.0
      * @NScriptType ClientScript
      * @NModuleScope SameAccount
      */
      define(['./NSI_CM_InputDialog'],

      function(nsiInputDialog) {

      // Use variable to track state as per trick explained at: https://www.abaci-us.com/netsuite-suitescript-2-dialog-confirm-workaround/
      var editReasonCaptured = false;

      /**
      * Validation function to be executed when record is saved.
      */
      function saveRecord(context) {

      if (!editReasonCaptured) {
      var options = {
      title: 'Input Dialog',
      message: 'Please enter your edit reason below:',
      buttons: [{
      label: 'Ok',
      value: 1
      },{
      label: 'Cancel',
      value: 2
      }],
      textarea: {
      rows: 6,
      cols: 48,
      isMandatory: true,
      caption: 'Edit Reason',
      actionButtons: [1]
      }
      };

      var failure = function (reason) {
      console.log('Failure: ' + reason);
      }

      var success = function (result, value) {
      //alert('Input dialog closed by clicking button ' + result + ' | value: ' + value);
      if (result == 1) {

      var currentRecord = context.currentRecord;

      currentRecord.setValue({
      fieldId: 'custbody_edit_notes',
      value: value
      });

      editReasonCaptured = true
      getNLMultiButtonByName('multibutton_submitter').onMainButtonClick(this); // Trigger NetSuite to reinvoke the saveRecord callback
      }

      }

      nsiInputDialog.create(options, success, failure);
      } else {
      editReasonCaptured = false; // Reset flag
      return true; // Allow NetSuite to proceed with saving
      }
      }

      return {
      saveRecord: saveRecord
      };
      });

      1. Hello!

        Thank you, this one works! It was that Abaci Trick that didn’t work with my test. This is great 🙂

        1. Good to hear. You’re welcome! Glad to see another use case of this “hack” 😉

          1. Hi! I noticed one issue still. As you know there might be other actions linked to the Save button. By default like “Save & New” or with approval “Save & Approve”. With this Abaci Trick it does not follow what user has selected, it will follow what has been the last selection and is visible as the Main Button. This is dangerous, because with this user might click “Save” but result might be “Save & Approve”.

            Any ideas how to fix this?

          2. Hi Markus,

            Interesting find… This issue has to do with NetSuite’s internal behavior and not the input dialog module and I don’t directly know how you can address it. Have you had a look at the documentation? Maybe NetSuite APIs offer a way to detect the save mode. If not, perhaps you can inspect the code in the browser and see if there’s a way to detect the different save options. Another alternative could be to create a custom save button and hide the default one. In that way, you’re fully in control and not dependent on undocumented NetSuite functions.

            Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *

You deserve to know as soon as we share a new NetSuite Insight.
We won't disturb you with anything else or share/sell your data.