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.
1
2
3
4
5
6
7
8
9
10
11
12
13
define([], () => {
const saveRecord = (scriptContext) => {
console.log('saveRecord')
// do something
}
return {
saveRecord
}
})
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 recordscriptContext.mode
- The mode the record is in;create
,copy
oredit
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 recordscriptContext.fieldId
- The field that was changedscriptContext.sublistId
- The sublist that the field is in, if it is in a sublist or nullscriptContext.lineNum
= The line if the field is in a sublist or nullscriptContext.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 recordscriptContext.fieldId
- The field that was changedscriptContext.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 deletedcommit
- 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 recordscriptContext.sublistId
- The sublist that was changedscriptContext.operation
- The sublist operation that was performed (insert
,remove
orcommit
)
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 recordscriptContext.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 recordscriptContext.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 recordscriptContext.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 recordscriptContext.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.