This topic contains 7 replies, has 0 voices, and was last updated by ironside 8 years ago.
-
AuthorPosts
-
November 4, 2016 at 4:30 pm #5988
nic kcdI have been wanting to become more OO. I develop a little in NetSuite and their APIs are in JS. I have the code below. Here is what is does.
1) search APIs does search on all transactions that are on the water.
2) Loops through all of the transactions and then searches for everyone of these transactions it does another search on them. The reason is because NetSuite’s API governance only allows a 1000 lines with search APIs (this can be worked around other ways as well). Also want to do it this way because will be working in other logic that will make this way necessary.
3) Then pushes these values, item, quantity, created from record into three arrays.
4) The last loop loops through the three arrays, but then prints out the item and the created from transaction the number of times of the quantity value. This is because we are going to print these as labels for each item.
Now have been wanting to become more OO. I did another thing similar where I looped through object with keys and values. Thought that was neat. My question is how would you make this more OO. I have some ideas on what I would do but would like to hear some ideas.
1) Create some functions that will call step 3 and step 4. So each time it looped it called functions
2) I would like to do something with callbacks or promises. Might not be the use for them in this scenario, but
3) Push the items into an object then run forEach method on those objects.
So the question is how to make this more OO style with JavaScript.
PHP Code:
// search APIs
filters = [];
filters[0] = new nlobjSearchFilter(‘location’, null, ‘anyof’, [’23’,’25’,’20’,’24’]);
filters[1] = new nlobjSearchFilter(‘mainline’, null, ‘is’, ‘true’);var columns = [];
columns[0] = new nlobjSearchColumn(‘tranid’);
columns[1] = new nlobjSearchColumn(‘createdfrom’);var searchResults =[];
var searchResults = nlapiSearchRecord(‘itemreceipt’, null, filters, columns);tranId = [];
createdFrom = [];
quantity = [];
item = [];
data = ”;if(searchResults){
for (var i = 0; i This is a cached copy. Click here to see the original post. -
November 5, 2016 at 8:11 am #5989
erictgrubaughRather than focus on being more “OO”, I would suggest focusing on being more “JS”. JS doesn’t have typical “OO” concepts from Class-oriented languages like Java or C#, nor does it need them, IMO.
I usually approach this searching use case like so (assuming SuiteScript 1.0):I break my functionality into separate files: one for each script type entry point I need, and one or more to contain the business logic.
Each file contains the appropriate logic wrapped in an immediately-invoked function expression to provide some namespacing and encapsulation.
Each individual search I need is always isolated in its own function. The function merely performs the search and returns the results; it does no processing on the results. The function might page through the results and concatenate them all in a single array, if necessary.
A separate function is always responsible for receiving the results and translating them into native JS Objects.
My business logic files do not call any NetSuite APIs; they only work on native JS Objects/primitives/functions. This not only makes the code clearer, but it also has the handy side effect of making that code unit-testable with npm+mocha without having to try and mock the SuiteScript API.
I will typically use third-party libraries like ramda or lodash for a more functional style when manipulating my data structures. I’ll avoid that here as it may not be your style and may just muddle things up a bit.
Taking a step back and looking at your approach, I’m not sure you need to be doing two searches. You should be able to get all the information you need from a single search. While it may need to be paged to get more than 1000 results, you shouldn’t need two sets of filters/columns. You can leverage the “lineuniquekey” field to page through the results of line items.I’ll pretend you’re doing this search in a Scheduled Script. In this case I don’t know enough about what you want to do, so I just put everything in one file. Here’s how I might tackle it:
PHP Code:
// aScheduledScript.js
// _onWater namespace
var _onWater = _onWater || {};
var _onWater.scheduled = (function () {
// Scheduled Script entry point
function execute(type) {
var quantity = [];
var createdFrom = [];
var data = [];
data = findItemsOnWater().map(resultToData);
// At this point you have a flat list of item receipt line items as JS objects
// which you can proceed to process however you wish
// e.g. How many items are on the water?
var countOnWater = data.reduce(function (total, curr) {
return total + curr.quantity;
}, 0);
// e.g. What are all the items on the water? (would include duplicates)
var itemsOnWater = data.map(function (n) {
return n.itemName;
});
}
/**
* Search for all item receipt lines currently on the water
*/
function findItemsOnWater() {
var page = [];
var results = [];
var lastKey = 0;
var filters = [
[“mainline”, “is”, “F”], “AND”,
[“cogs”, “is”, “F”], “AND”,
[“taxline”, “is”, “F”], “AND”,
[“shipping”, “is”, “F”], “AND”,
[“location”, “anyof”, [“23″,”25″,”20″,”24”]], “AND”,
[“lineuniquekey”, “greaterthan”, lastKey]
];
var columns = [
new nlobjSearchColumn(‘item’),
new nlobjSearchColumn(‘tranid’),
new nlobjSearchColumn(‘createdfrom’),
new nlobjSearchColumn(‘quantity’),
new nlobjSearchColumn(‘lineuniquekey’).setSort(false)
];do {
page = nlapiSearchRecord(‘itemreceipt’, null, filters, columns) || [];
results.concat(page);
// Update the lineuniquekey filter
filters.pop();
lastKey = R.last(page).getValue(“lineuniquekey”);
filters.push([“lineuniquekey”, “greaterthan”, lastKey]);
} while (page.length === 1000);
return results;
}
/**
* resultToData :: nlobjSearchResult -> Object
*
* Translates a single search result into a native JS object
*/
function resultToData(result) {
return {
“createdFrom”: result.getValue(“createdfrom”),
“createdFromName”: result.getText(“createdfrom”),
“item”: result.getValue(“item”),
“itemName”: result.getText(“item”),
“quantity”: parseFloat(result.getValue(“quantity”)),
“tranid”: result.getValue(“tranid”)
};
}
return {
“execute”: execute
}
})();FWIW SuiteScript 2.0 does make the paging a bit easier by providing a paging API in the N/search module.
Some great resources I’ve found over the years for JS development patterns:https://addyosmani.com/resources/ess…patterns/book/
https://addyosmani.com/writing-modular-js/
http://shop.oreilly.com/product/0636920025832.do
https://leanpub.com/javascriptallongesix/read
ironside replied on 11/08/2016, 07:53 AM: JS certainly does have OO constructs such as classes. It has officially for a year now (EMCAScript 2015).
-
November 5, 2016 at 5:15 pm #5990
ironsideNFT-SS2: If you’re interested in a more OO approach to javascript (aka TypeScript aka current standard Javascript ECMAScript 2015) you might want to take a look.
nic kcd replied on 11/09/2016, 06:34 PM: Looks cool. I am getting an error when I try to install. I reported it to https://github.com/npm/npm/issues/14…ment-259490233. Do you have any insight into this error?
-
November 7, 2016 at 9:10 am #5991
nic kcderictgrubaugh This is super helpful. Having something I am working on transformed like this has been eye-opening. The pattern for dealing with search results has been a great help too. As always you are the man.
I am going to play around and do some more research myself, but I have some follow-up questions and assumptions I have made I commented below.
PHP Code:
data = findItemsOnWater().map(resultToData);
So this data takes the function findItemsOnWater which is basically runs the search API for every 1000 results. The results are returned. The results returned are going to be an array of arrays, with an array for every 1000 chunk . This is why you used map over the results, opposed to just calling another function from the results, you used map method so it would go over each array?
PHP Code:
var countOnWater = data.reduce(function (total, curr) {
return total + curr.quantity;
}, 0);Right now var data = all of the array with objects. The objects containing the values and text of the columns. Little uncertain what the two parameters, where they came from and what reducing doing? Is it essentially just turning it into one object? Is the 0 just to return 0 if data hasn’t been evaluated yet?
PHP Code:
var itemsOnWater = data.map(function (n) {
return n.itemName;
});I think this is the point where that makes it possible to print out all of the items repeatedly. I’ll have to think on this a little more.
PHP Code:
return {
“execute”: execute
}
}) ();This is returning an object with key and value of execute, not really sure why this is. Also the lone (). Not really sure what is going on here.
P.S. thanks for the sources too and feedback!
-
November 7, 2016 at 9:48 am #5992
erictgrubaughAlways happy to help.
I’ll try to hit all your follow-ups here. LMK if I miss something.
Grab a cup of coffee, or a beer, or maybe both, because this gets lengthy =) Here we go.
reducers
Let’s talk about `reduce` first. A `reducer` is a function that transforms a list of items into a single value by iterating over the list and continually updating an accumulated value. In this case, we’re using `reduce` to transform the `data` list into a single sum: the sum of all the `quantity` values in the list. The first parameter of the reducer function is called the `accumulator`, which is basically the running sum of quantities as the reducer iterates over the list, while `curr` is the current element in the list.
The 0 is just the initial value of our accumulator. When we hit the very first element, `total` is basically undefined, so we need to initialize it to something. Since we’re calculating a sum here, we want our initial value to be 0. Thus, for the first element in our list, we are essentially doing `return 0 + curr.quantity;`
Here’s the MDN reference page for `reduce`: https://developer.mozilla.org/en-US/…s/Array/Reduce
Processing the Search Results
Actually, `data` will not be an Array of Arrays; it will be a flat array of *all* the search results, no matter how many there are (provided you don’t run out of governance).
For example, `[1,2,3].concat([4,5,6]);` returns `[1,2,3,4,5,6]`, not `[[1,2,3],[4,5,6]]`.
`map` just iterates over the list and transforms each element into something else. In our case, we’re using `map` and `resultToObject` to transform each `nlobjSearchResult` instance in the list to a corresponding native JS Object. So `data` ends up being a flat list of JS Objects, not search result instances.
In general, I use `map` because I personally have an irrational burning hatred of looping structures. Whenever I see a for- or while-loop, I do everything I can to replace it with a combination of `Array.map`, `Array.reduce`, and `Array.filter`. There are times when a loop can’t be avoided, like in `findItemsOnWater`, but I try to make that the exception, not the rule.
Modular Syntax
To understand the module structure, you need to understand the “Revealing Module” pattern, about which you can find a ton of detail in those extra resources I provided, way more detail than I can give here. Basically, what we end up with when NetSuite evaluates this script is an `_onWater.scheduled` object that contains an `execute` method. This is the method we want to use as our entry point, so in our Scheduled Script record, we would set the “Scheduled Function” to “_onWater.scheduled.execute”.
I use this pattern for namespacing and isolating my 1.0 code; it is particularly important to do this with 1.0 Client Scripts, otherwise you get function collisions if you have, for instance, two different client scripts with a function called `pageInit`. Only the last one will survive as it will overwrite all previous definitions of `pageInit`. If you namespace them, you avoid this collision.
IIFEs
The “lone ()” is used because we are defining `_onWater.scheduled` with an “Immediately-Invoked Function Expression” (IIFE):
PHP Code:
var _onWater.scheduled = (function () {
It’s a little easier to see if you basically rip all the code out of the function:
PHP Code:
var _onWater.scheduled = (function () {
// code
})();Note the parens that wrap the entire function definition as well. They’re not necessary, but it’s a commonly-used pattern that indicates this is an IIFE.
Basically we are defining an anonymous function and then calling it immediately. Whatever this anonymous function returns will be immediately stored in _onWater.scheduled. You can test this out in the browser console by doing something like:
PHP Code:
(function (a, b) { return a + b; })(5, 3)
This is equivalent to writing something like:
PHP Code:
function sum(a,b) { return a + b; }
sum(5,3);
There’s a really good definition of IIFEs here: http://benalman.com/news/2010/11/imm…on-expression/
-
November 9, 2016 at 8:38 am #5993
nic kcdReally good stuff, I am really grateful for the time you took to write all of this up. I am looking forward to researching the sources you have provided.
I also received this answer on SO. Would just like to offer it for other people trying to learn about different design patterns.
PHP Code:
var itemsToProcess = getItemsToProcess();
var data = getDataString(itemsToProcess);//**** HELPER FUNCTIONS ****//
function getItemsToProcess(){
// search APIs
var filters = [];
filters.push(new nlobjSearchFilter(‘location’, null, ‘anyof’, [’23’,’25’,’20’,’24’]));
filters.push(new nlobjSearchFilter(‘createdfrom’, null, ‘isnotempty’));
filters.push(new nlobjSearchFilter(‘quantity’, null, ‘greaterthan’, 0));
filters.push(new nlobjSearchFilter(‘mainline’, null, ‘is’, ‘F’));
filters.push(new nlobjSearchFilter(‘shipping’, null, ‘is’, ‘false’));
filters.push(new nlobjSearchFilter(‘cogs’, null, ‘is’, ‘false’));
filters.push(new nlobjSearchFilter(‘taxline’, null, ‘is’, ‘false’));var columns = [];
columns.push(new nlobjSearchColumn(‘item’));
columns.push(new nlobjSearchColumn(‘createdfrom’));
columns.push(new nlobjSearchColumn(‘quantity’));var searchResults = fullSearch(‘itemreceipt’, filters, columns);
var rows = [];
for(var i in searchResults){
var result = {};
result.item = searchResults[i].getText(columnsLine[0]);
result.createdFrom = searchResults[i].getText(columnsLine[1]);
result.quantity = searchResults[i].getValue(columnsLine[2]);
rows.push(result);
}
return rows;//**** HELPER FUNCTIONS ****//
function fullSearch(type, filters, columns){
var search = nlapiCreateSearch(type, filters, columns);
var resultset = search.runSearch();
var resultsets = [];
var returnSearchResults = [];
var searchid = 0;
var startdate, enddate, resultslice;
/* Grabs results first */
do {
resultslice = getResultSlice(resultset, searchid);
for (var rs in resultslice) {
returnSearchResults.push(resultslice[rs]);
searchid++;
}
} while (resultslice.length == 1000);return returnSearchResults;
//*********** HELPER FUNCTION ***********/
function getResultSlice(resultset, searchid){
var resultslice = resultset.getResults(searchid, searchid + 1000);
return resultslice || [];
}
}}
function getDataString(itemsToProcess){
var data = ”;
for(var i in itemsToProcess){
data += printItem(itemsToProcess[i].item, itemsToProcess[i].createdFrom, itemsToProcess[i].quantity);
}
return data;//**** HELPER FUNCTIONS ****//
function printItem(item, createdFrom, quantity){
var tempString = ”;
for (var x = 0; x < quantity; x++){
console.log(item + ' ' + createdFrom.substring(16));
tempString += item + createdFrom + 'n';
}
return tempString;
}
}
erictgrubaugh replied on 11/09/2016, 08:58 AM: Just make sure you’re careful using for…in loops on Arrays instead of Objects. See https://developer.mozilla.org/en-US/…ments/for…in
-
November 10, 2016 at 7:26 pm #5994
CREECEBe careful if you are using Array methods and you want to use nlapiYieldScript. It does not support using nlapiYieldScript within array methods which is something I just learned this week. I’m not sure about how SS 2.0 handles this.
-
November 10, 2016 at 9:02 pm #5995
ironsideOriginally posted by erictgrubaugh
View Post
/sigh Yes, you are correct as always ironside. However, Rhino 1.7 (NetSuite’s JS interpreter) doesn’t support any of ES2015 natively, so I find that trying to teach that on top of SuiteScript just muddies the waters for those looking to understand the fundamentals of NetSuite development. If they choose to look for alternatives like NFT-SS2 that do enable that support, they can investigate further at that point.
Good point – I admit Eric my response was a bit of venting that NS is still on Rhino 1.7 (ES5) with no upgrade in sight.
erictgrubaugh replied on 11/11/2016, 08:52 AM: I hear you on that frustration. I want my destructuring operator and arrow functions so badly!
-
AuthorPosts
You must be logged in to reply to this topic.