For loop switches the current element to the latest one when adding an event listener [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 2 days ago.
I have this for loop:
for(tab of tabGroupMain.tabs) {
tab.button.addEventListener("click", function(ev) {
UI.ToggleTab(tab.content, tabGroupMain);
});
}
and if I decide to log the current tab it correctly returns me the current tab e.g.
Object { name: "Info", id: 1, button: span#infoTabButton.title.medium.bold.menuTitle, content: div#infoTabContent.menuTab }
however the moment I try to get the current tab in the addEventListener method it returns the latest element of the array, no matter which tab it currently is e.g.
Object { name: "Market", id: 0, button: span#marketTabButton.title.medium.bold.menuTitle, content: div#marketTabContent.menuTab }
(should return):
Object { name: "Info", id: 1, button: span#infoTabButton.title.medium.bold.menuTitle, content: div#infoTabContent.menuTab }
code for context:
new UI.Tab("Market", "marketTabButton", "marketTabContent");
new UI.Tab("Info", "infoTabButton", "infoTabContent");
new UI.TabGroup("Main", [UI.tabByName("Info"), UI.tabByName("Market")] );
is the code broken? in that case what can I do to fix it?

Not declaring a variable causes it to be declared as global (which is bad).
In your loop, tab will iterate over all the values and end on the last one. So, if you refer to it afterwards it will hold the value of that last item. This will happen both if you skip declaration (creating a global variable) or define it with var, which defines the variable in the function scope (if you are inside a function) or global scope (if outside).
To have the expected behavior, either use const or let, which define the variable in the block scope and are distinct from one iteration of the loop to another, or use forEach Array method like so (given tabGroupMain.tabs is an Array):
tabGroupMain.tabs.forEach(tab => {
tab.button.addEventListener("click", function(ev) {
UI.ToggleTab(tab.content, tabGroupMain);
});
}

Related

How to change a vue data variable using this and (multiple) dynamic bracket notations

I am trying to achieve the following:
I start of with a p element that contains the data variable "reportText.Scope_1_T_1", this variable contains the string: "Text to change";
On creation of this component, created() gets called and it fires off a call to the method createObject. The method createObject requires multiple arguments, but the only relevant argument is the second one, which includes the location of the data variable I want to change (in this case: "reportText.Scope_1_T_1");
The method createObject splits this argument/location based on the dots and returns an array. So the string "reportText.Scope_1_T_1" returns the array ["reportText", "Scope_1_T_1"];
Following that this array gets looped through and combined with the context (=this). First loop results in context = this["reportText"], second loop returns in context = this["reportText"]["Scope_1_T_1"].
After this I assign a new String to context (context = reply.fields)
My expectation was that this code would result in a change of the data variable this.reportText.Scope_1_T_1, but unfortunately nothing happens to this variable.
I have tried playing around with dot notation and bracket notation, but nothing really worked. For example if I try to change the code in my createObject method to this:
this.reportText.Scope_1_T_1 = "New String"; or
this["reportText"]["Scope_1_T_1"] = "New String";
It suddenly does work? I don't understand why. I even tried to see if I somehow make a copy of 'this' so it doesn't reference the same object, but as far as I see it doesn't make a copy. It does seems to be a reference problem, because it somehow points to a different location when I use my dynamic brackets.
Here is my relevant code(if you need more, please let me know):
<template>
<p>{{ reportText.Scope_1_T_1 }}</p>
</template>
<script>
export default {
data: function() {
return {
reportText: {
Scope_1_T_1: 'Text to change'
}
}
},
created() {
this.$store.getters.getAppPromise.then(app => {
this.createObject(app, 'reportText.Scope_1_T_1', 'String', '=irrelevantExpression');
})
},
methods: {
createObject(app, location, type, expression) {
if (type === 'String') {
app.createGenericOjbect(
{
fields: {
qStringExpression: expression
}
},
reply => {
let context = this;
location = location.split('.');
location.forEach(item => {
context = context[item];
});
context = reply.fields;
}
)
}
}
}
}
</script>
I would greatly appreciate it if anyone could help me figure out what the difference is between using my dynamically created context and a static context (like this: this["reportText"]["Scope_1_T_1"]). I think that's the key in solving this problem.
My code is based on this stackoverflow question:
Javascript Square Bracket Notation Multiple Dynamic Properties
It's just the final step that won't work. Assigning a new value to context at the end will just update that local variable, not the property of the object.
Instead what you need to do is grab a reference to the relevant object and then update the property. To grab the object you need to drop the final section from the location path. That final section is then the property name that needs to be updated:
let context = this;
const path = location.split('.');
const property = path.pop()
path.forEach(item => {
context = context[item];
});
context[property] = reply.fields;
The syntax used for property access hides some asymmetry in how the parts of the path are interpreted.
Consider this example:
const a = b.c.d.e
What happens is:
Start with b.
Grab the value in property c.
Grab the value in property d.
Grab the value in property e.
Assign that value to a.
All nice and symmetric, c, d and e all seems to work the same way.
Now consider flipping that example:
b.c.d.e = a
This is very different.
Start with b.
Grab the value in property c.
Grab the value in property d.
Assign a to the property e.
In this scenario the c and d properties are still just read operations but the e is handled totally differently. That final part is a write operation instead.
The key thing to appreciate here is that the final part of a 'path' like this is special when you want to set the value. Normally this hides behind the syntax but when you want to break it down like in your example you need to be conscious of what is actually going on.
Whether you use . or [] notation makes no difference to this behaviour, though to access properties dynamically you have to use [].

Why is this object changing before the function is run? [duplicate]

This question already has answers here:
Is Chrome’s JavaScript console lazy about evaluating objects?
(7 answers)
Closed 3 years ago.
I have an object with an array (of class objects) as one of its values. I have a function that, in part, runs a class method on one of the objects inside that array (within the object).
When I run my code, printing the array before and after the function, the change is both present before AND after the function runs.
Why is this happening? Hoisting?
As a test, I created another key:value pair in the object such that the value is an integer, and changed my function to just bump that integer up 1. Here, it works fine - the print of my object before the function has that integer as 1, and then afterward has the integer as 2.
I also tried NOT using a class method on the object to make the adjustment, and it still failed.
class Book{
constructor (color, title, pagecount){
this.color = color;
this.title = title;
this.pagecount = pagecount;
}
changePages() {
this.pagecount += 50;
}
}
let book1 = new Book("Red", "Book1", 100);
let book2 = new Book("Blue", "Book2", 200);
let book3 = new Book("Green", "Book3", 300);
var myBookArr = [book1, book2, book3]
var myObj = {arr: myBookArr, integerTest: 0}
function thisDoesStuff(){
//other operations not related to myObj
myObj.arr[0].changePages();
}
When I run the below, in BOTH console.logs, it shows that arr[0] (which is book1) has 150 pages.
console.log(myObj);
changePages();
console.log(myObj);
I am expecting the first console.log to show book1 as its original value, then the function changes it.
Please, hover on the icon with i letter on it near your log.
You will see, that Chrome (if you using Chrome, of course) will say:
Value below was evaluated just now
It happens because objects and other complex entities are being passed by reference, not by value. So, when you are expanding you log, browser getting up-to-date value of the reference.
Try to console.log copy of your value (e.g. console.log({...myObj})), or use JSON.stringify or other string-like representation of your object.
Note, that it is not error in your code. It is just a feature of the console.log, so (if I interpreted it correctly) your code works just fine :)
Your code is correct, it's all based off when the browser decides to evaluate the object for console.log output.
See post here that another user was having a similar question:
Weird behavior with objects & console.log

ES6 Fat Arrow and Parentheses `(...) => ({...})` [duplicate]

This question already has answers here:
ECMAScript 6 arrow function that returns an object
(6 answers)
Closed 6 years ago.
I've been working through some Graph QL/React/Relay examples and I ran into some strange syntax.
When defining the fields in Graph QL Objects the following syntax is used:
const xType = new GraphQLObjectType({
name: 'X',
description: 'A made up type for example.',
fields: () => ({
field: {/*etc.*/}
})
});
From what I gather this is just defining an anonymous function and assigning it to xType.fields. That anonymous function returns the object containing the field definitions.
I'm assuming with however the Graph QL schema mechanism works this has to be defined as a function returning an object rather than simply an object. But the part that has me confused is the parenthesis around the curly braces.
Is this to differentiate an object definition from a function definition? Is it for clarity's sake for the reader?
The only similar syntax a google search has found is in the airbnb style guide where it seems to be a readability/clarity thing.
Just looking for confirmation or an explanation beyond my assumptions as I start to play around with Graph QL a little more.
fields: () => ({
field: {/*etc.*/}
})
is a function that implicitly returns an object (literal). Without using () JavaScript interpreter interprets the {} as a wrapper for the function body and not as an object.
Without using parens: (), the field: ... statement is treated as a label statement and the function returns undefined. The equivalent syntax is:
fields: () => { // start of the function body
// now we have to define an object
// and explicitly use the return keyword
return { field: {/*etc.*/} }
}
So parents are not there for clarity. It's there for using implicit-returning feature of arrow functions.
It's for clarity's sake for the compiler as well as for the reader. The field: syntax in your example appears to be an unambiguous giveaway that this is an object literal, but take this code for instance:
let f = () => {
field: 'value'
}
console.log(f()) //=> undefined
You would expect this to log an object with field set to 'value', but it logs undefined. Why?
Essentially, what you see as an object literal with a single property, the compiler sees as a function body (denoted by opening and closing curly braces, like a typical function) and a single label statement, which uses the syntax label:. Since the expression following is it just a literal string, and it is never returned (or even assigned to a variable), the function f() effectively does nothing, and its result is undefined.
However, by placing parentheses around your "object literal," you tell the compiler to treat the same characters as an expression rather than a bunch of statements, and so the object you desire is returned. (See this article on the Mozilla Development Network, from the comments section.)
let g = () => ({
field: 'value'
})
console.log(g()) //=> { field: 'value' }

Implicitly global "item" variable - difference between Internet Explorer and FireFox

Just out of curiosity..
I have this JS code:
var someExternalArray = [{id: 1, name: 'a'}, {id: 2, name: 'b'}, {id: 3, name: 'c'}];
var newArray = []
//var item;
for (var i = 0; i < someExternalArray.length; i++){
item = new Object();
item.id = someExternalArray[i].id;
item.name = someExternalArray[i].name;
newArray.push(item);
}
alert('0:' + newArray[0].name + ',1:' + newArray[1].name + ',2:' + newArray[2].name);
Notice the commented var item which leaves the loop with implicitly declared item variable.
If I run this code on FireFox, the result of alert is: 0:a,1:b,2:c
If I run the same code in Internet Explorer, the result is:
0:c,1:c,2:c
Here is jsfiddle: https://jsfiddle.net/fvu9gb26/
Of course, when I uncomment the var item it works the same way in every browser.
Does anyone know why this difference occurs? Thank you.
Basically, that's because Internet Explorer's window object exposes an item() method that your script cannot overwrite.
In the line:
item = new Object();
item is not declared in the local scope, so it is interpreted as a property of the global object (window.item). On Firefox, window does not expose a member named item, so a new member is introduced and the result of new Object() is assigned to it.
On the other hand, Internet Explorer exposes a native method named window.item(). That member is not writeable, so the assignment cannot take place -- it is silently ignored. In other words, item = new Object() has no effect at all (well, it does create an object but cannot assign it afterwards).
The subsequent assignments to id and name end up creating new members of the item() method. These are always the same members of the same method, so their values are overwritten on every loop iteration. In addition, the same object (the item() method) is pushed to the array on every iteration.
Therefore, the array ends up containing three times the same object, and the values of its id and name members are the last values assigned to them (in the last iteration), respectively 3 and 'c'.
This is tricky. For some obscure reason, Internet Explorer has a native method called item in the global context window (if someone knows why, you're welcome to share: I can't find it in the documentation). So, when you use the identifier item without declaring a variable, it refers to this method.
The first instruction in the loop (item = new Object();) does nothing because window.item is a readonly property. So, after this instruction, window.item is still the native function.
The next instructions (those which sets id and name) works, because while the property window.item is readonly, the Function object it's pointing to can be modified.
After the loops, you added the same Function object three times, and only the last iteration counts because you override the id and name properties everytime.

How to use scope variables as property names in a Mongo Map/Reduce emit

There is a question (and answer) that deals with the general case. I am having difficulty using a scope variable as a field key (as opposed to the field value)
In the example below all the FULLY_CAPS fields are scope variables. In the case of SERVICE and IDENTIFIER the emit correctly uses the value of the scope variable as it is passed to the M/R.
However when I try to use the value of a scope variable as a key in the emitted document, the document is created with the scope variable name (as opposed to it's value).
return emit({
service: SERVICE,
date: _this.value.date,
identifier: _this.value[IDENTIFIER]
}, {
errors: {
count: 1,
type_breakdown: {
SINGLES_ONLY: {
count: 1
}
}
}
});
Is there a way around this problem?
When using the shortcut syntax for creating objects in JavaScript, the left hand side/property name is always interpreted as a literal value, regardless of quotes.
For example:
var d={ name: "Aaron" }
Is equivalent to:
var d={ "name" : "Aaron" }
As there are two ways to set a property value:
obj.propertyName=value
obj["propertName"]=value
You have to construct your object using the second syntax, at least in part.
var errors={
count: 1,
type_breakdown: { }
}
};
var countObj={ count:1 };
errors.type_breakdown[SINGLES_ONLY]=countObj;
// pass results to emit call

Categories

Resources