案例展示NetSuite UI界面中创建动态下拉列表

在NetSuite的主界面或列表中,创建动态下拉子列表,无缝嵌入到商业应用流程中

有什么用

学会如何在NetSuite UI界面中创建动态下拉列表

  • 在主界面(Custom Body Field)中创建动态下拉列表字段
  • 在列表行(Custom Line Item Field)中创建动态下拉列表字段

在现实业务流程中,这样的动态下拉列表应用十分广泛;而NetSuite自带的自定义字段的动态下来过滤功能十分有限。很多的应用场景无法满足,这里就不一一展开说明,懂的都懂。

需求

image-20240618205434547

本案例:要在下拉列表中,将Replacement Vendor字段的内容,动态地根据Replacement Item字段的内容而决定。就是根据Item货品的供应商列表来过滤当前自定义记录界面中的Replacement Vendor字段的下拉内容,从而达到Replacement Vendor字段的下拉列表不展示系统中所有的Vendor,而是限定于特定Item货品(当前行中的货品字段)的供应商列表(这个使用其实也同时可以展示该供应商的此货品采购价)

怎么用

  1. 创建一个系统中的自定义字段
    1. 如果是把动态列表放在主界面,那么该‘容器’字段也可以使用UserEvent的script生成出这个一个动态的自定义字段。
    2. 如果是把动态列表放在主界面下的列表中(比如:销售订单的货品列表中),那么就必须要新建一个自定义列字段;
    3. 而假如是要把动态列表放在主界面下的列表中(比如:主从关系的自定义记录类型Custom Record Type),那么就是新建一个自定字段在在子记录类型中(这个子自定义记录类型在UI中会变成一个列表sublist)
  2. 新建一个Library脚本文件Script(详见下方的PRI_DropDown_lib.js文件全部内容展示)
  3. 创建一个User Event Script记录
    1. 用来新建初始化字段(可选,根据实际需要)
    2. 用来初始化字段的HTML内容;这个初始化内容是根据列表中当前的货品来决定的,UI的编辑状态用户是可以随时在界面中切换货品字段内容,从而需要重新初始化该HTML内容(详见实现在下方的Client Script中)
  4. 创建一个Client Script记录
    1. 在列表的line init事件中,初始化当前行的Vendor列表(根据当前的货品字段内容)
    2. 在用户切换了Item货品字段内容后,重新刷新初始化当前行的Vendor列表

相关内容

实现方法

Create Custom Field

新建自定义字段的定义案例:

image-20240619173308707

说明:这个字段的TYPE,用Free Form Text也是一样的。

User Event

to initial the UI to put-in/inject the dropdown HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
var intDefProject = currentRecord.getValue('custbody_pri_prj');
var clsProjectDropDown = new CLLIB.PROJECTDROPDOWN(intEntityId,
'');

// Set entity projects dropdown options HTML
var strInitSelectHtml = clsProjectDropDown.initOptionHtml(
intDefProject, scriptContext.type);
objFldProject.defaultValue = "<div class=\"uir-field-wrapper\" data-field-type=\"text\"><span id=\"projectdropdown_fs_lbl_uir_label\" class=\"smallgraytextnolink uir-label \"><span id=\"projectdropdown_fs_lbl\" class=\"labelSpanEdit smallgraytextnolink\" style=\"\">"
+ "<a tabindex=\"-1\" title=\"What's this?\" href='javascript:void(\"help\")' style=\"cursor:help\" class=\"smallgraytextnolink\" onmouseover=\"this.className='smallgraytext'; return true;\" onmouseout=\"this.className='smallgraytextnolink'; \">"
+ strPrjLbl
+ "</a>"
+ "</span></span><span class=\"uir-field\">"
+ strInitSelectHtml + "</span>" + "</div>";

上面这个方案是直接注入到系统的自定义字段中(在NetSuite系统的自定义字段中定义的)

或者使用全新的Script新增的动态的html字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var objFldProject = objForm.addField({
id: 'custpage_pri_prj',
type: ui.FieldType.INLINEHTML,
label: strPrjLbl
});
objForm.insertField({
field: objFldProject,
nextfield: 'custbody_pri_co',// 'job'
});

objFldProject.defaultValue = "<div class=\"uir-field-wrapper\" data-field-type=\"text\"><span id=\"projectdropdown_fs_lbl_uir_label\" class=\"smallgraytextnolink uir-label \"><span id=\"projectdropdown_fs_lbl\" class=\"labelSpanEdit smallgraytextnolink\" style=\"\">"
+ "<a tabindex=\"-1\" title=\"What's this?\" href='javascript:void(\"help\")' style=\"cursor:help\" class=\"smallgraytextnolink\" onmouseover=\"this.className='smallgraytext'; return true;\" onmouseout=\"this.className='smallgraytextnolink'; \"> "
+ strPrjLbl
+ "</a>"
+ "</span></span><span class=\"uir-field\">"
+ "<select id=\"custpage_projectdropdown\" name=\"custpage_projectdropdown\" autocomplete=\"off\" lineindex=\"1\" class=\"input uir-custom-field\">"
+ "<option value=\"\"> </option>"
+ "</select>"
+ "</span>" + "</div>" + "";


Client Script dynamic behaviors

pageInit

可选,仅用于使用主界面的系统自带自定义字段来‘加载’动态列表的情况下

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
/**
* Function to be executed after page is initialized.
*
* @param {Object}
* scriptContext
* @param {Record}
* scriptContext.currentRecord - Current form record
* @param {string}
* scriptContext.mode - The mode in which the record is
* being accessed (create, copy, or edit)
*
* @since 2015.2
*/
function pageInit(scriptContext) {

...

// Bind select option change event
var clsProjectDropDown = new CLLIB.PROJECTDROPDOWN(intEntityId,
objDropdownDom, {'currentRecord': currentRecord});
clsProjectDropDown.bindSelectEvent();

...
}

fieldChanged

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function fieldChanged(scriptContext) {
switch (scriptContext.fieldId) {

case ....

// Bind select option change event
var clsProjectDropDown = new CLLIB.PROJECTDROPDOWN(
intEntityId, objDropdownDom, {'currentRecord': currentRecord});

if (!intEntityId) {
clsProjectDropDown.clearSelectOptions();
return true;
}

// Draw entity project dropdown options
clsProjectDropDown.drawDOM();
break;
}
}

pri_RetMgr_vendor_refreshSelectOption

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
29
30
31
32
33
34
35
36
37
38
39
function pri_RetMgr_vendor_refreshSelectOption(currentRecord) {

var lnIdx = currentRecord.getCurrentSublistIndex({
sublistId: strSublistId_replc
});
var intItemId = currentRecord.getCurrentSublistValue(strSublistId_replc, RETMGRLIB.REC_RETURNMGR_REPLC_LINE.ITEM);
var intVendorId_cur = currentRecord.getCurrentSublistValue(strSublistId_replc, RETMGRLIB.REC_RETURNMGR_REPLC_LINE.VENDOR);

try {

var objDropdownDom = document.getElementById('recmachcustrecord_pri_return_mgr_replc_parent_custrecord_pri_return_mgr_replc_selvnd_fs');
// Bind select option change event
var clsVendorDropDown = new DDLIB.DROPDOWN(intItemId, objDropdownDom, {'currentRecord': currentRecord});
jQuery('#recmachcustrecord_pri_return_mgr_replc_parent_custrecord_pri_return_mgr_replc_selvnd_fs').html(clsVendorDropDown.initOptionHtml(intVendorId_cur));
clsVendorDropDown.bindSelectEvent();

} catch (ex) {
console.log(ex);
}

// Special case: After you change the item from Item A to Item B, the original vendor value should/might be cleared(since that vendor is not in new Item B’s vendor list)
if (intVendorId_cur) {
var bolVendorAvailable = false;
var arrProjectResObj = clsVendorDropDown.arrProjectResObj;
for (var i = 0; arrProjectResObj && i < arrProjectResObj.length; i++) {

if (intVendorId_cur == arrProjectResObj[i].ID){
bolVendorAvailable = true;
break;
}
}
if (bolVendorAvailable === false)
currentRecord.setCurrentSublistValue({
sublistId: strSublistId_replc,
fieldId: RETMGRLIB.REC_RETURNMGR_REPLC_LINE.VENDOR,
value: ''
});
}
}

从这个函数可以得知这个自定义的动态下拉字段,初始化的值也是会被加载的,非常顺滑。


Library - PRI_DropDown_lib.js

used in both Client and UserEvent/Server side. 核心公共函数库

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//------------------------------------------------------------------
//Developer: Carl
//Description: Need a dynamic Drop Down on the record/transaction of vendor list.
// Thus, the drop down on record/transaction should filter for item that are related.
//------------------------------------------------------------------

/**
* @NApiVersion 2.x
* @NModuleScope Public
*/
define(
['N/error', 'N/record', 'N/runtime', 'N/search', './PRI_RM_ReturnManager_lib'],
/**
* @param {error}
* error
* @param {record}
* record
* @param {runtime}
* runtime
* @param {search}
* search
* @param {dialog}
* dialog
*/
function (error, record, runtime, search, RETMGRLIB) {

// ---------------------- Drop Down Class-----------------
function DROPDOWN(intItemId, objDropdownDom, options) {

this.intItemId = intItemId;
this.objDropdownDom = objDropdownDom;

if (intItemId) {

this.arrProjectResObj = this.lookupVendorRecords();
}

this.strInitSelectFld = "<select id=\"custpage_projectdropdown\" name=\"custpage_projectdropdown\" autocomplete=\"off\" lineindex=\"1\" class=\"input uir-custom-field\">"
+ "<option value=\"\"> </option>"
+ 'REPLACE_OPTIONAL_HTML' + "</select>";

if (options && typeof (options.currentRecord) != 'undefined') {
this.currentRecord = options.currentRecord;
}

}

/**
* lookup Vendor Records
*
* @param {String}
* strLookupKeyWord Lookup Keyword
* @returns {Object} objOrderStatusSublist
*/
DROPDOWN.prototype.lookupVendorRecords = function (strLookupKeyWord) {

var intItemId = this.intItemId;

if (!intItemId)
return [];

var arrProjectRes = [];
var objFilters = [['isinactive', 'is', 'F']];
// if (intItemId) {
// objFilters.push('and');
// objFilters.push([ RETMGRLIB.REC_PRIPROJECT.CUSTOMER, 'anyof',
// [ intItemId ] ]);
// }
objFilters.push('and');
objFilters.push(['internalid', 'anyof', [intItemId]]);
var objColumns = ['itemid', 'displayname', 'othervendor', 'cost'];
objColumns.push({name: "internalid", join: "vendor"});

var objRecordRes = search.create(
{
type: 'item',
filters: objFilters,
columns: objColumns
}).run().getRange({
start: 0,
end: 1000
});

for (var i = 0; objRecordRes && i < objRecordRes.length; i++) {

arrProjectRes.push({
ID: objRecordRes[i].getValue({
name: "internalid",
join: "vendor"
}),
// NAME: objRecordRes[i].getValue('itemid'),
COST: objRecordRes[i].getValue('cost'),
VALUE: objRecordRes[i].getText('othervendor')
});
}

return arrProjectRes;
};

/**
* Dynamically clear select options
*
* @returns {Boolean}
*/
DROPDOWN.prototype.clearSelectOptions = function () {

jQuery("#custpage_projectdropdown").replaceWith(
this.strInitSelectFld.replace('REPLACE_OPTIONAL_HTML',
''));

return true;
};

/**
* Draw select option DOM element
*/
DROPDOWN.prototype.drawDOM = function () {

var objDropdownDom = this.objDropdownDom;
var arrProjectResObj = this.arrProjectResObj ? this.arrProjectResObj : this.lookupVendorRecords();

var strOptionHtml = '';
for (var idx = 0; idx < arrProjectResObj.length; idx++) {
strOptionHtml += '<option value="'
+ arrProjectResObj[idx].ID
+ '" data-status='
+ arrProjectResObj[idx].COST
+ ' title="'
+ JSON.stringify(arrProjectResObj[idx]).replace(
/\"/g, "'") + '">'
+ arrProjectResObj[idx].VALUE + '</option>';
}
var strInitSelectFld = this.strInitSelectFld.replace(
'REPLACE_OPTIONAL_HTML', strOptionHtml);

jQuery("#custpage_projectdropdown").replaceWith(
strInitSelectFld);

this.bindSelectEvent();
};

/**
* Bind Selection Operation <br>
* Note: Only used in Client Side
*/
DROPDOWN.prototype.bindSelectEvent = function () {

jQuery(document).ready(
function () {
jQuery("#custpage_projectdropdown").change(
function () {

// alert(jQuery(this).val());
nlapiSetCurrentLineItemValue('recmachcustrecord_pri_return_mgr_replc_parent', RETMGRLIB.REC_RETURNMGR_REPLC_LINE.VENDOR,
jQuery(this).val());

// var strStatus = jQuery(this).find(
// ":selected").data("status");
});
});
};

/**
* Initial Option HTML, can use in server side
*
* @param {integer}
* intDefSelectVal Default Contact record type Id
* @param {string}
* strUserEventType User Event Type
* @returns {string}
*/
DROPDOWN.prototype.initOptionHtml = function (
intDefSelectVal, strUserEventType) {

// if (!this.intItemId)
// return '';

var arrProjectResObj = this.arrProjectResObj ? this.arrProjectResObj : this.lookupVendorRecords();

var strOptionHtml = '';
for (var idx = 0; idx < arrProjectResObj.length; idx++) {
if (intDefSelectVal
&& arrProjectResObj[idx].ID == intDefSelectVal)
strOptionHtml += '<option selected="selected" value="'
+ arrProjectResObj[idx].ID
+ '" data-status='
+ arrProjectResObj[idx].COST
+ ' title="'
+ JSON.stringify(arrProjectResObj[idx])
.replace(/\"/g, "'") + '">'
+ arrProjectResObj[idx].VALUE + '</option>';
else
strOptionHtml += '<option value="'
+ arrProjectResObj[idx].ID
+ '" data-status='
+ arrProjectResObj[idx].COST
+ ' title="'
+ JSON.stringify(arrProjectResObj[idx])
.replace(/\"/g, "'") + '">'
+ arrProjectResObj[idx].VALUE + '</option>';
}

var strInitSelectFld = this.strInitSelectFld;
if (strUserEventType == 'view')
strInitSelectFld = strInitSelectFld.replace(
'<select id=\"custpage_projectdropdown\"',
"<select id=\"custpage_projectdropdown\" disabled");

strInitSelectFld = strInitSelectFld.replace(
'REPLACE_OPTIONAL_HTML', strOptionHtml);

return strInitSelectFld;
};

return {
DROPDOWN: DROPDOWN
};

});
  • bindSelectEvent,当完成了HTML展示动态更新的下拉子列表后,绑定新的点击事件;当用户选取新的列表选项后,我们将值传递给另一个系统的字段(或者其他的商业逻辑定义)

衍生阅读

来吧,约个时间当面谈,我很乐意效劳,有什么NetSuite系统定制开发中的疑难杂症我可以帮忙的吗?

image-20240619215304248

随手记录 - 如何下载网页中视频

image-20240615213247707

Firefox插件名称和版本信息

image-20240615213422690