I have an XML document that defines a task, which is a list of actions to be performed on certain data. I need to convert this "task list" to a Javascript method which can be called at some later time, which in turn calls a series of pre-defined methods, with the appropriate data. How would you achieve this?
Important Clarification:
I'm not worried about the XML parsing. I'm more interested in how to actually build the Task Method, including binding the essential data to the pre-defined action methods. That's the part I'm struggling with.
Edit: I've revised my example to make it a bit more interesting, and hopefully a bit clearer.
XML:
<task id="enter-castle">
<if holding="castle-key">
<print message="You unlock the castle door and enter." />
<destroy item="castle-key" />
<goto location="castle" />
<else>
<print message="The castle door is locked." />
</else>
</if>
</task>
Javascript:
Game = {
print: function(message) {
// display message
},
destroy: function(item) {
// destroy the object
},
goto: function(location) {
// change player location
},
ifHolding: function(item) {
// return true if player has item
}
};
parseTask(taskNode) {
var taskId = taskNode.getAttribute('id');
// What goes here??
Game.tasks[taskId] = /* ??? */;
}
When I call parseTask() on the <task id="enter-castle"> node, this should create a function that, in effect, does the following when called:
Game.tasks.enterCastle = function() {
if (Game.ifHolding('castle-key')) {
Game.print("You unlock the castle door and enter.");
Game.destroy('castle-key');
Game.goto('castle');
} else {
Game.print("The castle door is locked.");
}
}
What you want are closures.
function createMethod(arguments) {
var task = doSomethingWithYour(arguments);
return function(xmlData) { // <- this is the fundamental part
// do the task with your data
// the "task" vars are still available
// even if the returned function is executed in a different context
}
}
This allows you to create an own method for each task. Don't use the Function constructor or eval.
This is a situation where JavaScript's eval() function will make your life much easier. You can easily build a JavaScript source string matching your desired one and evaluate it to assign the function to the desired property of your Game object.
Of course, there are drawbacks to using "eval", which I won't explore in this answer since you can find countless justifications for why not to use it on the web. However, building and evaluating a simple JS source string will be much easier in the short term than say, a closure based solution, despite any potential drawbacks of performance and security. Moreover, the "eval" based solution will be easy to test since you can simply inspect the source string before it is evaluated.
So try something like this:
function buildTaskFunction(taskXml) {
var source='', depth=0, node /*a visitor to each DOM node*/;
// foreach (node in traverseInOrder(taskXml)) {
switch (node.nodeName) {
case 'TASK':
source += 'Game.tasks.' + makeFunctionName(node.id) + '= function(){';
depth++;
break;
case 'IF':
source += 'if(' + getConditionalAttribute(node) + '){'
depth++;
break;
case 'ELSE':
source += '}else{';
break;
case 'DESTROY':
source += 'Game.destroy("' + node.getAttribute('item') + '");'
break;
case 'PRINT':
source += 'Game.print("' + node.getAttribute('message') + '");'
break;
// case etc...
default: throw new Error('unhandled node type "' + node.nodeName + '"');
}
// end "foreach node".
while (depth-- > 0) { // You'll need to account for nested "if"s somehow...
source += '}';
}
eval(source);
}
And again, there are many potential problems (not definitive ones) with using "eval", so please do read about and try to understand them in the context of your solution. Use your own judgement when deciding if the drawbacks are worth the benefits in your own program -- just because a tool can be dangerous doesn't mean you should not use it.
Example using dojo:
dojo.require("dojox.xml.parser");
dojo.ready(function(){
// Parse text and generate an XML DOM
var xml = "<tnode><node>Some Text</node><node>Some Other Text</node></tnode>";
var dom = dojox.xml.parser.parse(xml);
var docNode = dom.documentElement();
// ...
}
The remainder of the function is non-trivial, but would largely just consist of attribute lookup using ['<attribute-name>'] and child node iteration using dojo.forEach(<node>.childNodes, function(childNode) { /* do stuff */ });
Related
New to Js, sorry if this is an obvious one.
I have some strings in my code that correspond to the names of variables. I'd like to put them into a function and have the function be able to make changes to the variables that have the same names as the strings.
The best example is where this 'string' is passed through from a data tag in html, but I have some other situations where this issue appears. Open to changing my entire approach too is the premise of my question is backwards.
<html>
<div data-location="deck" onClick="moveCards(this.data-location);">
</html>
var deck = ["card"];
function moveCards(location){
location.shift();};
Thanks!
A script should not depend on the names of standalone variables; this can break certain engine optimizations and minification. Also, inline handlers are nearly universally considered to be pretty poor practice - consider adding an event listener properly using Javascript instead. This will also allow you to completely avoid the issue with dynamic variable names. For example:
const deck = ["card", "card", "card"];
document.querySelector('div[data-location="deck"]').addEventListener('click', () => {
deck.shift();
console.log('deck now has:', deck.length + ' elements');
});
<div data-location="deck">click</div>
I think this can technically be done using eval, but it is good practice to think more clearly about how you design this so that you only access objects you directly declare. One example of better design might be:
container = {
obj1: //whatever this object is
...
objn:
}
function applyMethodToObject(object_passed){
container[object_passed].my_method();
}
I'm not sure I 100% follow what you're trying to do, but rather than trying to dynamically resolve variable names you might consider using keys in an object to do the lookup:
const locations = {
deck: ['card']
}
function moveCards (location) {
// if 'deck' is passed to this function, this is
// the equivalent of locations['deck'].shift();
locations[location].shift();
};
Here's a working demo:
const locations = {
deck: ['card 1', 'card 2', 'card 3', 'card 4']
};
function move (el) {
const location = el.dataset.location;
const item = locations[location];
item.shift();
updateDisplay(item);
}
// update the display so we can see the list
function updateDisplay(item) { document.getElementById('display').innerHTML = item.join(', ');
}
// initial list
updateDisplay(locations['deck']);
#display {
font-family: monospace;
padding: 1em;
background: #eee;
margin: 2em 0;
}
<div data-location='deck' onclick="move(this)">click to shift deck</div>
<div id="display">afda</div>
When you assign a value to an object in javascript you can access with dot or array notation. IE
foo = {};
foo.bar = "bar";
console.log(foo.bar);
console.log(foo["bar"]);
Additionally, global variables are added to the window object, meaning deck is available at window["deck"] or window[location] in your case. That means your moveCards function could do:
function moveCards(location) {
// perform sanity checks since you could change data-location="foo"
// which would then call window.foo.shift()
if (window[location]) {
window[location].shift();
}
}
That said, this probably isn't a great approach, though it's hard to say without a lot more context.
I am learning JavaScript so that I can implement Google Tag Manager. I have a list of paths that I would like GTM to rewrite to something friendlier like so:
function() {
return document.location.pathname.indexOf('/l/138281/2016-06-07/dy383') > -1 ? 'Test Success' : undefined;
}
function() {
return document.location.pathname.indexOf('/l/138281/2016-04-03/55z63') > -1 ? 'SPP Contact Success' : undefined;
I'm just not sure how to combine these returns into one function (I currently have about 30 URLs to rewrite). I imagine I can use if/else, but advice would be quite lovely.
--edit--
URL Path Rewrite To
/test-638-jsj /test-success
/spp-zxcv-765 /spp-contact-success
/foo-asdf-123 /foo
/foo-bar-987 /foo-bar
The return function mentioned above does this beautifully for an individual link. I just want to be able to rewrite a series of URLs in one function (or however it makes sense to do this most specifically). Hopefully that helps clarify.
Thanks!
It is always a great idea to structure your code: separate abstract functionality from the specific problem.
What you are actually doing is scannins strings for occurences of keywords and returning specific values if such a keyword has been found.
Therefore, you need a function performing the above computation and a JavaScript datastructure holding your keywords and their values (= Object):
// Return patterns[key] if any key is found in string, else return string:
function match(string, patterns) {
for (key of Object.keys(patterns)) {
if (string.indexOf(key) > -1) return patterns[key];
}
return string;
}
var patterns = {
'/l/138281/2016-06-07/dy383': 'Test Success',
'/l/138281/2016-04-03/55z63': 'SPP Contact Success'
}
console.log(match('/l/138281/2016-06-07/dy383', patterns)); // "Test Success"
console.log(match('/doesnotexist', patterns)); // "/doesnotexist"
console.log(match(document.location.pathname, patterns));
I'm using Javascript with jQuery. I'd like to implement out params. In C#, it would look something like this:
/*
* odp the object to test
* error a string that will be filled with the error message if odp is illegal. Undefined otherwise.
*
* Returns true if odp is legal.
*/
bool isLegal(odp, out error);
What is the best way to do something like this in JS? Objects?
function isLegal(odp, errorObj)
{
// ...
errorObj.val = "ODP failed test foo";
return false;
}
Firebug tells me that the above approach would work, but is there a better way?
The callback approach mentioned by #Felix Kling is probably the best idea, but I've also found that sometimes it's easy to leverage Javascript object literal syntax and just have your function return an object on error:
function mightFail(param) {
// ...
return didThisFail ? { error: true, msg: "Did not work" } : realResult;
}
then when you call the function:
var result = mightFail("something");
if (result.error) alert("It failed: " + result.msg);
Not fancy and hardly bulletproof, but certainly it's OK for some simple situations.
I think this is pretty much the only way (but I am not a hardcore JavaScript programmer ;)).
What you could also consider is to use a callback function:
function onError(data) {
// do stuff
}
function isLegal(odp, cb) {
//...
if(error) cb(error);
return false;
}
isLegal(value, onError);
Yes, as you yourself mentioned, objects are the best and only way to pass data by reference in JavaScript. I would keep your isLegal function as such and simply call it like this:
var error = {};
isLegal("something", error);
alert(error.val);
The answers I have seen so far aren't implementing out parameters in JavaScript, as they are used in C# (the out keyword). They are merely a workaround that returns an object in case of an error.
But what do you do if you really need out parameters?
Because Javascript doesn't directly support it, you need to build something that is close to C#'s out parameters. Take a look at this approach, I am emulating C#s DateTime.TryParse function in JavaScript. The out parameter is result, and because JavaScript doesn't provide an out keyword, I am using .value inside the function to pass the value outside the function (as inspired by MDN suggestion):
// create a function similar to C#'s DateTime.TryParse
var DateTime = [];
DateTime.TryParse = function(str, result) {
result.value = new Date(str); // out value
return (result.value != "Invalid Date");
};
// now invoke it
var result = [];
if (DateTime.TryParse("05.01.2018", result)) {
alert(result.value);
} else {
alert("no date");
};
Run the snippet and you'll see it works: It parses the str parameter into a Date and returns it in the result parameter. Note that result needs to be initialized as empty array [], before you call the function (it can also be an object{} depending on your needs). This is required because inside the function you "inject" the .value property.
Now you can use the pattern above to write a function as the one in your question (this also shows you how to emulate the new discard parameter known as out _ in C#: In JavaScript we're passing [] as shown below):
// create a function similar to C#'s DateTime.TryParse
var DateTime = [];
DateTime.TryParse = function(str, result) {
result.value = new Date(str); // out value
return (result.value != "Invalid Date");
};
// returns false, if odb is no date, otherwise true
function isLegal(odp, errorObj) {
if (DateTime.TryParse(odp, [])) { // discard result here by passing []
// all OK: leave errorObj.value undefined and return true
return true;
} else {
errorObj.value = "ODP failed test foo"; // return error
return false;
}
}
// now test the function
var odp = "xxx01.12.2018xx"; // invalid date
var errorObj = [];
if (!isLegal(odp, errorObj)) alert(errorObj.value); else alert("OK!");
What this example does is it uses the result parameter to pass an error message as follows:
errorObj.value = "ODP failed test foo"; // return error
If you run the example it will display this message in a popup dialog.
Note: Instead of using a discard parameter as shown above, in JavaScript you could also use a check for undefined, i.e. inside the function check for
if (result === undefined) {
// do the check without passing back a value, i.e. just return true or false
};
Then it is possible to omit result as a parameter completely if not needed, so you could invoke it like
if (DateTime.TryParse(odp)) {
// ... same code as in the snippet above ...
};
I am using a callback method (similar to Felix Kling's approach) to simulate the behavior of out parameters. My answer differs from Kling's in that the callback function acts as a reference-capturing closure rather than a handler.
This approach suffers from JavaScript's verbose anonymous function syntax, but closely reproduces out parameter semantics from other languages.
function isLegal(odp, out_error) {
//...
out_error("ODP failed test foo"); // Assign to out parameter.
return false;
}
var error;
var success = isLegal(null, function (e) { error = e; });
// Invariant: error === "ODP failed test foo".
there is another way JS can pass 'out' parameters. but i believe the best ones for your situation were already mentioned.
Arrays are also passed by reference, not value. thus just as you can pass an object to a function, and then set a property of the object in the function, and then return, and access that object's property, you can similarly pass an Array to a function, set some values of the array inside the function, and return and access those values outside the array.
so in each situation you can ask yourself, "is an array or an object better?"
I'm not going to post any code but what fails to be done here in these answers is to put rhyme to reason. I'm working in the native JS arena and the problem arose that some native API calls need to be transformed because we can't write to the parameters without ugly shameful hacks.
This is my solution:
// Functions that return parameter data should be modified to return
// an array whose zeroeth member is the return value, all other values
// are their respective 1-based parameter index.
That doesn't mean define and return every parameter. Only the
parameters that recieve output.
The reason for this approach is thus: Multiple return values may be needed for any number of procedures. This creates a situation where objects with named values (that ultimately will not be in sync with the lexical context of all operations), constantly need to be memorized in order to appropriately work with the procedure(s).
Using the prescribed method, you only have to know what you called, and where you should be looking rather than having to know what you are looking for.
There is also the advantage that "robust and stupid" alogrithms can be written to wrap around the desired procedure calls to make this operation "more transparent".
It would be wise to use an object, function, or an array (all of which are objects) as a "write-back-output" parameter, but I believe that if any extraneous work must be done, it should be done by the one writing the toolkit to make things easier, or broaden functionality.
This is a one for all answer for every occaision, that keeps APIs looking the way the should at first look, rather than appearing to be and having every resemblence of a hobble-cobbled weave of spaghetti code tapestry that cannot figure out if it is a definition or data.
Congratulations, and good luck.
I'm using the webkitgtk3 and interfaceing some native C Library procs. so this proven code sample might at least serve the purpose of illustration.
// ssize_t read(int filedes, void *buf, size_t nbyte)
SeedValue libc_native_io_read (SeedContext ctx, SeedObject function, SeedObject this_object, gsize argument_count, const SeedValue arguments[], SeedException *exception) {
// NOTE: caller is completely responsible for buffering!
/* C CODING LOOK AND FEEL */
if (argument_count != 3) {
seed_make_exception (ctx, exception, xXx_native_params_invalid,
"read expects 3 arguments: filedes, buffer, nbyte: see `man 3 read' for details",
argument_count
); return seed_make_undefined (ctx);
}
gint filedes = seed_value_to_int(ctx, arguments[0], exception);
void *buf = seed_value_to_string(ctx, arguments[1], exception);
size_t nbyte = seed_value_to_ulong(ctx, arguments[2], exception);
SeedValue result[3];
result[0] = seed_value_from_long(ctx, read(filedes, buf, nbyte), exception);
result[2] = seed_value_from_binary_string(ctx, buf, nbyte, exception);
g_free(buf);
return seed_make_array(ctx, result, 3, exception);
}
The following is approach i am using. And this is answer for this question. However code has not been tested.
function mineCoords( an_x1, an_y1 ) {
this.x1 = an_x1;
this.y1 = an_y1;
}
function mineTest( an_in_param1, an_in_param2 ) {
// local variables
var lo1 = an_in_param1;
var lo2 = an_in_param2;
// process here lo1 and lo2 and
// store result in lo1, lo2
// set result object
var lo_result = new mineCoords( lo1, lo2 );
return lo_result;
}
var lo_test = mineTest( 16.7, 22.4 );
alert( 'x1 = ' + lo_test.x1.toString() + ', y1 = ' + lo_test.y1.toString() );
The usual approach to the specific use case you outlined in Javascript, and in fact most high level languages, is to rely on Errors (aka exceptions) to let you know when something out of the ordinary has occurred. There's no way to pass a value type (strings, numbers etc) by reference in Javascript.
I would just do that. If you really need to feed custom data back to the calling function you can subclass Error.
var MyError = function (message, some_other_param)
{
this.message = message;
this.some_other_param = some_other_param;
}
//I don't think you even need to do this, but it makes it nice and official
MyError.prototype = Error;
...
if (something_is_wrong)
throw new MyError('It failed', /* here's a number I made up */ 150);
Catching exceptions is a pain, I know, but then again so is keeping track of references.
If you really really need something that approaches the behavior of out variables, objects are passed by reference by default, and can handily capture data from other scopes--
function use_out (outvar)
{
outvar.message = 'This is my failure';
return false;
}
var container = { message : '' };
var result = use_out(container );
console.log(container.message); ///gives the string above
console.log(result); //false
I think this goes a some ways towards answering your question, but I think your entire approach is broken from the start. Javascript supports so many much more elegant and powerful ways to get multiple values out of a function. Do some reading about generators, closures, hell even callbacks can be nice in certain situations-- look up continuation passing style.
My point with this whole rant is to encourage anyone reading this to adapt their programming style to the limitations and capabilities of the language they're using, rather than trying to force what they learned from other languages into it.
(BTW some people strongly recommend against closures because they cause evil side-effects, but I wouldn't listen to them. They're purists. Side effects are almost unavoidable in a lot of applications without a lot of tedious backtracking and stepping around cant-get-there-from-here obstacles. If you need them, keeping them all together in a neat lexical scope rather than scattered across a hellscape of obscure pointers and references sounds a lot better to me)
The main advantage of real output parameters is direct modification of one or more scalar variables in the scope of the caller. Among the approaches proposed in other answers, only callbacks satisfy this requirement:
function tryparse_int_1(s, cb)
{ var res = parseInt(s);
cb(res);
return !isNaN( res );
}
function test_1(s)
{ var /* inreger */ i;
if( tryparse_int_1( s, x=>i=x ) )
console.log(`String "${s}" is parsed as integer ${i}.`); else
console.log(`String "${s}" does not start with an integer.`);
}
test_1("47");
test_1("forty-seven");
In this case, passing each output parameter requires five extra characters to wrap its identifier into an anonymous setter function. It is neither very readable nor easy to type frequently, so one can resort to the single most interesting property of scripting languages—their ability to do magick, such as executing strings as code.
The following example implements an extended version of the integer-parsing function above, which now has two output parameters: the resulting integer and a flag indicating whether it is positive:
/* ------------ General emulator of output parameters ------------ */
function out_lit(v)
{ var res;
if( typeof(v) === "string" )
res = '"' + v.split('\"').join('\\\"') + '"'; else
res = `${v}`;
return res;
}
function out_setpar(col, name, value)
{ if( col.outs == undefined ) col.outs = [];
col.outs[name] = value;
}
function out_setret(col, value)
{ col.ret = value; }
function out_ret( col )
{ var s;
for(e in col.outs)
{ s = s + "," + e + "=" + out_lit( col.outs[e] ); }
if( col.ret != undefined )
{ s = s + "," + out_lit( col.ret ); }
return s;
}
/* -------- An intger-parsing function using the emulator -------- */
function tryparse_int_2 // parse the prefix of a string as an integer
( /* string */ s, // in: input string
/* integer */ int, // out: parsed integer value
/* boolean */ pos // out: whether the result is positive
)
{ var /* integer */ res; // function result
var /* array */ col; // collection of out parameters
res = parseInt(s);
col = [];
out_setpar( col, int, res );
out_setpar( col, pos, res > 0 );
out_setret( col, !isNaN( res ) );
return out_ret( col );
}
In this version, passing each output parameters requires two extra characters around its identifier to embed it into a string literal, plus six characters per invocation to evaluate the result:
function test_2(s)
{ var /* integer */ int;
var /* boolean */ pos;
if( !eval( tryparse_int_2( s, "int", "pos" ) ) )
{ console.log(`String "${s}" does not start with an integer.`); }
else
{ if( pos ) adj = "positive";
else adj = "non-positive";
console.log(`String "${s}" is parsed as a ${adj} integer ${int}.`);
}
}
test_2( "55 parrots" );
test_2( "-7 thoughts" );
test_2( "several balls" );
The output of the test code above is:
String "55 parrots" is parsed as a positive integer 55.
String "-7 thoughts" is parsed as a non-positive integer -7.
String "several balls" does not start with an integer.
This solution, however, has a deficiency: it cannot handle returns of non-basic types.
Perhaps a cleaner approach is the emulation of pointers:
// Returns JavaScript for the defintion of a "pointer" to a variable named `v':
// The identifier of the pointer is that of the variable prepended by a $.
function makeref(v)
{ return `var $${v} = {set _(val){${v}=val;},get _() {return ${v};}}`; }
// Calcualtes the square root of `value` and puts it into `$root`.
// Returns whether the operation has succeeded.
// In case of an error, stores error message in `$errmsg`.
function sqrt2
( /* in number */ value, /* value to take the root of */
/* out number */ $root , /* "pointer" to result */
/* out string */ $errmsg /* "pointer" to error message */
)
{ if( typeof( value ) !== "number" )
{ $errmsg._ = "value is not a number.";
return false;
}
if( value < 0 )
{ $errmsg._ = "value is negative.";
return false;
}
$root._ = Math.sqrt(value);
return true;
}
The following test code:
function test(v)
{ var /* string */ resmsg;
var /* number */ root ; eval( makeref( "root" ) );
var /* string */ errmsg; eval( makeref( "errmsg" ) );
if( sqrt2(v, $root, $errmsg) ) resmsg = `Success: ${root}`;
else resmsg = `Error: ${errmsg}`;
console.log(`Square root of ${v}: ` + resmsg );
}
test("s" );
test(-5 );
test( 1.44);
prints:
Square root of s: Error: value is not a number.
Square root of -5: Error: value is negative.
Square root of 1.44: Success: 1.2
"Pointers" created by this method are reusable in other functions and subsequent invocations of the same function. For example, you could define a function that appends strings:
// Append string `sep' to a string pointed to by $s, using `sep` as separator:
// $s shall not point to an undefined value.
function append($s, sep, val)
{ if( $s._ != '' ) $s._ += sep;
$s._ += val;
}
and use it thus:
const sep = ", "
var s; eval( makeref("s") );
s = '';
append( $s, sep, "one" );
append( $s, sep, "two" );
append( $s, sep, "three" );
console.log( s );
It will print:
one, two, three
I've got a Javascript (dojo) function that I'm using to auto-populate a form. The idea is to pass in a JS object with keys for the form field ids, and values that help define how to fill in the field.
It works, but it feels a bit ugly to me (the switch statement, the objects within objects syntax). Any thoughts on how can I improve* this code?
/**
* Fill in the form with passed in values
*
* #param {Object} defaults
*
* Can be used to pass in default values to the form. Expects an object like this:
* {<field id>: {type: '<field type>', value:'<value>'}
*
* for example:
* {
* paymethod: {type: 'select', value:'Account Transfer'}, // html select
* fundsource: {type: 'combo', value:'Corporation Account'} // dijit.comboBox
* }
*/
function fillDefaults(defaults) {
var props;
for (var field in defaults) {
props = defaults[field];
switch (props['type']) {
// so far only select and combo have been implemented
// others will be added as needed
// and maybe grouped depending on how they work
// (e.g. all dijits together, <input> with <select>, etc.)
case 'select':
dojo.byId(field).value = props['value'];
dojo.byId(field).onchange()
break;
case 'combo':
dijit.byId(field).attr('value', props['value']);
break;
}
}
}
[*] improve: make prettier, more js-like, more dojo-like, more streamlined
What does pretty mean? What's js-like?
A switch, although memory intensive, is cleaner if you plan to extend with more objects.
Maybe, instead of a switch, have an object containing your functions:
funcs = {
select: function(value) {
dojo.byId(field).value = value;
dojo.byId(field).onchange()
},
combo: function(value) {
dijit.byId(field).attr('value', value);
}
};
function payFillDefaults(defaults) {
var props;
for(var field in defaults) {
props = defaults[field];
if(funcs[props['type']]) {
funcs[props['type']](props['value']);
}
}
}
Improved a bit according JSLint standards:
function fillDefaults(defaults) {
var props, field;
for (field in defaults) {
props = defaults[field];
switch (props.type) {
case 'select':
dojo.byId(field).value = props.value;
dojo.byId(field).onchange();
break;
case 'combo':
dijit.byId(field).attr('value', props.value);
break;
}
}
}
Take a look at dojox.form.manager — it does approximately what you want to achieve. It support existing form widgets ("main" mixin), practically all DOM form elements (node mixin), the unified event processing, and so on. It can be used with unmodified forms.
As you would probably guessed already it is structured as a set of independent mix-ins so you can select only required functionality. For convenience and as an example there a class, which combines all mix-ins together: Manager.
Read what it does, see if it fits your needs, if not, study its code and borrow what you like. If it misses something — share your feedback on the mailing list (on gmane.org). Or, if you can improve it, contribute back.
I'm developing a JavaScript library that will be used by 3rd party developers.
The API includes methods with this signature:
function doSomething(arg1, arg2, options)
arg1, arg2 are 'required' simple type arguments.
options is a hash object containing optional arguments.
Would you recommend to validate that:
- argument types are valid?
- options attributes are correct? For example: that the developer didn't pass by mistake onSucces instead of onSuccess?
why do popular libraries like prototype.js do not validate?
You have the right to decide whether to make a "defensive" vs. a "contractual" API. In many cases, reading the manual of a library can make it clear to it's user that he should provide arguments of this or that type that obey these and those constraints.
If you intend to make a very intuitive, user friendly, API, it would be nice to validate your arguments, at least in debug mode. However, validation costs time (and source code => space), so it may also be nice to leave it out.
It's up to you.
Validate as much as you can and print useful error messages which help people to track down problems quickly and easily.
Quote this validation code with some special comments (like //+++VALIDATE and //--VALIDATE) so you can easily remove it with a tool for a high-speed, compressed production version.
Thanks for the detailed answers.
Below is my solution - a utility object for validations that can easily be extended to validate basically anything...
The code is still short enough so that I dont need to parse it out in production.
WL.Validators = {
/*
* Validates each argument in the array with the matching validator.
* #Param array - a JavaScript array.
* #Param validators - an array of validators - a validator can be a function or
* a simple JavaScript type (string).
*/
validateArray : function (array, validators){
if (! WL.Utils.isDevelopmentMode()){
return;
}
for (var i = 0; i < array.length; ++i ){
WL.Validators.validateArgument(array[i], validators[i]);
}
},
/*
* Validates a single argument.
* #Param arg - an argument of any type.
* #Param validator - a function or a simple JavaScript type (string).
*/
validateArgument : function (arg, validator){
switch (typeof validator){
// Case validation function.
case 'function':
validator.call(this, arg);
break;
// Case direct type.
case 'string':
if (typeof arg !== validator){
throw new Error("Invalid argument '" + Object.toJSON(arg) + "' expected type " + validator);
}
break;
}
},
/*
* Validates that each option attribute in the given options has a valid name and type.
* #Param options - the options to validate.
* #Param validOptions - the valid options hash with their validators:
* validOptions = {
* onSuccess : 'function',
* timeout : function(value){...}
* }
*/
validateOptions : function (validOptions, options){
if (! WL.Utils.isDevelopmentMode() || typeof options === 'undefined'){
return;
}
for (var att in options){
if (! validOptions[att]){
throw new Error("Invalid options attribute '" + att + "', valid attributes: " + Object.toJSON(validOptions));
}
try {
WL.Validators.validateArgument(options[att], validOptions[att]);
}
catch (e){
throw new Error("Invalid options attribute '" + att + "'");
}
}
},
};
Heres a few examples of how I use it:
isUserAuthenticated : function(realm) {
WL.Validators.validateArgument(realm, 'string');
getLocation: function(options) {
WL.Validators.validateOptions{
onSuccess: 'function',
onFailure: 'function'}, options);
makeRequest : function(url, options) {
WL.Validators.validateArray(arguments, ['string',
WL.Validators.validateOptions.carry({
onSuccess : 'function',
onFailure : 'function',
timeout : 'number'})]);
We have to discover and eliminate problems as soon as possible. If don't use TypeScript or Flow, you rather do it with a validation lib. It will help you avoiding spending hours hunting obscure errors caused by invalid types given as arguments. It looks like many take it serious - https://www.npmjs.com/package/aproba gets currently 9M(!) downloads per week.
To me it doesn't suite, explained here http://dsheiko.com/weblog/validating-arguments-in-javascript-like-a-boss
I go with https://www.npmjs.com/package/bycontract that based on JSDoc expressions:
import { validate } from "bycontract";
const PdfOptionsType = {
scale: "?number"
}
function pdf( path, w, h, options, callback ) {
validate( arguments, [
"string",
"!number",
"!number",
PdfOptionsType,
"function=" ] );
//...
return validate( returnValue, "Promise" );
}
pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } ); // ok
pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string
On methods you can just reuse existing JSDoc comment block:
import { validateJsdoc, typedef } from "bycontract";
typedef("#PdfOptionsType", {
scale: "number"
});
class Page {
#validateJsdoc(`
#param {string} path
#param {!number} w
#param {!number} h
#param {#PdfOptionsType} options
#param {function=} callback
#returns {Promise}
`)
pdf( path, w, h, options, callback ) {
return Promise.resolve();
}
}
However I keep this validation on dev/test environments, but skip it on live:
import { config } from "bycontract";
if ( process.env.NODE_ENV === "production" ) {
config({ enable: false });
}
It depends. How big this library would be? It is said that typed languages are better for big projects with complex API. Since JS is to some extent hybrid, you can choose.
About validation - I don't like defensive programming, the user of the function shall be obliged to pass valid arguments. And in JS size of code matters.
An intermediate way would be to return a reasonable default value (e.g. null) when required arguments are missing. In this way, the user's code will fail, not yours. And it will probably be easier for them to figure out what is the issue in their code rather than in yours.
When I've developed APIs like these in the past, I've validated anything that I feel is a "major" requirement - in your example, I'd verify the first two arguments.
As long as you specify sensible defaults, it should be pretty simple for your user to determine that "optional" arguments aren't specified correctly, since it won't make any change to the application, but everything will still work properly.
If the API is complex, I'd suggest following Aaron's advice - add comments that can be parsed by a compressor around your validation so the developers get the benefit of validation, but can extract the extra dead weight when pushing the code into production.
EDIT:
Here're some examples of what I like to do in the cases where validation is necessary. This particular case is pretty simple; I probably wouldn't bother with validation for it, since it really is trivial. Depending on your needs, sometimes attempting to force types would be better than validation, as demonstrated with the integer value.
Assume extend() is a function that merges objects, and the helper functions exist:
var f = function(args){
args = extend({
foo: 1,
bar: function(){},
biz: 'hello'
}, args || {});
// ensure foo is an int.
args.foo = parseInt(args.foo);
//<validation>
if(!isNumeric(args.foo) || args.foo > 10 || args.foo < 0){
throw new Error('foo must be a number between 0 and 10');
}
if(!isFunction(args.bar)){
throw new Error('bar must be a valid function');
}
if(!isString(args.biz) || args.biz.length == 0){
throw new Error('biz must be a string, and cannot be empty');
}
//</validation>
};
EDIT 2:
If you want to avoid common misspellings, you can either 1) accept and re-assign them or 2) validate the argument count. Option 1 is easy, option 2 could be done like this, although I'd definitely refactor it into its own method, something like Object.extendStrict() (example code works w/ prototype):
var args = {
ar: ''
};
var base = {
foo: 1,
bar: function(){},
biz: 'hello'
};
// save the original length
var length = Object.keys(base).length;
// extend
args = Object.extend(base, args || {});
// detect if there're any extras
if(Object.keys(args).length != length){
throw new Error('Invalid argument specified. Please check the options.')
}
Don't validate. More code is more code the user has to download, so it's a very real cost on the user and production systems. Argument errors are easy enough to catch by the developer; don't burden the user with such.