Home Anatomy of SuiteScript: Client Script
Post
Cancel

Anatomy of SuiteScript: Client Script

Introduction

Client scripts are used to add custom functionality to the UI of a record; it will not be triggered when modifying/creating a record from SuiteScript or SuiteTalk. It runs in the browser and will always run the version of javascript the browser is using. Notably, you are able to use console.log for debugging and it will show up in the browser when deployed. Most APIs are available for client scripts, but some are not.

Usually client scripts are deployed to a specific record. Client scripts can also be attached to Suitelets.

Client scripts can not add or remove fields from the UI, but they can hide or show them. Client scripts can not add or remove buttons, but they can be used to define the behavior of the buttons.

Before we jump in, Eric Grubaugh has made a great video on the lifecycle of a client script.

Let’s jump right into the entry points for client scripts

Entry Points

pageInit

This entry point is called when the record loads in the UI. You must have this entry point in every client script even if you aren’t going to use it.

Invalid
1
2
3
4
5
6
7
8
9
10
11
12
13
define([], () => {


    const saveRecord = (scriptContext) => {
      console.log('saveRecord')
      // do something
    }


    return {
        saveRecord
    }
})
Valid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
define([], () => {


    const pageInit = (scriptContext) => {
      // do nothing
    }

    const saveRecord = (scriptContext) => {
      console.log('saveRecord')
      // do something
    }


    return {
        pageInit,
        saveRecord
    }
})

This entry point comes with two fields for the scriptContext object:

  • scriptContext.currentRecord - The current record
  • scriptContext.mode - The mode the record is in; create, copy or edit

validateField

This entry point is called when a field is changed in the UI. This applies to both body fields and sublist fields.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.fieldId - The field that was changed
  • scriptContext.sublistId - The sublist that the field is in, if it is in a sublist or null
  • scriptContext.lineNum = The line if the field is in a sublist or null
  • scriptContext.columnNum = The line if the field is in a matrix field or null

There is no special entry point for a field changed on a sublist or matrix field. There are other entry points that will be called; but none that are specific to a sublist field. When other actions are taken on a sublist, other entry points are called. See below.

Note that we do not have the new field value in the scriptContext object. We will have to use scriptContext.currentRecord.getValue({fieldId}) to get the new value (or possibly getSublistValue).

This entry point should be used to validate the field. If this function returns true, the field will be updated. If this function returns false, the field will not be updated. No error message will be displayed to the user, but because client scripts runs in the browser, you can you use the alert function.

Be careful not to include this function if it’s empty. Not returning a value is the same as returning false.

So if I create a client script in SDF and carelessly leave this function in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
define([], () => {

  const pageInit = (scriptContext) => {
    // correct; this function is required
  }

  const validateField = (scriptContext) => {
    // carelessly left in
  }

  const fieldChanged = (scriptContext) => {
    //main logic
  }

  return {
     pageInit,
     validateField,
     fieldChanged
  }

})

I won’t be able to update any fields

fieldChanged

This function is identical to validateField except that it is called after the field is updated. It has no return value and is used solely to perform actions after the field is updated.

Because this field is called after every field is updated a common pattern is to abstract the logic into a separate function for each field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fieldChanged(scriptContext) {

  if(scriptContext.sublistId) {
    return // we'll handle this separately if needed
  }

  switch(scriptContext.fieldId) {
    case 'custrecord_field2':
      fieldChangedField1(scriptContext)
      break
    case 'custrecord_field1':
      fieldChangedField2(scriptContext)
      break
  }
}

postSourcing

Many fields in NetSuite are linked together. For example when filling out a customer field on a sales order, the subsidiary, the terms, Bill-To etc. will be filled out automatically. So the question is, do we get the fieldChanged and validateField events for these fields? The answer is yes, we do get the events for these fields. But what if I specifically want to do something when all dependent fields are filled out for the customer?

This is where postSourcing comes in. This function is called after all dependent fields are filled out.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.fieldId - The field that was changed
  • scriptContext.sublistId - The sublist that the field is in, if it is in a sublist or null

sublistChanged

This function is called when a change is made to a sublist. The changes are one of the following:

  • insert - A new line was inserted.
  • remove - A line was deleted
  • commit - A line was commited

Inserted has a strict definition in NetSuite. This function is when you click the +  Insert button on a sublist. When the insert button is clicked, the sublistChanged function is called with the insert action even before entering data on the line. When you enter data and click the ✓  OK button, the sublistChanged function is called with the commit action.

If you add to the bottom of the sublist, the sublistChanged function is called once after adding with the commit action.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.sublistId - The sublist that was changed
  • scriptContext.operation - The sublist operation that was performed (insert, remove or commit)

lineInit

This function is called when a line is initialized. This happens when clicking any existing line or when clicking the empty line at the bottom of the sublist or when clicking the +  Insert button on a sublist.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.sublistId - The sublist that was clicked

validateLine

This function is called when a line is committed.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.sublistId - The sublist that was clicked

This function should return true if the line is valid and false if the line is not valid.

For example if you want to make sure the cost of a work order is not greater than the cost of the assembly, you would run a function after each line is committed to sum the cost of the assembly and compare it to the cost of the work order.

Here is an outline how such a script might look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function validateLine(scriptContext) {

  const currentRecord = scriptContext.currentRecord
  const sublistId = scriptContext.sublistId

  const assemblyCost = getAssemblyCost(currentRecord)
  const workOrderCost = getWorkOrderCost(currentRecord)

  if(assemblyCost > workOrderCost) {
    alert('The cost of the assembly is greater than the cost of the work order')
    return false
  }

  return true

  function getAssemblyCost(currentRecord) {
    const assembly = currentRecord.getValue({fieldId: 'assemblyitem'})
    // Some awesome SuiteQL
  }

  function getWorkOrderCost(currentRecord) {
    const linecount = currentRecord.getLineCount({sublistId: 'item'})
    // Some tedious SuiteScript
  }

}

validateDelete

This function is called when a line is deleted.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.sublistId - The sublist that the line was deleted from

This function should return true if the line is able to be deleted and false if the line should be prevented from being deleted.

validateInsert

This function is called when a line is inserted. Again, this is called when clicking the +  Insert button on a sublist. Without any fields filled out, the validateInsert function is called. The usefullness of this function is limited.

I have seen this function used just to prevent the user from inserting a line.

The scriptContext object has the following fields:

  • scriptContext.currentRecord - The current record
  • scriptContext.sublistId - The sublist that the line was inserted into

Something like this:

1
2
3
function validateInsert(scriptContext) {
  return false
}

That’s it.

Conclusion

The client script is a powerful tool that can be used to automate many tasks in NetSuite. Because of its limitation of only being able to run on the client, it is not a good fit for all use cases. Much validation logic should be done on the server side, particularly in the beforeSubmit entry point of user event scripts. Nevertheless, client scripts are very usefull for automating the primary job of many users - entering data into NetSuite.

Because of the wide range of use cases, it is difficult to give a good example of a client script.

I will eventually write a dedicated post with a good example of a client script.

This post is licensed under CC BY 4.0 by the author.

Anatomy of SuiteScript: User Event

Anatomy of SuiteScript: Restlet