Question
Is there a clean and robust way to handle calls to a plugin so that the caller can always expect to have a JavaScript array returned to them, regardless of whether the plugin is a Firefox add-on or an ActiveX control? I've already got the actual plugin calls wrapped in functions, like:
function getDevice(deviceKey) {
return plugin.getDevice(deviceKey);
}
I can change it to something like:
function getDevice(deviceKey) {
return normalizeArray(plugin.getDevice(deviceKey));
}
function normalizeArray(array) {
return typeof(array) == 'unknown' ? array.toArray() : array;
}
but I still need to remember to actually call normalizeArray from the wrapper functions, and the normalize implementation seems like it could be lacking in robustness and/or making some suspect assumptions.
Is there a better way to handle this situation?
Background
I'm writing some JavaScript to interact with a plugin through JavaScript. The plugin is available as an add-on in FF and as an ActiveX in IE. There are a number of methods available in the plugin that return arrays. In FF, calling:
typeof(retVal);
on the object returns 'object'. I can do things like:
retVal.length;
retval[0];
and they work as expected. When I make those same method calls in IE, calling:
typeof(retVal);
returns 'unknown' and calls like:
retVal.length;
retval[0];
are undefined.
I've done some debugging and discovered that what the ActiveX is really returning is an array of variants. This object IS recognizable by JScript, and a JavaScript version of the array can be obtained by caling retVal.toArray().
I would make the array normalisation part of the wrapper but keep it as a separate function so:
function getDevice(deviceKey) {
return normalizeArray( plugin.getDevice(deviceKey) );
}
You're right, the normalizeArray function does seem a bit brittle. You might instead want to do an instanceof test, and even wrap it in try..catch as IE is known to do strange things when ActiveX objects are tested this way, so:
function normalizeArray(obj) {
try {
if (obj instanceof Array) {
return obj;
}
} catch(d) {}
// Convert obj to array - see if toArray property is truthy
if (obj.toArray) {
// Is this syntax correct?
// If it is, call it
return obj.toArray();
} else {
// Not Array, no toArray(), what next?
}
}
However I'm not sure if the object returned by your Firefox plugin is a javascript Array or if it's created in the same scope as the normalizeArray function (i.e. it might be created from a different Array constructor) so the instanceof test will fail even if it is an Array. An alternative test is:
Object.prototype.toString.call(obj) == '[object Array]'
which I think should definitely be wrapped in try..catch for IE if there is any chance obj is an ActiveX object.
Incidentally, typeof is an operator, there is no need to use the grouping operator ().
Related
This question already has answers here:
How to determine that a JavaScript function is native (without testing '[native code]')
(2 answers)
Closed 4 years ago.
I want to use javascript's Proxy objects to augment a data structure with a bit of additional logging. While doing so, I noticed a problem:
simpleProxy = new Proxy(new Number(4), {})
simpleProxy.valueOf(); // TypeError: valueOf method called on incompatible Proxy
The error makes sense to me. valueOf is invoked with the this object set to the Proxy instead of the Number object thus leading to a type error.
The problem to me now is that in user code it is actually useful to propagate the Proxy as the this value into function calls so that access to properties is done through the proxy. Only when calling "low-level" routines like the above do I get in trouble. The question: How can I reliably detect that I'd be calling such a Proxy incompatible function, possibly assuming for a moment that user defined functions are safe to call?
This is not asking if the called function is a native function. Only certain native functions can not handle Proxy values. Besides, the detection in the linked question is rather unsound and would require different special cases here. Consider a function linked from a native interface. It may very well be able to operate on a proxy value, thus detecting it as a native function would be a false positive.
Currently, I believe the best approach is to find out if the target in get is primitive by calling target.valueOf() first instead of infering anything from whether the function is native or not. As such, this is not a duplicate.
First we check if the Property is from the prototype or from the target itself, if not and the key is that of a method, we return an anonymous function that passes its arguments into the method with the appropriate this when called.
Note: I'm not entirely sure if this is foolproof.
let a = new Proxy(new Number(3), {
get(target, key) {
if (!target.hasOwnProperty(key) && typeof target[key] === "function") {
return function(...args) {
return target[key].call(target, args);
}
}
return target[key];
}
});
console.log( a.valueOf() );
Often, we are presented with an array (IEnumerable) property that specific values need to be extracted. in c# we can do something similar to:
public AssetModel PromoImage {
get
{
return Assets.FirstOrDefault(x => x.AssetTypeCd == "promoimage");
}
private set { }
}
Is there a way to easily to this within Angular 2?
Lodash provides similar functionality to LINQ for JavaScript programs (and then some), though not deferred execution -- LINQ queries are deferred until they are enumerated, while lodash (usually) performs the query immediately and returns an array/object of results. (Though in this case LINQ wouldn't even defer it since FirstOrDefault returns a scalar and not a queryable/enumerable.)
In your case, you would do something like this:
let obj = {
get promoImage() {
return _.find(assets, a => a.assetTypeCd === 'promoimage');
},
// ...
};
Then accessing obj.promoImage will execute the function to obtain the attribute's value.
(Here I assume this is where we are creating the new object, and assets is the assets list in the lexical scope of the constructor function. You can change it to reference this if you are storing data on the object itself and not in constructor upvalues.)
Notes:
Lodash does not depend on Angular at all.
ES6 provides a find() method on the Array prototype, so this feature will be built-in to browsers once ES6 is adopted. Sadly, IE is (as usual) the outlier without any support for it. However, Lodash is still a very useful library to have in your toolkit, and note that Lodash's find() works on objects too, not just arrays.
I am writing a piece of library code for internal development at my company, so this is purely a personal satisfaction question - if there are issues, our error logging will note it pretty quickly and we'll be able to do updates... I'd just like to avoid those if possible :D
The library needs to operate on <th> or <td> values passed in that are either sourced from document.get..., document.createElement or onmousedown events. I've had issues when the wrong sort of object (i.e. non-th/td) went into the function, so I've put a fail-out clause at the top of the function:
function doSomething (cell) {
var _objName = Object.getPrototypeOf(cell).toString();
if (_objName.indexOf("HTMLTableCellElement") === -1 &&
_objName.indexOf("HTMLTableHeaderCellElement") === -1) {
// log error
return false;
}
// normal work here
}
Is this safe? I'm targetting modern (IE 10+, Chrome) browsers within our company, and it seems to work at the moment:
IE returns "[object HTMLTableCellElementPrototype]" (For <TD>) or "[object HTMLTableCellHeaderElementPrototype]" (for <TH>)
Chrome returns "[object HTMLTableCellElement]" for both
I'm concerned that I'm relying on a non-documented implementation detail and things are going to break when we move to other browsers or browsers are updated.
Is this safe, or should I be using another method to ensure correct input? Some of this input is going to be from things the user has clicked on, so there definitely will be invalid input going to the function...
should I be using another method to ensure correct input?
Yes. Using toString might work, but checking class names is never a really good idea.
Instead, simply use .tagName to check whether an element of the right kind was passed:
if (cell.tagName != "TD" && cell.tagName != "TH") { return false; }
Alternatively, you could use a DOM selector like in
if (!cell.matches("td, th")) { return false; }
but cross-browser support for .matches() isn't that good.
Now that I'm programming in javascript more and more often, there's a task I'm coming across quite often that I wonder could be dealt with more elegantly.
It's about checking whether a variable, values say, is an array of Xs, or rather just an X, immediatly followed by an iteration over it or its elements.
(X being object, string, number, ... anything really -- except array).
Especially when dealing with xml or json files, a single X is not wrapped in [ ] to make it a 1-element array, and my code breaks if I don't watch out.
I deal with this now in the following way:
if (!(values instanceof Array)) values = [values]
values.forEach(function(value){/*do stuff with value*/});
For now, I've written a function to take care of this,
function arrayIfNot(arr) {return (arr instanceof Array) ? arr : [arr];}
Which I can use as
arrayIfNot(values).forEach(function(value){/*do stuff with value*/});
but as it is such a common task, I'd be surprised if there isn't a common shortcut or library function (jQuery?) to do this.
Thanks!
EDIT:
I suppose I could extend the prototypes like so (haven't tried):
Array.prototype.toArray = function () {return this;};
String.prototype.toArray = function () {return [this];};
...
so that I could do
values.toArray().forEach(function(value){/*do stuff with value*/});
but I'm always warned against extending the prototype. What do you think?
Thanks again!
Personally, I've adopted this idiom:
[].concat(a)
This takes advantage of the behavior of concat, which is that if its argument is a scalar, it just adds it to the array; if it is an array, it adds each of its elements.
There is a common way to do this in modern browsers, it's Array.isArray, and it's supported from IE9 and up.
MDN has a polyfill for non-supporting browsers.
jQuery has it's own version, jQuery.isArray
For your specific example, a common way to do it is
values = Array.isArray(values) ? values : [values];
If you're willing to use a library, Underscore.js has a lot of fantastic additions to basic JavaScript. Using Underscore, I commonly do something like this:
function myFunction(arg) {
arrayArg = _.flatten([arg])
}
If your arg might itself contain arrays which you need to preserve, just add true as a second argument to flatten.
For example; the function alert or writeln; how could I find which interface these functions come from programmatically within JavaScript?
Yes, like this:
if (typeof(yourFunction) !== "undefined") {
// do something, like call the function
}
You can easily check to see that a function is defined with typeof:
if (typeof(maybeFunction) === "function") {
// do something
}
On the other hand, it is not easy in general to know where a function is defined. Different browsers host their core functional implementations in different places, and furthermore it is incredibly easy to copy references to functions:
var myAlert = alert; // Now myAlert is a function,
// but where will you find a function myAlert() declaration? Nowhere...
So I think the proper answer to your question is, it's not possible (in general). You can use a debugger to find it on the fly, or a good text editor or grep tool to find it offline, but you won't be able to find it programatically.
If you want to "list an objects functions", you can do:
function listOwnMethods(obj) {
var ownMethods = [];
for (var p in obj) {
if (obj.hasOwnProperty(p) && typeof obj[p] == 'function') {
ownMethods.push(p);
}
}
return ownMethods;
}
However, this will not list the non–enumerable properties. If you want to also get enumerable inherited methods, remove the hasOwnProperty test.
Some versions of JavaScript also have getters and setters, so properties may behave like functions even though their Type is not "function". Finally, host objects can return anything they like when tested with typeof, so you may not be able to determine all (or even any) of a host object's methods that way.