I have a question regarding global variables I guess.
I have the following code:
$('span.ion-chevron-left, span.ion-chevron-right').click(function() {
var myIterator = $(this).attr('data-iterator');
('ion-chevron-right' == $(this).attr('class')) ? window[myIterator].next() : window[myIterator].prev();
if(!window[myIterator].valid()) {
(window[myIterator].key() == window[myIterator].getTotal()) ? window[myIterator].rewind() : window[myIterator].end();
}
// do something
});
You need to know that the variable myIterator holds a string like 'myIterator1', 'myIterator2', 'myIterator3'...
As you can see I want to avoid redundance. I don't want to write something like this:
if('myIterator1' == myIterator) { ... }
if('myIterator2' == myIterator) { ... }
So I am trying to call methods on different iterator objects by writing window[myIterator].method();
Is this the right way to do it? I figured it would be possible to use eval, but I red that eval is evil so is there a difference? Or how would one solve this kind of problem? Are there any security concerns in my current code?
Best regards
Your general approach is sound, but you should avoid polluting the global namespace by putting your iterators in a dedicated object. Instead of window[myIterator], use (for example) iteratorDict[myIterator], which iteratorDict is defined by
var iteratorDict = {
"myIterator1": ...,
"myIterator2": ...,
...
}
Or, if you don't want to define all of them at once like that, simply change your definition var myIterator1 = ... to iteratorDict["myIterator1"] = ...
Related
JavaScript for browser
I need to test that one deeply embedded property is not null and if this condition is true, then to change its property. But any its parent can be null also. Therefore I am to check each item in the chain... Therefore I write such ugly code:
if(window[rootNamespace].vm.tech &&
window[rootNamespace].vm.tech.currentWorkType &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion.currentWorkSection &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion.currentWorkSection.currentStageSet){
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion
.currentWorkSection.currentStageSet.selectedEntity = item;
}
Is there a shorter method of checking?
(I was the original poster proposing the try-catch method, but based on the discussion on that post you were worried about performance. Here's an alternate approach.)
You can use prototype methods to implement a safe method of accessing subproperties. Here is a method which can safely test for the existence of a nested property:
// Indicates whether an object has the indicated nested subproperty, which may be specified with chained dot notation
// or as separate string arguments.
Object.prototype.hasSubproperty = function() {
if (arguments.length == 0 || typeof(arguments[0]) != 'string') return false;
var properties = arguments[0].indexOf('.') > -1 ? arguments[0].split('.') : arguments;
var current = this;
for(var x = 0; x < properties.length; x++) {
current = current[properties[x]];
if ((typeof current) == 'undefined') return false;
}
return true;
};
A full set of methods can be found here, with sample code.
Timings can be run here, and indicate that using the try-catch method may run a couple of orders of magnitude slower than your original approach when errors are thrown, but is otherwise quite fast. The prototype methods are more generic and can lead to a more declarative style, while offering much better performance than try-catch misses, but obviously not quite as good as hand-crafting if statements each time and/or try-catch without a miss.
I've also blogged about this approach.
Syntax wise I don't think so, but I recommend refactoring at least.
var getCurrentStageSet = function(window){
return window[rootNamespace].vm.tech &&
window[rootNamespace].vm.tech.currentWorkType &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion.currentWorkSection &&
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion.currentWorkSection.currentStageSet
}
var setSelectedEntity = function(currentStageSet, item){
currentStageSet.selectedEntity = item;
}
By abstracting this logic your actual set of the property will be more readable, and reusable:
var currentStageSet = getCurrentStageSet(window);
if (currentStageSet){
setSelectedEntity(currentStageSet, item);
}
For such a trivial, self-contained piece of code, it's probably not unreasonable to just catch and ignore the error (possibly log) e.g.
try {
if (window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion.currentWorkSection.currentStageSet) {
window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion.currentWorkSection.currentStageSet = item;
}
} catch (e) {
// log the error but continue
}
Not sure what else could really go wrong in this type of check, alternatively you could catch a TypeError specifically but not sure it would really matter all that much.
I generally wouldn't recommend catch all's but in this case it seems self contained enough to not be a huge risk.
Anything beyond that requires effort e.g. building an object decorator or a fluent interface type solution, seems overkill to me though.
You can create some variables to get code more readable
var tech = window[rootNamespace].vm.tech;
var workType, curVariant, curVer, curWorkSection;
if(tech){
workType = tech.currentWorkType
}
if(workType){
curVariant = workType.currentVariant;
}
if(curVariant){
curVer =curVariant.currentVersion;
}
if(curVer){
curWorkSection = curVer.currentWorkSection;
}
if(curWorkSection && curWorkSection.currentStageSet){
curWorkSection.currentStageSet.selectedEntity = item;
}
This is the most compact syntax possible in basic JavaScript. It avoids all the null-checking by using error-trapping instead. None of the other answers are as compact because the language is simply missing the feature you're after from C#.
Apparently, I'm being down-voted by the authors of the other, much less compact answers, but this is nevertheless the only single-line answer. Note that other approaches listed here have you creating multiple functions, even. :| If you want compact, this is it.
try { window[rootNamespace].vm.tech.currentWorkType.currentVariant.currentVersion
.currentWorkSection.currentStageSet.selectedEntity = item; } catch (err) {}
Currently I can do:
function addStat() {
player.str = player.str + 1;
}
But I want to be able to use things other than just "str" with my player object. So I decided with doing something like this:
function addStat(stat) {
player.stat = player.stat + 1;
}
But that doesn't seem to work, iv'e tried looking up the syntax for using parameters but could not find anything similar to the way I need.
I learned about "this" but I can't get it to work with my function.
I thought this:
function addStat(thing, stat) {
thing.stat = thing.stat + 1;
statReset();
}
would work but I can see why it won't. I made sure the rest of my javascript and html work and when I add those functions nothing breaks, it just doesn't work.
When assigning properties with a variable, you need to use bracket notation, as opposed to dot notation. This, then, looks like:
function addStat(stat) {
(stat in player) ? ++player[stat] : player[stat] = 1;
}
Due to comments (that I disagree with), I figured I should mention that since you are attempting to modify a property that may not exist, you should also add a safety check to see if you can modify it.
Otherwise you will be modifying undefined, and that will cause undesired output..
You can access properties with []:
function addStat(prop) {
player[prop] = player[prop] + 1;
}
so calling addStat("stat") will actually set player.stat.
In javascript, the syntax
object.key
is equivalent to
object["key"]
So your thing.stat is equivalent to thing["stat"], i.e. the key is the literal string "stat" when what you really want is to use the value referenced by the parameter stat as the key:
thing[stat] = thing[stat] + 1;
I'm a javascript newbie so I'm writing ugly code so far sometimes due to my lack of experience and how different it is to the languages I'm used to, so the code I'll post below works, but I'm wondering if I'm doing it the right way or perhaps it works but it's a horrible practice or there is a better way.
Basically, I have a little dude that moves within a grid, he receives from the server an action, he can move in 8 directions (int): 0:up, 1: up-right, 2: right... 7: up-left.
the server will send him this 0 <= action <= 7 value, and he has to take the correct action... now, instead of using a switch-case structure. I created a function goUp(), goLeft(), etc, and loaded them in an array, so I have a method like this:
var getActionFunction = actions[action];
actionFunction();
However, what to set all this up is this:
1) create a constructor function:
function LittleDude(container) {
this.element = container; //I will move a div around, i just save it in field here.
}
LittleDude.prototype.goUp() {
//do go up
this.element.animate(etc...);
}
LittleDude.prototype.actions = [LittleDude.prototype.goUp, LittleDude.prototype.goUpLeft, ...];
//In this array I can't use "this.goUp", because this points to the window object, as expected
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction(); //LOOK AT THIS LINE
}
Now if you pay attention, the last line won't work.. because: when i use the index to access the array, it returns a LittleDude.prototype.goUp for instance... so the "this" keyword is undefined..
goUp has a statement "this.element"... but "this" is not defined, so I have to write it like this:
actionFunction.call(this);
so my doAction will look like this:
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction.call(this); //NOW IT WORKS
}
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way. Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
What you are trying to do is one of the possible ways, but it is possible to make it more simple. Since object property names are not necessary strings, you can use action index directly on prototype. You even don't need doAction function.
LittleDude = function LittleDude(container) {
this.container = container;
}
LittleDude.prototype[0] = LittleDude.prototype.goUp = function goUp() {
console.log('goUp', this.container);
}
LittleDude.prototype[1] = LittleDude.prototype.goUpRight = function goUpRight() {
console.log('goUpRight', this.container);
}
var littleDude = new LittleDude(123),
action = 1;
littleDude[action](); // --> goUpRight 123
littleDude.goUp(); // --> goUp 123
actionFunction.call(this); //NOW IT WORKS
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way.
No, using .call() is perfectly fine for binding the this keyword - that's what it's made for.
Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
You don't have to define them on the prototype if you don't use them directly :-) Yet, if you do you might not store the functions themselves in the array, but the method names and then call them with bracket notation:
// or make that a local variable somewhere?
LittleDude.prototype.actions = ["goUp", "goUpLeft", …];
LittleDude.prototype.doAction = function(action) {
var methodName = this.actions[action];
this[methodName](); // calls the function in expected context as well
}
Ok, I know this is a stupid question, and stupid quesitons are most commonly the hardest to find answers for as everyone assumes they are just known. Anyway, i have this:
var map = {
"key1" : "someValue1",
"key2" : "someValue2"
};
And then I enumerate the entries thusly:
Y.Object.each( map, function(value,key,object) {
// do something;
} );
But what I really want to do is something like:
map = Y.Object.wrap(map);
map.each( function(value,key,object) {
} );
It is worth noting that the original object may be passed in as an argument, so I will not always be constructing them. In other words, i really do want to wrap one (and in a way that will not double wrap if it is already wrapped). The answer is probably obvious and even in the documentation, I just cant find it. So I through myself at the mercy of the googlesphere...
--------------------- EDIT --------------------
When I say wrap, I guess what I mean is (in YUI speak) augment. In other words, I would like the Y.Object methods directly available on the object in question. For example, I want to be able to do something like this:
var map = {
"key1" : "someValue1",
"key2" : "someValue2"
};
map.each( function(value,key,object) {
//do something with each entry in my map
} );
Instead of having to always do this:
var map = {
"key1" : "someValue1",
"key2" : "someValue2"
};
Y.Object.each( map, function(value,key,object) {
//do something with each entry in my map
} );
There are two reasons for this. First, I am lazy and don't want to have to keep typing Y.Object.each( myObject... when all I want is to iterate through each property of my object. Second, I want to be forward compatible so that when the functions each and some get defined natively (which I think they already are in firefox and chrome) I can leverage the native implementations without code change. I think the second reason is something Crockford seems to emphasize since he always seems to say that if you want to add a function to the prototype of an Object, do so in a way that will check to see if the function is already defined before doing so. Feel free to correct me if I am wrong anywhere above, and as always, feel free to suggest a solution :)
As best I know, YUI3 doesn't have a wrap function that works for regular objects and adds your desired each() method for them. But, you could make your own like this:
YUI().use('node', function(Y) {
// code to declare our new wrapper function
function myWrap(o) {
if (o instanceof myIterator) return(o); // don't double wrap
return(new myIterator(o));
}
// code to declare a base class used in the iterator
function myIterator(o) {
Y.mix(this, o);
}
// the actual each() iteration function
myIterator.prototype.each = function(fn) {
var key;
for (key in this) {
if (this.hasOwnProperty(key)) {
fn.call(this, this[key], key, this);
}
}
}
// start of regular code that uses the new wrapper
var map = {
"key1" : "someValue1",
"key2" : "someValue2"
};
var x = myWrap(map);
x.each(function(value, key, object) {
console.log(value);
});
});
You can see it work here: http://jsfiddle.net/jfriend00/h7Fpp/.
Basically, I am rewriting part of one of my web applications. I had a script that would collapse some or all panels of the interface at once, and another to code them.
However, my old functions looked really ugly, and were annoying to type and not powerful enough:
function collapse_all()
{
document.getElementById("panel_1").style.display="none"
document.getElementById("panel_2").style.display="none"
document.getElementById("panel_3").style.display="none"
}
function expand_all()
{
document.getElementById("panel_1").style.display=""
document.getElementById("panel_2").style.display=""
document.getElementById("panel_3").style.display=""
}
Now I have this:
function panel() //first variable in argument is collapse or expand, all others are panels to act on
{
var panels = panel.arguments
alert(typeof panel.arguments)
var mode = panels.shift() //here's my problem
if(mode=="collapse") {mode="none"}
if(mode=="expand") {mode=""}
var items = panels.length
for (i = 0;i < items;i++) {document.getElementById(panels[i]).style.display=mode}
}
panel("collapse","panel_1","panel_2","panel_3")
I have a problem though. Firebug tells me panels.shift() is not a function. With some Googling I managed to find out that panel.arguments isn't an array but an object, so I can't use array methods on it. I'm just really confused as to how I could either convert the object into an array or find another workaround, as I know next to nothing about JavaScript objects. Some example code would be highly appreciated.
You can convert the arguments object into an array like this:
var argsArray = Array.prototype.slice.call(arguments);
What this does is use the slice method common to all arrays via Array.prototype to create a genuine Array object from the array-like arguments. call() (a method of all functions) is used to call this slice method with a this value of arguments and no parameters, which has the effect of copying all of the elements of this into a new array. This may seem devious or hacky but it is actually designed into the language: see the note at the bottom of section 15.4.4.10 of the ECMAScript 3rd Edition spec.
Also, within a function you are provided the arguments object as a variable, so you don't need to access it as a property of the function object as you are doing. In your case, just use arguments rather than panel.arguments.
You could keep it much simpler (cleaned up your formatting, semi-colons, etc.):
function panel()
{
var panels = Array.prototype.slice.call(arguments);
var displayMode = (panels[0] == "collapse" ? "none" : "");
for (var i = 1; i < panels.length - 1; i++)
{
document.getElementById(panels[i]).style.display = displayMode;
}
}
Also, if you're rewriting your application, it might be a good time to consider using things like jQuery. You could assign each one of your panels a certain class name, and reduce your code to something like this:
function panel(hide)
{
$('.className').css({ display: (hide ? 'none' : '') });
}
which you could use like so:
panel(true); // or
panel(false);
Or, because now it's so syntactically simple, you might as well just create two separate functions so that your code is straightforward and you know exactly what it's going to do from the function names alone:
function showPanels() {
$('.className').css({ display: '' });
}
function hidePanels() {
$('.className').css({ display: 'none' });
}
And finally, if you don't worry about doing it via CSS, you could really shorten your script to this, which can't be any clearer:
function showPanels() {
$('.className').show();
}
function hidePanels() {
$('.className').hide();
}
Cheers!