This article is relevant if you are looking to understand how to develop a NetSuite Suitelet and would like to understand the Model View Controller pattern.
Background
Approximately a year ago, I wrote an article about to code a SuiteLet using the Model View Controller pattern. Upon contemplation of that article, I recognized that the code itself might be difficult to understand. In the back of my mind, I thought I would like to create an even simpler example to help learn from.
More recently, I needed to create a quick utility for our team to lookup cases. In our practice, we built a tool called “Prolecto Task Manager” (PTM) which is effectively a completely different way to use NetSuite Cases. In this model, we wanted to expose case-like functionality to individuals that did not have standard permissions (such as our clients). For our internal team, we needed a quick way to lookup PTMs (cases), but we had not exposed that in our native application. So I went to work to code it up.
The Simple Lookup Application
Just as NetSuite provides a global keyword search, we too needed a simple keyword search that would be isolated to our PTMs (cases) and provide hyperlinks to our specific PTM records. Thus, the simple app does the following:
- Single Lookup Field: users enter keywords
- Sortable Resultlist: return the results sortable with links that direct to the specific PTM (case)
Thus, we have a very easy to understand application that is great for learning SuiteLet development. At the same time, we can introduce the Model View Controller Pattern, which was talked about more thoroughly in the previous article. Click on images to see the basics of the application.
The NetSuite Controller SuiteLet Pattern
Below is the code for the Controller pattern. You can also click here to download the program.
//----------------------------------------------------------------------------------------------------------- // Data: 20200509 // Authors: Marty Zigman, Principal, Prolecto Resources, Inc. // Application: PTM Search Suitelet acting as a controller // Purpose: A lookup PTMs (cases) using global search as a stand alone suitlet //----------------------------------------------------------------------------------------------------------- /** * @NApiVersion 2.x * @NScriptType Suitelet * @NModuleScope Public */ define( ['./ptm-search-model-20', './ptm-search-view-20'], function (model, view) { function onRequest(context) { log.debug({title:'Controller onRequest Method', details: context.request.method}); var params = context.request.parameters var m = model.load(params); var v = view.load(m); context.response.writePage(v.form) } return { onRequest: onRequest }; });
The NetSuite Model SuiteLet Pattern
Below is the code for the Model pattern. You can also click here to download the program.
//----------------------------------------------------------------------------------------------------------- // Data: 20200509 // Authors: Marty Zigman, Principal, Prolecto Resources, Inc. // Application: PTM Search Model // Purpose: take input and use global search to find PTMs (case records) //----------------------------------------------------------------------------------------------------------- /** * @NApiVersion 2.x * @NModuleScope Public */ define(['N/search'], function(search) { //start function function entry(params){ log.debug({title:'model entry params', details: JSON.stringify(params)}); return new Model(params); } //build the model function Model(params){ this.input = params.input if (!params.input){ this.data = []; } else { this.data = lookupPTM(params.input); } } //perform the lookup function lookupPTM(input){ log.debug({title:'model lookupPTM', details: 'value of input: ' + input}); if (input){ // note, 'ptm:' is the alias keyword used in the NetSuite account for 'cases' return search.global({ keywords: 'ptm:' + input }); } else { return [] } } // Entry Points return { load : entry, }; // Return Entry Points } // Function ); // Define
The NetSuite View SuiteLet Pattern
Below is the code for the View pattern. You can also click here to download the program.
//----------------------------------------------------------------------------------------------------------- // Data: 20200509 // Authors: Marty Zigman, Principal, Prolecto Resources, Inc. // Application: PTM Search View // Purpose: given a model, build a presentation view //----------------------------------------------------------------------------------------------------------- /** * @NApiVersion 2.x * @NModuleScope Public */ const PTMLINK = '/app/site/hosting/scriptlet.nl?script=783&deploy=1&compid=700889&mode=detail&unlayered=F&taskid='; define(['N/error', 'N/ui/serverWidget'], function(error, ui) { //expecting a model, even if empty function entry(model){ if (!model){ log.error({title:'view entry', details: 'no model passed'}); error.create({ name: 'model view entry', message: 'no model passed', notifyOff: true }); } return new View(model); } function View(model){ if (model){ log.debug({title:'view View', details: 'value of model.input: ' + model.input}); } else { log.debug({title:'view View', details: 'no model available'}); } this.form = createForm(model); } function createForm(model){ //create the form var objForm = ui.createForm({ title : 'PTM Lookup Utility', hideNavBar: false }); if (!model){ return objForm; } objForm.addSubmitButton({ label : 'Lookup' }); //create the header elements var lookupGroup = [] var t = { col : 'input', type : ui.FieldType.TEXT, displaytype : ui.FieldDisplayType.NORMAL, label : 'Keywords', value : model.input } lookupGroup.push(t) //this approach is overkill for a single value array lookupGroup.forEach(function(f){ var fld = objForm.addField({ id: f.col, type: f.type, label: f.label }).updateDisplayType({ displayType: f.displaytype }); }); //add sublist; no tab required var objSublist = objForm.addSublist({ id : 'sublist', type : ui.SublistType.LIST, label : 'Results' }); if (!isEmpty(model.data)){ //create result set list objSublist.addField({ id : 'client', label : 'Client', type : ui.FieldType.TEXT }).updateDisplayType({ displayType: ui.FieldDisplayType.INLINE }); objSublist.addField({ id : 'ptm', label : 'PTM', type : ui.FieldType.TEXTAREA }).updateDisplayType({ displayType: ui.FieldDisplayType.INLINE }); objSublist.addField({ id : 'incident', label : 'Incident', type : ui.FieldType.DATE }).updateDisplayType({ displayType: ui.FieldDisplayType.INLINE }); //add the data model.data.forEach(function(row, i){ //to work with this data, we must stringify and parse it var o = JSON.parse(JSON.stringify(row)) objSublist.setSublistValue({ id : 'client', value : o.values.info2, line : i }); objSublist.setSublistValue({ id : 'ptm', value : '<a target=_blank class=dottedlink href=' + PTMLINK + o.id + '>'+ o.values.name + '</a>', line : i }); objSublist.setSublistValue({ id : 'incident', value : o.values.info1, line : i }); objSublist.label = i + 1 + ' Results'; }); } return objForm; } // Entry Points return { load : entry }; // Return Entry Points } // Function ); // Define
Work with NetSuite Professionals
My hope is that all of us learn to wield the power of the NetSuite development platform. The capacity to invent and solve for challenges is one of my most favorite notions when offering expertise to help our clients realize the value of their NetSuite investment.
If you found this article valuable, feel free to sign up for new articles as I post them. If you are looking to work with a team that holds high standards for care and values NetSuite leadership, let’s have a conversation.