Custom Module in SuiteScript 2.0 - javascript

I have a JavaScript library file that I use throughout my scripts when creating SuiteScripts for NetSuite. I'm trying to get myself all prepped on 2.0 and start pushing new projects to it and get away from SuiteScript 1.0. So, I transcribed my library file into 2.0. However, whenever I create a new script and have my library referenced, I always get the following message:
SuiteScript 2.0 entry point scripts must implement one script type function.
But, if I remove the reference from the script, it lets me load it up and create the script record. Then I can put the reference back and upload the revised script file without issue. I can't for the life of me understand why this happens. If it matters, here is the JSDoc header in the file:
/**
* my.library.v2.js
* #NApiVersion 2.x
* #NModuleScope Public
*/
A script would also start off like this after its own JSDoc header:
define(['N/error', 'N/record', 'N/runtime', 'N/search', './my.library.v2'],
function(error, record, runtime, search, myLib) {
function doStuff(context) { /* do a bunch of stuff */ }
return { pageInit : doStuff };
});
Along the same issue, I also have trouble with running some scripts. Everything works perfectly when applying this module to client scripts once I get beyond the previously mentioned issue. However, I cannot successfully get a server-side script to initiate with the library. I constantly get this error:
com.netsuite.suitescript.exception.NLServerSideScriptException: TypeError: Cannot call method "split" of undefined
What's throwing me off is that the library has no call of String.split() inside of it. And I have no way to log where the error may be originating from, as it is occurring when loading the module into the server script. And I know that's what's happening because this occurs regardless of the operation type and the scripts I create always have a condition to check for specific operation types before getting into the nitty gritty. For example, if I have it checking for a "print" operation, but the record is opened to "edit". I even have the entire function wrapped inside of a try/catch block.
I haven't had much luck in tracing the cause of these issues down, so I'm hoping that both isues stem from the same cause.

If you could post/provide your custom-module it would have helped alot to narrow down the issue, but as far as I can tell, you are including some module which is not available on the server-side scripts like N/currentRecord. This module is only available on client-scripts and will not let you load/create server-side scripts as you mentioned. So, if you remove those modules and try to update/create server-side scripts it should work.
Similarly, there are some modules which can only be used on server-side scripts like N/task module and would return error if you try to load/require them on client side scripts.

Ok, so there were errors on my behalf. One of my function definitions was missing "function". Since the functions are being added as properties of an object, I can see why it kept getting overlooked, if you aren't careful enough you eye just glides over the problem. There's also an issue with an array prototype, but I don't think the prototype is necessary for SuiteScript 2.0. It was developed for use with SuiteScript 1.0 due to a lot of handy object functions not yet having been implemented until a later JS version release than what is used for the server side engine in NetSuite.

Related

Is there a way to tell Google Closure Compiler to *NOT* inline my local functions?

Here's what I'm looking for:
I want to use the wonderful features of SIMPLE mode minification while disabling just one specific feature (disable local function inline).
UPDATE: The answer is NO, it's not possible given my setup. But for me there is a workaround given I am using Grails.
As #Chad has explained below, "This violates core assumptions of the compiler". See my UPDATE3 below for more info.
IN QUESTION FORM:
I'm using CompilationLevel.SIMPLE_OPTIMIZATIONS which does everything I want, except that it's inlining my local functions.
Is there any way around this? For example, is there a setting I can place in my JS files to tell Google Closure not to inline my local functions?
It would be cool to have some directives at the top of my javascript file such as:
// This is a JS comment...
// google.closure.compiler = [inlineLocalFunctions: false]
I'm developing a Grails app and using the Grails asset-pipeline plugin, which uses Google Closure Compiler (hereafter, Compiler). The plugin supports the different minification levels that Compiler supports via the Grails config grails.assets.minifyOptions. This allows for 'SIMPLE', 'ADVANCED', 'WHITESPACE_ONLY'.
AssetCompiler.groovy (asset-pipeline plugin) calls ClosureCompilerProcessor.process()
That eventually assigns SIMPLE_OPTIMIZATIONS on the CompilerOptions object. And by doing so, CompilerOptions.inlineLocalFunctions = true as a byproduct (this is hard coded behavior in Compiler). If I were to use WHITESPACE_ONLY the result would be inlineLocalFunctions=false.
So by using Asset Pipeline's 'SIMPLE' setting, local functions are being inlined and that is causing me trouble. Example: ExtJS ext-all-debug.js which uses lots of local functions.
SO post Is it possible to make Google Closure compiler *not* inline certain functions? provides some help. I can use its window['dontBlowMeAway'] = dontBlowMeAway trick to keep my functions from inlining. However I have LOTS of functions and I'm not about to manually do this for each one; nor would I want to write a script to do it for me. Creating a JS model and trying to identity local functions doesn't sound safe, fun nor fast.
The previous SO post directs the reader to https://developers.google.com/closure/compiler/docs/api-tutorial3#removal, where the window['bla'] trick is explained, and it works.
Wow thanks for reading this long.
Help? :-)
UPDATE1:
Okay. While spending all the effort in writing this question, I may have a trick that could work. Grails uses Groovy. Groovy makes method call interception easy using its MetaClass API.
I'm going to try intercepting the call to:
com.google.javascript.jscomp.Compiler.compile(
List<T1> externs, List<T2> inputs, CompilerOptions options)
My intercepting method will look like:
options.inlineLocalFunctions=false
// Then delegate call to the real compile() method
It's bed time so I'll have to try this later. Even so, it would be nice to solve this without a hack.
UPDATE2:
The response in a similar post (Is it possible to make Google Closure compiler *not* inline certain functions?) doesn't resolve my problem because of the large quantity of functions I need inlined. I've already explained this point.
Take the ExtJS file I cited above as an example of why the above similar SO post doesn't resolve my problem. Look at the raw code for ext-all-debug.js. Find the byAttribute() function. Then keep looking for the string "byAttribute" and you'll see that it is part of strings that are being defined. I am not familiar with this code, but I'm supposing that these string-based values of byAttribute are later being passed to JS's eval() function for execution. Compiler does not alter these values of byAttribute when it's part of a string. Once function byAttribute is inlined, attempts to call the function is no longer possible.
UPDATE3: I attempted two strategies to resolve this problem and both proved unsuccessful. However, I successfully implemented a workaround. My failed attempts:
Use Groovy method interception (Meta Object Protocol, aka MOP) to intercept com.google.javascript.jscomp.Compiler.compile().
Fork the closure-compiler.jar (make my own custom copy) and modify com.google.javascript.jscomp.applySafeCompilationOptions() by setting options.setInlineFunctions(Reach.NONE); instead of LOCAL.
Method interception doesn't work because Compiler.compile() is a Java class which is invoked by a Groovy class marked as #CompileStatic. That means Groovy's MOP is not used when process() calls Google's Compiler.compile(). Even ClosureCompilerProcessor.translateMinifyOptions() (Groovy code) can't be intercepted because the class is #CompileStatic. The only method that can be intercepted is ClosureCompilerProcessor.process().
Forking Google's closure-compiler.jar was my last ugly resort. But just like #Chad said below, simply inserting options.setInlineFunctions(Reach.NONE) in the right place didn't resurrect my inline JS functions names. I tried toggling other options such as setRemoveDeadCode=false to no avail. I realized what Chad said was right. I would end up flipping settings around and probably destroying how the minification works.
My solution: I pre-compressed ext-all-debug.js with UglifyJS and added them to my project. I could have named the files ext-all-debug.min.js to do it more cleanly but I didn't. Below are the settings I placed in my Grails Config.groovy:
grails.assets.minifyOptions = [
optimizationLevel: 'SIMPLE' // WHITESPACE_ONLY, SIMPLE or ADVANCED
]
grails.assets.minifyOptions.excludes = [
'**ext-all-debug.js',
'**ext-theme-neptune.js'
]
Done. Problem solved.
Keywords: minify, minification, uglify, UglifyJS, UglifyJS2
In this case, you would either need to make a custom build of the compiler or use the Java API.
However - disabling inlining is not enough to make this safe. Renaming and dead code elimination will also cause problems. This violates core assumptions of the compiler. This local function is ONLY referenced from within strings.
This code is only safe for the WHITESPACE_ONLY mode of the compiler.
Use the function constructor
var fnc = new Function("param1", "param2", "alert(param1+param2);");
Closure will leave the String literals alone.
See https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function

What's the best way to handle Eclipse errors on CouchDB Map/Reduce JavaScript functions?

As noted in Where to write and store mongoDB map/reduce functions in java project - Eclipse doesn't like a JavaScript (.js) file to only contain the following:
function(doc) {
if(doc.somekey) emit(doc._id, doc);
}
The keyword function is marked with an error:
Syntax error on token "function", Identifier expected after this token
This form is used in MapReduce, but perhaps it's not exactly valid JavaScript (I'm not a lawyer). Is there any way to allow this form?
edit it looks like it's a function expression instead of a function statement. ( MDN, ECMA-262 )
Non-solution: "Just add a function name" according to https://stackoverflow.com/a/11258388/185799 it seems that it's not important to economize on the size of these functions. However, adding a function name results in: {"error":"compilation_error","reason":"Compilation of the map function in the 'myView' view failed: Expression does not eval to a function."}
Possible solution? Add a function name or "module.exports = " at the top, and then remove it during a build phase, using grunt/gulp/yeoman or something related.
For now, what I am actually doing is using the literal form function anonymous(... and then doing a string replace just before calling synchronizeWithDb() to replace function anonymous( with function(. This doesn't answer my question, but works around it.
You have three bad options:
You can ignore the error since CouchDB can process this file (it will wrap it later to make it valid).
You can change the file extension but then you lose syntax highlight, code completion and error checks.
You can delete the error in the error view. It will stay deleted until you change the file the next time or you do a full build.
You may be able to configure Eclipse to stop validating the file.
There are two ways to implement #4:
You can ignore the resource. That makes this file invisible to Eclipse and all plugins. Which means you can't edit it anymore inside of Eclipse. Use Resource Filters for that.
You can check the per-project preferences for Validation rules for JavaScript. Maybe you can exclude the file.

Autocompletion for multiple js files in Cloud9

I'm trying to move my workspace to c9 because the Ace editor's autocompletion really pleased me when I worked on NodeJS projects.
But now I would like to work on JS files client-sided. It is from this point autocompletion going wrong. Indeed, there is nothing such as "require" command in client-side JS inside of the JS files themselves (except using some plugins) to inform of the other source files used in.
So when I use, in one of my JS files, a function that is defined in an other file (even libraries, frameworks : jquery, etc), Ace notifies me that the function is not defined (cause it has no way to know that the function is defined in another file, I guess).
Here we go : is there some comment line I could put in my code, or some configuration of c9 I could set, to correct that behavior ?
To remove the errors and warning, you can just add the following line near the top of your javascript file:
/* globals jquery lodash someOtherLibrary */
However, Cloud9 doesn't do autocomplete for client side libraries yet.
Abuse C9's require support
When you use var yourLibrary = require("./somefile.js");, auto-completion works perfectly.
But, as you stated, require() doesn't exist, nor do you want to have yourLibrary set to undefined. (Or to just throw an error)
As it turns out, C9 isn't that smart:
//Your library was defined in some other file
var yourLibrary; //"This does nothing other than making C9 happy
function require() {return 1;} //Define the require function
if(false) {
yourLibrary = require("yourLibraryFile.js");
}
Now, you can use autocomplete (it even shows the documentation comments)!
Note: It doesn't always work.

Declaring class differently for different Dojo versions without duplicating code?

I have an iWidget designed for IBM Connections, and my javascript code depends on Dojo (which is included by default in Connections).
It currently works in Connections 4.0 and 4.5, but is broken in Connections 5.0 (released last week), as Dojo has been updated to v1.9 and complains about my use of dojo.require.
These messages appear in the browser console when my widget tries to load on Connections 5.0:
Avoid calling dojo.require() to load classes at runtime, use net.jazz.ajax.xdloader.load_async() instead. Function '(anonymous)' required class 'dojox.atom.io.model'.
Avoid calling dojo.require() to load classes at runtime, use net.jazz.ajax.xdloader.load_async() instead. Function '(anonymous)' required class 'dojox.atom.io.Connection'.
I want to make conditional code that uses different ways of defining my widget class and requiring other Dojo modules depending on the Dojo version.
The widget javascript currently looks like this:
dojo.provide('insightCommunityWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightCommunityWidgetClass',null,{
// Class fields and methods. Currently 680 lines uncompressed.
});
I haven't yet created a version that works with Dojo 1.9 / Connections 5.0, but I think it would look something like this (and I'll have to make my javascript file name match the desired class name):
define(['dojo/_base/declare','dojox.atom.io.model','dojox.atom.io.Connection'], function(declare){
return declare(null, {
// Class fields and methods.
});
});
How can I have both of these in one file and choose between them without duplicating the class body?
Update:
I've attempted some conditional code, checking (define && define.amd) as suggested by Dimitri, tested this on Connections 4.0 and 4.5, and am getting very weird behaviour.
Temporarily ignoring any attempt to not duplicate my class, here's some conditional code which I've used exactly as shown, with a severely reduced widget class:
if (define && define.amd) {
console.log('Declaring insightWidgetClass with AMD (new method).');
define(['dojo/_base/declare','dojox/atom/io/model','dojox/atom/io/Connection'],
function(declare){
return declare(null,{
SVC_INV: 1,
onLoad: function() {
console.log('insightWidgetClass onLoad.');
}
});
}
);
} else {
console.log('Declaring insightWidgetClass with dojo.declare (old method).');
dojo.provide('insightWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightWidgetClass',null,{
SVC_INV: 1,
onLoad: function() {
console.log('insightWidgetClass onLoad.');
}
});
}
This seems not to run at all. None of my console.log messages appear in the browser console.
If I comment out the conditionals and make it so the only active code is the block after else, it runs. I get the "declaring ... (old method)" and the "insightWidgetClass onLoad" console messages.
I thought maybe enclosing the Dojo provide, require and declare calls in any kind of block might cause a problem, so I tested just putting the working code in an if (true) { block, and it still works.
The last things I've tried at this point are adding this one line before everything else, to see what define is:
console.log('dojo define',define);
... which breaks it. No console messages at all from my code.
Then I remove the define argument from that new line, so it's only sending a string to the console, and the code works again.
It seems like any mention of a define identifier silently stops the rest of the code from running.
There are no errors or warnings in the console indicating a problem. All I can say to that is: WTF?!
Now back to checking dojo.version instead.
Normally both should still work, dojo.provide() and dojo.require() are deprecated, but not entirely removed. Just make sure that your loading dojo in synchronous mode.
Besides that, the AMD way of coding is introduced in Dojo 1.7, which means that it should be supported on IBM Connections 4.5 as well (though I don't know about IBM Connections 4).
But if you really want to use both code bases, you can simply refer to the same object in stead of duplicating it, for example:
var myModule = {
// Class fields and methods.
};
if (dojo.version.major == 1 && dojo.version.minor == 9) {
define(['dojo/_base/declare','dojox.atom.io.model','dojox.atom.io.Connection'], function(declare){
return declare(null, myModule);
});
} else {
dojo.provide('insightCommunityWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightCommunityWidgetClass',null, myModule);
}
Or you could use the following check:
if (typeof define === 'function' && define.amd) {
// AMD style code
} else {
// Non-AMD style code
}
This is the approach most cross-loader libraries use. Libraries that work both on AMD loaders (Dojo, Require.js), but also on Node.js or simply by using global namespacing use a similar piece of code to determine how they load their module.
This is not your code, it should work as it is. We recently faced the same problem and identified the cause.
Connections 5 is using an AMD version of the Jazz framework which provides its own dojo loader. This framework is used to aggregate the needed dojo modules into a single JS file, which limits the number of requests to the server. Unfortunately, this loader no longer handles synchronous modules loading. It fails with the warning you reported when dojo.require() requests a module that is not yet loaded by the aggregator. If the module was already loaded, because it was part of the Jazz aggregated file, then it works. It explains why you can dojo.require() some modules, but not all of them.
-> A workaround is to deploy a server side OSGi bundle to get the modules you need part of the aggregated JS file. There is a documented extension point for this. This can unblock you while enhancing the performance of your page.
Now, we opened a PMR to IBM support. The development team is working on a resolution. We hope that they will be able to deliver a fix soon.
We reported the following issues:
dojo.require()
dojo.requireLocalization()
dojo.registerModulePath()/require({paths:})
If you think about something else, please let me know.

BreezeJS RequireJS support unstable (knockout example)

As I'm trying to compile my project with breeze and requirejs for production, I'm running into multiple issues. Unrolling this, I tried starting fresh with the Knockout RequireJS example.
After I installed MVS2013Pro I was able to run the project, but not without issues:
window.breeze is defined so it has been leaked to global scope (this
should not happen with RequireJS)
Hitting F5 Refresh will crash with
Uncaught Error: Can't find breeze and/or Q (breeze savequeuing)
Uncaught TypeError: undefined is not a function (breeze dataservice)
With these errors I'm not even trying to establish my grunt requirejs (r optimizer) task. As these are the root of my problems.
Note: I'm not searching for a knockout solution, my stack actually is: MongoDataService+Breeze+SaveQueueing+RequireJS+AngularJS+Socket.IO+Cordova
window.breeze
You're right that breeze sneaks back into the browser way down at the bottom of breeze.debug.js:
if (global.window) {
global.window.breeze = breeze;
}
We don't know why we put that in the code base. No one remembers. Coming at the end as it does, it clearly isn't needed by Breeze itself.
I commented it out in preliminary testing and that seems to do the trick. The window.breeze becomes undefined in the Todo-KO-Require sample; it remains defined (properly) in samples that do not use require.js.
We don't think this would be a breaking change for anyone ... and the remedy is simple enough: define it yourself at the top of your app.
We'll probably remove these lines for the next release unless we discover a reason to do otherwise.
F5 fail?
I can't repro this at all. I suspect your are conflating your discovery that breeze is inadvertently in the global namespace with your efforts to write an Angular app that uses breeze.savequeuing.js.
Why do I think this? Because the error you describe arises when trying to use breeze.savequeuing.js in an Angular app which is explicitly disallowed in the comments (line #14) of v.1.0.4:
Depends on Breeze (which it patches) and Q.js (not for use in Angular ... yet)
You no doubt omitted Q.js from your required libraries and it all goes to custard from there.
I'm personally allergic to the "save-when-user-changes-anything" user experience that made save queuing necessary. I'm almost sorry I wrote it. But there it is.
The apparently dependence on Q.js is the reason it isn't available for Angular apps as written. It's actually pretty easy to make it Angular-ready now because we recently (quietly) made accessible the component that Breeze is using for promises
breeze.Q // returns the promises component currently used by Breeze
Therefore, you could change the one reference to Q on line #104 to
var deferredSave = breeze.Q.defer();
This should work when using breeze v.1.4.14 and later. I haven't fully tested it yet.
This change (or something like it) likely will find its way into breeze.savequeuing.js v.1.1.0 (as I just pushed a non-release version of that).

Categories

Resources