I am trying to upload a Map/reduce type script to netsuite following a suitescript 2.0 training guide. I am receiving the following error: "SuiteScript 2.0 entry point scripts must implement one script type function."
I'm using the getInputData() and map() functions. Returning a reference object pointing to a saved search. Then extracting and logging the context value and the parsed context value (comparing json strings and js objects in the lesson).
Anyone see a syntax error, know what I might be missing, or what I can test for?
Code:
/**
* #NApiVersion 2.x
* #NScriptType MapReduceScript
*/
define(['N/search']),
function(search) {
function getInputData() {
return { type: 'search', id: 'customsearch_iii_payments' };
}
function map(context) {
var jsonResult = context.value
var searchResult = JSON.parse(context.value);
log.debug('JSON result' + jsonResult);
log.debug('Search Result' + searchResult);
}
return {
getInputData: getInputData,
map: map
}
}
It was a netsuite specific syntax error my linter didn't catch. My script definition wasn't wrapping the entire script, just the module declarations.
Working Code:
/**
* #NApiVersion 2.x
* #NScriptType MapReduceScript
* #NModuleScope SameAccount
*/
define(['N/search'],
function(search) {
function getInputData() {
return { type: 'search', id: 'customsearch_iii_payments' };
}
function map(context) {
var jsonResult = context.value
var searchResult = JSON.parse(context.value);
log.debug('JSON result' + jsonResult);
log.debug('Search Result' + searchResult);
}
return {
getInputData: getInputData,
map: map
}
});
Also check the #NScriptType notation, in case you have ScheduleScript, netsuite will expect you to have a function called ¨execute¨ on the return object no matter if the syntax is correct.
I found the issue for me was that my script referenced local files which I hadn't yet uploaded.
Upload other local files before creating a script record.
double check for the require vs define keyword in the main method definition. 2.X ScheduledScript use define
Related
This is a follow up to the successful call using Netsuite Token Based Authentication (TBA) REST webservice,
I would like to get some guidance on how to create a NEW ENTRY RECORD.
Here is my custom type record entry list (please see screenshot)
https://gist.github.com/axilaris/4386c3537d04737d3775c156562b7545 <-- here is the python code for the TBA that has worked successful. I would like to know how to construct the next step on how to create a new entry.
This is a custom record with an ID like this customrecord1589
FYI - here is my other question on query Netsuite - REST API - Making query with Token Based Authentication (TBA) - (in Python)
But this question would be creating a new entry record
Within your restlet you need to use the N/record module to create a new custom record, here is what is should look similar to:
/**
* #NApiVersion 2.1
* #NScriptType Restlet
*/
define(["N/log", "N/record"], function (log, record) {
function post(context) {
return JSON.stringify(createCustomRecord(context));
}
function createCustomRecord(context) {
let success = true;
try {
let custRec = record.create({
type: "customrecord1589",
isDynamic: true,
});
//Set one or more fields here
custRec.setValue({
fieldId: "custrec123",
value: context.custrec123,
});
custRec.save();
} catch (e) {
log.error("Error creating record", e);
success = false;
}
return { success: success };
}
return {
post: post,
};
});
I'm trying to get the values from two transaction body field using this code below .
/**
*#NApiVersion 2.x
*#NScriptType UserEventScript
*#param {Record} context.currentRecord
*/
define(['N/record'],
function (msg) {
function beforeSubmit(context) {
try {
var record = context.currentRecord;
var createdDate = record.getValue({
fieldId: 'createddate'
});
var dataNecessidade = record.getValue({
fieldId: 'custbodyek_data_nece_requ_po'
});
console.log(createdDate ,dataNecessidade);
}
catch(ex){
log.error(ex);
}
}
return {
beforeSubmit : beforeSubmit,
};
});
The error raised is "TypeError: Cannot call method "getValue" of undefined"
What I'm doing wrong here?
Thank you!
There is no currentRecord property on the context passed into a user event, hence the error message telling you that record is undefined. Review the docs for the beforeSubmit entry point to find the appropriate values.
On SuiteScript 2 each entry point has different parameters so you need to check those parameters on the Help or if you use an IDE like Eclipse, you will get that information when you create a new script, so for a UserEvent script and the beforeSubmit entry point, you will get something like this:
/**
* Function definition to be triggered before record is loaded.
*
* Task #5060 : calculate PO Spent Amount and Balance in realtime
*
* #param {Object} scriptContext
* #param {Record} scriptContext.newRecord - New record
* #param {Record} scriptContext.oldRecord - Old record
* #param {string} scriptContext.type - Trigger type
* #Since 2015.2
*/
and then you can see that the context parameter doesn't have a currentRecord property, instead, it has two other parameters that you can use newRecord or oldRecord so your code can be like this:
/**
*#NApiVersion 2.x
*#NScriptType UserEventScript
*#param {Record} context.currentRecord
*/
define(['N/record'],
function (msg) {
// are you aware that you are "injecting" the 'N/record' library into the 'msg' variable ???
function beforeSubmit(context) {
try {
var record = context.newRecord;
var createdDate = record.getValue({
fieldId: 'createddate'
});
var dataNecessidade = record.getValue({
fieldId: 'custbodyek_data_nece_requ_po'
});
console.log(createdDate ,dataNecessidade);
}
catch(ex){
log.error(ex);
}
}
return {
beforeSubmit : beforeSubmit,
};
});
You try to write it like this, I always use this method to get the field value.
const bfRecord= context.newRecord;
const createdDate = bfRecord.getValue('createddate');
I am working on processing meta data information of my Indesign document links, using ExtentdScript.
I want to convert the object to string using JSON.stringify but when I use it, I am getting error saying:
can't execute script in target engine.
If I remove linkObjStr = JSON.stringify(linksInfObj); from below code, then everything works fine.
What is the equivalent to JSON.stringify in ExtendScript, or is there any other possibilities to display linksInfObj with its proper contents instead [object object]?
for (var i = 0, len = doc.links.length; i < len; i++) {
var linkFilepath = File(doc.links[i].filePath).fsName;
var linkFileName = doc.links[i].name;
var xmpFile = new XMPFile(linkFilepath, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_READ);
var allXMP = xmpFile.getXMP();
// Retrieve values from external links XMP.
var documentID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'DocumentID', XMPConst.STRING);
var instanceID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'InstanceID', XMPConst.STRING);
linksInfObj[linkFileName] = {'docId': documentID, 'insId': instanceID};
linkObjStr = JSON.stringify(linksInfObj);
alert('Object' + linksInfObj, true); // I am getting [Object Object] here
alert('String' + linkObjStr, true);
}
ExtendScript does not include a JSON object with the associated methods for parsing, namely JSON.parse() and JSON.stringify(). Nor does it provide any other builtin feature for parsing JSON.
Solution:
Consider utilizing a polyfill to provide JSON functionality such as JSON-js created by Douglas Crockford.
What you'll need to do:
Download the JavaScript file named json2.js from the Github repo and save it in the same location/folder as your .jsx file.
Note You can just copy and paste the raw version of json2.js from the same Github repo to create the json2.js file manually if you prefer.
Then at the top of your current .jsx file you'll need to #include the json2.js file by adding the following line of code:
#include "json2.js";
This is analogous to how you might utilize the import statement to include a module in modern day JavaScript (ES6).
A pathname to the location of the json2.js can be provided if you decide to save the file in a different location/folder than your .jsx file.
By including json2.js in your .jsx file you'll now have working JSON methods; JSON.parse() and JSON.stringify().
Example:
The following ExtendScript (.jsx) is a working example that generates JSON to indicate all the links associated with the current InDesign document (.indd).
example.jsx
#include "json2.js";
$.level=0;
var doc = app.activeDocument;
/**
* Loads the AdobeXMPScript library.
* #returns {Boolean} True if the library loaded successfully, otherwise false.
*/
function loadXMPLibrary() {
if (!ExternalObject.AdobeXMPScript) {
try {
ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
} catch (e) {
alert('Failed loading AdobeXMPScript library\n' + e.message, 'Error', true);
return false;
}
}
return true;
}
/**
* Obtains the values f XMP properties for `DocumentID` and `instanceID` in
* each linked file associated with an InDesign document (.indd). A returns the
* information formatted as JSON,
* #param {Object} doc - A reference to the .indd to check.
* #returns {String} - The information formatted as JSON.
*/
function getLinksInfoAsJson(doc) {
var linksInfObj = {};
linksInfObj['indd-name'] = doc.name;
linksInfObj.location = doc.filePath.fsName;
linksInfObj.links = [];
for (var i = 0, len = doc.links.length; i < len; i++) {
var linkFilepath = File(doc.links[i].filePath).fsName;
var linkFileName = doc.links[i].name;
var xmpFile = new XMPFile(linkFilepath, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_READ);
var allXMP = xmpFile.getXMP();
// Retrieve values from external links XMP.
var documentID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'DocumentID', XMPConst.STRING);
var instanceID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'InstanceID', XMPConst.STRING);
// Ensure we produce valid JSON...
// - When `instanceID` or `documentID` values equal `undefined` change to `null`.
// - When `instanceID` or `documentID` exist ensure it's a String.
instanceID = instanceID ? String(instanceID) : null;
documentID = documentID ? String(documentID) : null;
linksInfObj.links.push({
'name': linkFileName,
'path': linkFilepath,
'docId': documentID,
'insId': instanceID
});
}
return JSON.stringify(linksInfObj, null, 2);
}
if (loadXMPLibrary()) {
var linksJson = getLinksInfoAsJson(doc);
$.writeln(linksJson);
}
Output:
Running the script above will log JSON formatted something like the following example to your console:
{
"indd-name": "foobar.indd",
"location": "/path/to/the/document",
"links":[
{
"name": "one.psd",
"path": "/path/to/the/document/links/one.psd",
"docId": "5E3AE91C0E2AD0A57A0318E078A125D6",
"insId": "xmp.iid:0480117407206811AFFD9EEDCD311C32"
},
{
"name": "two.jpg",
"path": "/path/to/the/document/links/two.jpg",
"docId": "EDC4CCF902ED087F654B6AB54C57A833",
"insId": "xmp.iid:FE7F117407206811A61394AAF02B0DD6"
},
{
"name": "three.png",
"path": "/path/to/the/document/links/three.png",
"docId": null,
"insId": null
}
]
}
Sidenote: Modelling your JSON:
You'll have noticed that the JSON output (above) is structured differently to how you were attempting to structure it in your given example. The main difference is that you were using link filenames as property/key names, such as the following example:
Example of a problematic JSON structure
{
"one.psd": {
"docId": "5E3AE91C0E2AD0A57A0318E078A125D6",
"insId": "xmp.iid:0480117407206811AFFD9EEDCD311C32"
},
"two.jpg": {
"docId": "EDC4CCF902ED087F654B6AB54C57A833",
"insId": "xmp.iid:FE7F117407206811A61394AAF02B0DD6"
}
...
}
Producing JSON like this example isn't ideal because if you were to have two links, both with the same name, you would only ever report one of them. You cannot have two properties/keys that have the same name within an Object.
Edit:
As a response to the OP's comment:
Hi RobC, other than using #include 'json2.js', is there any other way to include external js file in the JSX file?
There are a couple of alternative ways as follows:
You could utilize $.evalFile(). For instance replace #include "json2.js"; with the following two lines:
var json2 = File($.fileName).path + "/" + "json2.js";
$.evalFile(json2);
Note: This example assumes json2.js resides in the same folder as your .jsx
Alternatively, if you're wanting to avoid the existence of the additional json2.js file completely. You could add a IIFE (Immediately Invoked Function Expression) at the top of your .jsx file. Then copy and paste the content of the json2.js file into it. For instance:
(function () {
// <-- Paste the content of `json2.js` here.
})();
Note: If code size is a concern then consider minifying the content of json2.js before pasting it into the IIFE.
I apply JavaScript Minifier to JSON-js
then put the result to my script.
In my app.js I have the following:
angular.module('app').controller('userList',
['appSettings'
,function (/*#type {app.appSettings}*/appSettings) {
appSettings.<<== it shows a list here but nothing from autocomplete.js
In my autocomplete.js I have the following (generated by JavaScript printing out my services and their members):
var app={};
app.appSettings={};
app.appSettings.userFailMessages={};
app.appSettings.userFailMessages.invalidJson
="Invalid request, user sent is not valid json.";
NetBeans refuses to code complete appSettings for me and doesn't seem to know it's defined in autocomplete.js. Maybe I'm getting my js doc wrong but tried a mix of combination of #var, #type and #param without success.
It code completes when I type app.appSettings. and gives me a list from autocomplete.js but I would like to know how I can tell NetBeans that the passed argument to the function is app.appSettings.
Maybe I should have autocomplete contain constructor functions instead of object literals as #type suggests a certain type and not an instance.
This is NetBeans 7.3.1
Was close to the answer, to have NetBeans use type you have to define the type. Then to indicate that the parameters passed to your angular module (or any function) are of a certain type I use the #param jsdoc
The angular module:
angular.module('app').controller('userList'
, ['$scope','appRules','appSettings'
,/**
* #param {app.appRules} appRules
* #param {app.appSettings} appSettings
* */
function ($scope,appRules,appSettings,$timeout) {
//<== here both appRules and appSettings give suggestions
// from autocomplete
autocomplete.js (not included in my html file but just there for code suggest)
/*#typedef {Object} app*/
var app={};
app.appRules={};
app.appRules.userIsInRole=function (user,role){};
app.appRules.general={};
app.appRules.general.isEmpty=function (val){};
app.appRules.general.isEmail=function (val){};
app.appSettings={};
app.appSettings.userFailMessages={};
app.appSettings.userFailMessages.invalidJson
="Invalid request, user sent is not valid json.";
app.appSettings.userFailMessages.noPrivilege
="You do not have the privileges needed to change this user.";
I ran the following code in the console on a page that contains my app to generate autocomplete.js:
var inj;
function allServices(mod, r) {
if (!r) {
r = {};
inj = angular.element(document.querySelector('[data-ng-app]')).injector().get;
}
angular.forEach(angular.module(mod).requires, function(m) {
allServices(m, r)
});
angular.forEach(angular.module(mod)._invokeQueue, function(a) {
try {
r[a[2][0]] = inj(a[2][0]);
} catch (e) {
}
});
return r;
};
var output=[];
function addOutput(names,prop){
if(names.length===1){
output.push('var ');
}
output.push(names.join('.'));
if(typeof prop === 'object'){
output.push('={};\n');
for(thing in prop){
//TODO: no arrays or route paths
if(/[0-9\/\\]/.test(thing)){
continue;
}
names.push(thing);
addOutput(names,prop[thing]);
}
}else{
output.push('=');
output.push(
(typeof prop === 'function')?
prop.toString():
JSON.stringify(prop)
);
output.push(';\n');
}
names.pop();
}
function createOutput(){
allMyServices = allServices('app');
addOutput(['app'],allMyServices);
console.log(output.join(''));
}
createOutput();
I'm using the library provided here successfully in several tests, but am now stuck trying to use asserters as per their docs as a passed function in the waitForElementByLinkText API method.
Here is where I've defined the Asserter:
function Asserter(_assert){
this.assert = _assert;
}
/**
* asserters.isVisible
*
* #asserter
*/
var isDisplayed = new Asserter(
function(el,cb) {
el.isDisplayed(function(err, displayed) {
if(err) { return cb(err); }
cb(null, displayed);
});
}
);
module.exports = {
Asserter: Asserter,
isDisplayed: isDisplayed
};
Then in my chained script, I am attempting to use it as follows, but the console.log executes before the element is visible:
.get('http://mydomain.com/mypage')
.elementByLinkText('Reset', function(err, el){
browser.next('clickElement', el, noop);
})
.waitForElementByLinkText('This is the link text', isDisplayed, 10000, 100, function(err){
console.log('The page has updated!');
})
I believe my code is using a deprecated version of the chaining syntax which is needed to support legacy code coming out of SeBuilder, but makes it hard to follow the samples which all use the new method.
You don't need to redefine commonly used asserters, please refer to the new example here:
https://github.com/admc/wd/blob/master/examples/deprecated/wait-for-simple.js
If you need more help, please provide some html/js sample, otherwise it's hard to figure out what you are actually trying to achieve.