whats wrong with this ternary operator? - javascript

I have an object menuNames which should maintain a list of menu items. If menuNames already has the slug, increment the value, if it doesnt contain the slug, set the value equal to 1. I'm doing this to track unique names. I want to end up with something like:
menuNames: {
home: 1,
products: 10,
contact: 1
}
this doesnt work (this would be contained in a loop going through each slug):
menuNames[slug] = (menuNames.hasOwnProperty(slug) ? menuNames[slug]++ : 1);
//this sets every value to 1
but this does work (this would be contained in a loop going through each slug):
if(menuNames.hasOwnProperty(slug)) {
menuNames[slug]++;
} else {
menuNames[slug] = 1;
}

menuNames[slug]++ increments the value, but also returns the original value.
You are doing menuNames[slug] =, so the value is set back to the original value after being incremented.
To fix it, just simply do:
menuNames[slug] = (menuNames.hasOwnProperty(slug) ? menuNames[slug]+1 : 1);
Or:
(menuNames.hasOwnProperty(slug) ? menuNames[slug]++ : menuNames[slug] = 1);

I guess it could work like this:
menuNames[slug] = (menuNames.hasOwnProperty(slug) ? ++menuNames[slug] : 1);

As the other answers say the problem is in the post increment.
Another way to write it is:
menuNames[slug] += (some_bool ? 1 : 0);
++ is very sensitive to bugs. Try to write it as a += statement.
if menuNames[slug] can be undefined, write it as:
menuNames[slug] = 0;
if (some_bool) {
menuNames[slug] += 1;
}
This is (in my opinion) the clearest way to write an initialization/counter loop.
If you like one-liners you'll cringe, but if you like bug free code you'll be happy to see this.

Related

javascript hashmap one line modify or create

I want to know if there is a clean way to modify a value in a hashmap or create it if it doesn't exist without doing an if block. Example of what i'm currently doing
let dict = {}
if(dict['key']){
dict['key'] += 1
} else {
dict['key'] = 1
}
Want to know if there is a cleaner way to do what I did above.
Use dot notation, because the property isn't dynamic, and alternate dict.key with 0 before adding 1.
dict.key = (dict.key || 0) + 1;

Push string to array if the array you want to push from is undefined

I am not a coder, I am messing around with some JavaScript as part of modding a game, so bear with me. This game supports es5/everything Chromium 28 supported.
I had code which pushed a string to an array from a variable, and when the variable was undefined a fixed string was pushed instead:
slotsArray.push({
landing_policy: ai.landing_policy || 'no_restriction'
});
The setup changed such that where ai.landing_policy was set it would contain multiple values, so it become an array. When it wasn't set only a single entry was required.
The same code does not appear to work where an array is in place:
for (var i = 0; i < count; i++) {
slotsArray.push({
landing_policy: ai.landing_policy[i] || 'no_restriction'
});
}
An error is produced because it's trying to check a value from a variable that hasn't been defined. I expected that to cause it to use the fixed value, but apparently that's not what happens, it just fails.
I've changed my approach to the code seen below in full:
if (Array.isArray(ai.landing_policy)) {
for (var i = 0; i < count; i++) {
slotsArray.push({
landing_policy: ai.landing_policy[i]
});
}
}
else {
slotsArray.push({
landing_policy: ai.landing_policy || 'no_restriction'
});
}
This code works, but what I'm looking to understand is whether this was the best solution? The old method felt elegant, while the new one looks a little clumsy.
You can use the ternary operator(? :).
It will return the second value if the first is true, and the third otherwise.
I've used array instanceof Array instead of Array.isArray(array) to support ES5.
var isArray = ai.landing_policy instanceof Array
for (var i = 0; i < (isArray ? count : 1); i++) {
slotsArray.push({
landing_policy: isArray ? ai.landing_policy[i] : ai.landing_policy || 'no_restriction'
});
}
Elegant solution not always converse to the most readable/desirable. I would probably do something like:
const formattedPolicy = ai.landing_policy.map(policy => policy || 'no_restriction');
slotsArray = [...formattedPolicy ];
Course this has to imply that the ai.landing_policy is always an array. If you need to double check first you could also do:
const formattedPollicy = ai.landing_policy.constructor === Array
? ai.landing_policy.map(policy => policy || 'no_restriction');
: [ai.landing_policy]
Looks like an elegant or short imho but your code is way more readable.

Javascript, Comparing objects with indexOf

I am trying to compare a current object to an array of id's coming in. The basic idea is that if the object has the same idea as anything inside the recived ID array, then I would like to set a boolean of selected to true. I was pointed in the direction of using a for each with an indexOf inside to check against. Here is my Attempt -
angular.forEach($scope.applicationsHere, function(index) {
if(data.applications.indexOf(index.id){
index.selected = true;
}
});
So what I am tyring to do is check the applications here against the data.applications. If the applicationsHere has an object with .id that matches one of the numbers in data.applications (data.applications is just an array of ids like [1,2,3]), then set the .selected to equal true.
I do not believe I have this logic correct, if anyone could help correct me I would much appreciate it. Thanks for reading!
if(data.applications.indexOf(index.id){ // this is missing a parenthesis
This line has the following actual behavior (thanks #Pointy for clarifying all the options)
Not found (-1) = true
First Element (0) = false
Any other element (1 to n) = true
From your question, your expected output is:
Not found (-1) = false
Found (0 to n) = true
If you're attempting to use JS' 0 = false, anything else is true, then you can do:
angular.forEach($scope.applicationsHere, function(index) {
if(data.applications.indexOf(index.id) + 1) {
index.selected = true;
}
});
Or, even shorter:
angular.forEach($scope.applicationsHere, function(index) {
index.selected = (data.applications.indexOf(index.id) + 1);
});
That being said, I would still recommend doing an actual >= 0 check for indexOf. Coercing like this causes confusion for other people reading the code since you're using an index for a boolean output. You can use a ternary operator if you're looking for compactness too.
angular.forEach($scope.applicationsHere, function(index) {
index.selected = data.applications.indexOf(index.id) >= 0 ? true : false;
});

If-statement fail in javascript

For several hours now am I trying to make a simple game, but one if-statement is failing:
function checkDiagonaal() {
if (document.getElementById("11").src.indexOf("o.png") &&
document.getElementById("22").src.indexOf("x.png") &&
document.getElementById("33").src.indexOf("o.png"))
{
winnaar = true;
}
}
The condition is not true, yet the variable winnaar is set on true. I don't see what I am doing wrong. Very probably just a little mistake.
I also tried this code:
if(document.getElementById("11").src === "images/o.png")
but this returns false (even when the condition is true). I would like to know why?
Use ...indexOf(...) >= 0 in such conditions.
indexOf returns -1 when the value is not found, -1 is truthy
From the MDN(great resource!):
"The indexOf() method returns the index within the calling String
object of the first occurrence of the specified value [...] returns -1
if the value is not found."
When statements get big they become a bit unreadable, it might be fine now, but if you need to add more checks, I would suggest a different approach:
function checkDiagonaal() {
var ids = [11,22,33];
var strs = ['o.png','x.png','o.png'];
var winnar = ids.every(function(id,i) {
return document.getElementById(id).src.indexOf(strs[i]) > -1;
});
}

Finding in a predefined set of text options

Say, I want to see if a DOM element is a block. I can write it in three ways, depending on my mood:
// first way
if (el.currentStyle.display == "block" || el.currentStyle.display == "inline-block" || el.currentStyle.display == "table-cell")
// second way
var blocks = {"block": 1, "inline-block": 1, "table-cell": 1};
if (el.currentStyle.display in blocks)//
// third way
if (el.currentStyle.display.match(/block|inline-block|table-cell/))
I have mixed feeling about all of them. First is too verbose once I have more than one option. Second contains those arbitrary values in the object (where I put 1s this time). Third looks like overkill. (What exactly is bad about overkilling?)
Do you know another, better way? If no, any cons I am missing about these three ways?
Javascript only, please.
I like the third way; I don't think it looks like overkill at all. If you need an even shorter way then this works too:
el.currentStyle.display.match(/(e-)?(block|cell)/)
But that's not very readable...
It might be worth abstracting it all away by extending the String prototype:
String.prototype.matches = function(what) {
return (',' + what + ',').indexOf(',' + this + ',') > -1;
};
// Using it:
el.currentStyle.display.matches('block,inline-block,table-cell');
If we're primarily aiming for readability, and if this is happening more than once -- perhaps even if it is just once -- I'd move the test to a function. Then define that function whichever way you like -- probably option 1, for max simplicity there.
Overkill? Possibly. But a gift to the programmer who wants to scan and understand the code 6 months from now. Probably you :-)
function isBlock(el) {
return (el.currentStyle.display == "block" ||
el.currentStyle.display == "inline-block" ||
el.currentStyle.display == "table-cell");
}
// ...
if (isBlock(el)) {
// do something
}
Can't you use the 2nd way but check if it's undefined and then skip the ": 1" part. I haven't tested though.
It looks like you need an inArray function, here is one from the top search result:
Array.prototype.inArray = function (value) {
var i;
for (i=0; i < this.length; i++) {
if (this[i] === value) {
return true;
}
}
return false;
};
Then the forth way would look like this:
if (['block','inline-block','table-cell'].inArray(el.currentStyle.display))
Or in a more readable manner:
var isBlock = ['block','inline-block','table-cell'].inArray(el.currentStyle.display);
My prefered solution for this is:
'block||inline-block||table-cell'.indexOf( el.currentStyle.display ) >= 0
I think that this will use native code of the string and be way more efficient than the array & iteration method.

Categories

Resources