Expand your SuiteScript skills in the Sustainable SuiteScript community for NetSuite developers.

4 Working with Sublists in SuiteScript 2.0

Created: May 22, 2017

Earlier in the series, we looked at how the N/record module allows us to work with records and body fields. That same module allows us to work with sublists on those records. We'll be demonstrating how the 1.0 sublist APIs have been converted in to 2.0 as well as some significant changes to the behaviour of sublists in 2.0.

Before we can examine the sublist APIs, we have to understand a very important SuiteScript concept: the Record Mode.

Record Modes: Standard and Dynamic

There are two different modes which you can use for working with Records in SuiteScript: Standard Mode and Dynamic Mode.

Standard Mode is the default mode. In this mode, the record's fields are not sourced, calculated, or validated until you save the record. This means that when working in Standard Mode, you will need to create the record, save it, then re-load it from the database in order to retrieve any formula or sourced field values. Because nothing is sourced or calculated until the record is saved, the order in which you set the fields on your record does not matter in Standard Mode; NetSuite will automatically set everything in the correct order when the record is submitted.

Dynamic Mode, however, is the real-time mode. Fields are calculated, sourced, and validated immediately as they are set. This mode largely mimics the behaviour you would see in the UI. You will get errors for invalid values right away, and calculated or sourced fields will be updated right away. Because of this real-time action, the order in which you set fields does matter in Dynamic Mode, so you are responsible for making sure your script sets fields in the same order that you would in the UI.

Now that we know what the Record Modes are, how does it actually look in development? We will go through several examples very shortly, but first we need to know that there are actually two separate APIs for working with sublists based on the Record Mode. These separate APIs also existed in SuiteScript 1.0, so let's look at how they have been translated over to 2.0.

API Equivalencies

Just like there were with record and body field APIs, there is essentially a 1:1 relationship between the 1.0 and 2.0 APIs for working with sublists.

This table describes the equivalent sublist APIs between SuiteScript 1.0 and 2.0

There are some cosmetic changes to note are in the function names, where many of the 2.0 names have dropped the word "Item", and they have replaced "LineItem" with "Sublist". Other than that, the functionality and the parameters you can provide to each function are largely unchanged.

Yet again I have bombarded you with the theory; now it's time to get into the code.

Example: Add a Line

// 1.0
function onRequest(params) {
    var order = nlapiCreateRecord("salesorder", {recordmode: "standard", entity: "75"});
    addLine(order);
    var orderId = nlapiSubmitRecord(order, true);
    order = nlapiLoadRecord("salesorder", orderId, {recordmode: "dynamic"});
    updateLine(order);
    insertLine(order);
    removeLine(order);
}

function addLine(rec) {
    // TODO
}

function updateLine(rec) {
    // TODO
}

function insertLine(rec) {
    // TODO
}

function removeLine(rec) {
    // TODO
}
// 2.0
/**
 * @NApiVersion 2.0
 * @NScriptType Suitelet
 * @NModuleScope SameAccount
 */
define(["N/record"], function (r) {
    function onRequest() {
        var order = r.create({
            type: r.Type.SALES_ORDER,
            isDynamic: false,
            defaultValues: {entity: 75}
        });
        addLine(order);
        var orderId = order.save({enableSourcing: true});
        order = r.load({
            type: r.Type.SALES_ORDER,
            id: orderId,
            isDynamic: true
        });
        updateLine(order);
        insertLine(order);
        removeLine(order);
    }

    function addLine(rec) {
        // TODO
    }

    function updateLine(rec) {
        // TODO     
    }

    function insertLine(rec) {
        // TODO     
    }

    function removeLine(rec) {
        // TODO    
    }

    return {onRequest: onRequest};
}); 

Here I've created an example Suitelet in both 1.0 and 2.0. I've set up the boilerplate of each script, and I've added the code to initialize a new Sales Order in each. I've created separate functions for the various line operations that we'll be performing; that way, we can focus just on the code for each operation, one at a time.

Again I won't be going through and testing this script in the article as this is just to show the API transition more than it is to show the outcomes of the code; those have not changed between the two versions.

The first thing we'll look at is how to add a new line item to our Sales Order. In both cases you can see I've instantiated the Sales Order in Standard mode, but for adding a new line, the API will actually be the same.

First let's add a line with Item 123 and a quantity of 5:

// 1.0
function addLine(rec) {
    rec.selectNewLineItem("item");
    rec.setCurrentLineItemValue("item", "item", "123");
    rec.setCurrentLineItemValue("item", "quantity", 5);
    rec.commitLineItem("item");
}
// 2.0
function addLine(rec) {
    rec.selectNewLine({sublistId: "item"});
    rec.setCurrentSublistValue({sublistId: "item", fieldId: "item", value: "123"});
    rec.setCurrentSublistValue({sublistId: "item", fieldId: "quantity", value: 5});
    rec.commitLine({sublistId: "item"});
}

This should look familiar to most, but we'll step through it anyway.

When we're adding a new line, we use the API that basically mimics what a user would do in the UI. We have to select the line we want to work with first. This selects the next available line in the sublist (i.e. the empty line at the end of the list).

Next we set the appropriate columns on the sublist - in this case, the Item and the Quantity.

Lastly, we commit our changes to the line. This mimics the user clicking "Add" on the line after making all of their changes.

As you can see, the 2.0 API is essentially a direct port of the 1.0 API; it is a very straight-forward transition.

Example: Update an Existing Line

Now that we've added a line to our Sales Order, we can save it and then re-load it to make sure we have the latest data. This time, we're loading the record in Dynamic mode.

After we've loaded the record, we want to update the line we just added; perhaps we want to increase the quantity by 2. This will require us to read the quantity value from the line, add 2 to the value, and then set it again on the same line.

// 1.0
function updateLine(rec) {
    rec.selectLineItem("item", 1);
    var currentQuantity = parseFloat(rec.getCurrentLineItemValue("item", "quantity"));
    var newQuantity = currentQuantity + 2;
    rec.setCurrentLineItemValue("item", "quantity", newQuantity);
    rec.commitLineItem("item");
}
// 2.0
function updateLine(rec) {
    rec.selectLine({sublistId: "item", line: 0});
    var currentQuantity = parseFloat(rec.getCurrentSublistValue({
        sublistId: "item",
        fieldId: "quantity"
    }));
    var newQuantity = currentQuantity + 2;
    rec.setCurrentSublistValue({
        sublistId: "item",
        fieldId: "quantity",
        value: newQuantity
    });
    rec.commitLineItem({sublistId: "item"});
}

Note that we use parseFloat when we read the value from the sublist. NetSuite is notoriously inconsistent at returning typed values (or maybe it's very consistent at only returning Strings, I'm honestly not sure which it is). This has not changed in 2.0, so we want to maintain this habit of ensuring we're receiving the correctly typed value.

Here you should notice a very important distinction between 1.0 and 2.0. In 1.0, the first line of a sublist had an index of 1. In 2.0, the first line of a sublist now has index 0, just like a typical JavaScript Array does. I feel like this is a very welcome correction to a baffling aspect of SuiteScript 1.0.

Other than that, as you can see the flow is still the same between 1.0 and 2.0; we select a specific line, modify it, then commit it. Since our record is in Dynamic mode, our changes to the field and the record are sourced, calculated, and validated immediately.

Example: Insert a Line

Just like the user can insert a line at any point in the sublist by clicking the Insert button, we can also insert lines using SuiteScript. The API is nearly identical to that of adding a new line.

// 1.0
function insertLine(rec) {
    rec.insertLineItem("item", 1);
    rec.setCurrentLineItemValue("item", "item", "77");
    rec.setCurrentLineItemValue("item", "quantity", 1);
    rec.setCurrentLineItemValue("item", "rate", 20);
    rec.commitLineItem("item");
}
// 2.0
function insertLine(rec) {
    rec.insertLine({sublistId: "item", line: 0});
    rec.setCurrentSublistValue({sublistId: "item", fieldId: "item", value: "77"});
    rec.setCurrentSublistValue({sublistId: "item", fieldId: "quantity", value: 1});
    rec.setCurrentSublistValue({sublistId: "item", fieldId: "rate", value: 20});
    rec.commitLine({sublistId: "item"});
}

We use the insert function and specify the index where we want to insert the line. In 1.0, by specifying an index of 1, we're saying that we want to insert this new line at the beginning of the sublist. In order to insert the new line at the beginning of the sublist in 2.0, we specify index 0 instead of index 1 as we did in 1.0.

Other than that, the calls we make are identical to adding a line.

Example: Finding and Removing a Line

In our last example today, we're going to remove a line, but we're going to do so by using a function that I don't see many SuiteScript developers leveraging: the function to find a sublist line by a specific value. The ability to find a line based on a value exists in both 1.0 and 2.0.

Previously in our script, we inserted Item 77 into our sublist. Now we want to remove that item from the list, but pretend we don't actually know which index it's on.

Before we can remove a line, we need to know its index. We can use the sublist's find functionality to locate the correct index.

// 1.0
function removeLine(rec) {
    var index = rec.findLineItemValue("item", "item", 77);
    if (index === -1) {
        // Not found
        return;
    }
    rec.removeLineItem("item", index);
}
// 2.0
function removeLine(rec) {
    var index = rec.findSublistLineWithValue({
        sublistId: "item",
        fieldId: "item",
        value: 77
    });
    if (index === -1) {
        // Not found         
        return;
    }
    rec.removeLine({sublistId: "item", line: index});
}

Here we use findLineItemValue to determine the index of the line on the item sublist that contains a value of 77 in its item column. If there are multiple lines that match your criteria, findLineItemValue returns the index of the first occurrence it encounters. If no lines match your criteria, then it will return -1.

The find function's name has changed slightly in 2.0, but other than that it behaves exactly the same.

recalc Replaced by sublistChanged

Before we wrap up, there is one important change to SuiteScript 2.0 Client Scripts that impacts how we work with sublists.

In 1.0 Client Scripts, there is an event called recalc. This event fires whenever a change has been made to a sublist that impacts the total of a transaction.

Let's say you are editing a Sales Order in the UI, and you adjust the Quantity of a line. That change will obviously change the total of the order, so NetSuite will fire the recalc event to any Client Scripts. However, if instead of changing the quantity, you changed something that did not impact the total, like perhaps the Location or the Item Description, then NetSuite would not fire recalc. This made it problematic to detect and handle changes to sublists that did not impact the total of a transaction.

In 2.0, there is no longer a recalc event on Client Scripts. It has been replaced by an event named sublistChanged. Unlike recalc, this event gets fired whenever any line change has been committed, regardless of what that change was. Whether you are inserting, removing, adding, or modifying a line, and no matter which columns you modify, this new event will get fired as soon as you commit that line change. This is much less confusing than recalc, and it allows us to build far more robust sublist interactions in our Client Scripts.