I'm very new to javascript, and can't figure this seemingly simple issue. I have an array of elements (say usernames) that i iterate over. i want to map these usernames to click event methods (i know this isn't the perfect way, but it's just something that seems to work):
this.events = this.events || {};
var self = this;
for (var i=0; i<arr.length; i++) {
console.log(data.user[i]); // < --- this username is correct
var username = data.user[i];
$(this.el).append("<td><button class='btn btn-primary save" + i + " btn-sm'>Save</button></td>");
var eventKeySave = 'click ' + '.save' + i;
this.events[eventKeySave] = function(){
(function (username) {
console.log("inside method save " + username); // <--- this username is wrong
self.onSave(username, 'something')
})(username);
};
}
this.delegateEvents();
now, in my onSave method, it just merely prints the usernames:
onSave: function(name, extras) {
console.log("you clicked save on " + name + " , " + type); // < -- wrong name
}
But why do i ALWAYS get only the LAST username of the array in my onSave function?
for example, if my array looks like [ 'david', 'emily', 'stephanie', 'michelle'], only michelle gets passed in to the onSave function, despite the fact that each button's click event should have been set with the respective name in the array.
i even tried to pass the username by value but it still doesn't work. what is going on in Javascript that i'm not seeing?
You need a closure:
this.events[eventKeySave] = (function (name) {
return function(){
console.log("inside method save " + name); // <--- this username is now ok
self.onSave(name, 'something');
};
})(username);
It happens because the function is called after that the for loop ended, so the value of "username" is the last value of the array in all cases.
Add a closure save the actual value of 'username' for that returned function
Related
I have an important question that I'm starting to face on 80% of the cases.
Lets say I have this:
var bcount = properties.count;
favicon.badge(bcount);
bcount.change(function() { favicon.badge(bcount); });
properties.count = its a number that changes depending on the user actions.
favicon.badge its a javascript that shows the action, which is working good.
I tried to use .change on the bcount var, but is giving me error as I supposed because is not an element.
Is there any way to listen a var when value changes?
The problem I'm facing is that when the count gets updated, with a new number. It only updates after refreshing the page.
Thanks!!
Edit: I'm trying to setup getter and stter:
var bcount = {
a: properties.count,
get b() {
return this.a;
},
set c(cname) {
this.a;
}
};
Is that okay? And now how i can init the call?
I think you need get and set functionality. You can put logic in those functions and they will do what you want in variable get and set.
See this: es5-getters-setter
Sample from above link:
var person = {
firstName: 'Jimmy',
lastName: 'Smith',
get fullName() {
return this.firstName + ' ' + this.lastName;
},
set fullName (name) {
var words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
}
person.fullName = 'Jack Franklin';
console.log(person.firstName); // Jack
console.log(person.lastName) // Franklin
Edit:
Every field has its get and set. If you want to make other confused when they read your code do that. but the right thing is this:
var properties = {
_count:0, // you hold count value here
get count() { // every time you get "properties.count" this function will run
// place for your code that must run on get, like "var bcount = properties.count"
return this._count;
},
set count(new_count) { // every time you want to do "properties.count = some_int" this function will run
// place for your code that must run on set, like "properties.count = some_int"
this._count = new_count;
}
};
// usage
properties.count = 10; // "properties._count" is "10" now
var bcount = properties.count; // you just got "properties._count" value
It looks like my for loop is looping the last object parsed into all fields.
http://codepen.io/anon/pen/EKxNaN
This is the actual code I am using, I built something kind of similar for codepen since I cannot request JSON from the actual source on codepen.
var championMasteryPHP = "https://api.myjson.com/bins/xked";
$.getJSON(championMasteryPHP, function (json) {
for (var i = 0; i < json.length; i++) {
var champID = json[i].championId;
var champLevel = json[i].championLevel;
var pointstonextlevel = json[i].championPointsUntilNextLevel;
var championInfo = "http://example.com/champInfo.php?champid=" + champID;
$.getJSON(championInfo, function (json2) {
var champName = json2.name;
var champTitle = json2.title;
$('#champ').append("<li>ID: " + champID + " | Name: " + champName + " | Level: " + champLevel + " | Points to Next Level: " + pointstonextlevel + "</li>");
});
};
});
Long story short, what I am trying to achieve would look like this.
But for some reason, this is what I get instead.
The right names, but the other variables are the very last variable in the listing.
Is there a better way to do this? Am I doing something terribly wrong?
Thanks.
You are using Asynchronous Process inside a javascript for loop more info here
You can modify to use self executing function, so it will work
var championMastery = "https://api.myjson.com/bins/xked";
$.getJSON(championMastery, function (json) {
for (var i = 0; i < json.length; i++) {
(function(obj){
var champID = obj.championId;
var champLevel = obj.championLevel;
var pointstonextlevel = obj.championPointsUntilNextLevel;
var championInfo = "http://example.com/champInfo.php?champid=" + champID;
$.getJSON(championInfo, function (json2) {
var champName = json2.name;
var champTitle = json2.title;
$('#champ').append("<li>ID: "+champID+" | Name: " +champName+ " | Level: " +champLevel+ " | Points to Next Level: " +pointstonextlevel+ "</li>");
});
})(json[i])
};
});
A very good article which I came across recently. Detailing about the Javascript Scope ans Closures . This is a must know things for coding in javascript, Jquery. And the part where there is an explanation to this above problem is look for the topic under The Infamous Loop Problem
Now coming to the problem. Your code should actually look like
$.getJSON(championInfo, function (json2) {
(function(){
var champName = json2.name;
var champTitle = json2.title;
$('#champ').append("<li>ID: "+champID+" | Name: " +champName+ " | Level: " +champLevel+ " | Points to Next Level: " +pointstonextlevel+ "</li>");
})()
});
Note the change where I have added the self executing function. The problem you got the weird output with the same outputs for all the function is because.
Your function Inside the $.getJSON call back is just a function definition and the function is not executed at that moment. By the time your first getJSON hits its call back your for loop is complete, And the final value in the variables are of the last loop and hence when your first getJSONhits its callback it displays the values of the last loop.
To make it more simple. Write this in your console window.
function alert1(){ alert("I am one!!");}
And now call / execute the function by doing
alert1();
You will get a alert saying "I am one!!"
Now add this to the console
function alert1(){ alert("I am one, But overritten!!");}
And then try out calling it. The out put would be "I am one, But overritten!!", So the function actually gets overwritten and that's what is happening in the for loop. The last function is the one which is available for all the callbacks.
I have an array of objects (specifically easelJS images) - something like this:
var imageArray = new Array;
gShape = new createjs.Shape();
// shape is something
imageArray.push(gShape);
What I want to do is have an event listener instead of:
gShape.addEventListener("click", function() {alert"stuff"});
I want the program to know specifically which region is clicked so that I can send an alert box in the following way:
imageArray[index].addEventListener("click", function(){
alert " you clicked region number " + index}
Sure. You can just use a closure to save the index of that iteration. Otherwise there are shared by the same function scope and will give you the value of the same iteration. Creating a separate function for each will save the state of that inside the function.
var imageArray = new Array;
gShape = new createjs.Shape();
// shape is something
imageArray.push(gShape); // Dumped all the objects
for (var i = 0; i < imageArray.length; i++) {
(function(index) {
imageArray[index].addEventListener("click", function() {
console.log("you clicked region number " + index);
})
})(i);
}
or better
for(var i = 0; i < imageArray.length; i++) {
imageArray[i].addEventListener("click", bindClick(i));
}
function bindClick(i) {
return function() {
console.log("you clicked region number " + i);
};
}
ES6 to the rescue
let imageArray = [];
gShape = new createjs.Shape();
// shape is something
imageArray.push(gShape); // Dumped all the objects
for (let i = 0; i < imageArray.length; i++) {
imageArray[i].addEventListener("click", function() {
console.log("you clicked region number " + i);
});
}
Using the let keyword creates a block scoping for the variable in iteration and will have the correct index when the event handler is invoked.
Something like this should work:
for (var i = 0 ; i < imageArray.length ; ++i) {
function(index) {
imageArray[index].addEventListener("click", function() {
alert ("You clicked region number: " + index");
});
} ( i);
}
The reason it works is because it creates a closure that holds the value of index that will be shown in the alert message. Each time through the loop creates another closure holding another value of index.
//gShape must be an array of HTMLElement
gShape.forEach(element => element.addEventListener("click", function () {
// this, refers to the current element.
alert ("You clicked region number: " + this.getAttribute('data-region'));
}));
Sure, a closure is the solution, but since he's got Ext loaded he might as well use it and get some very readable code. The index is passed as the second argument to Ext.Array.each (aliased to Ext.each).
Ext.each(imageArray, function(gShape, index) {
gShape.addEventListener("click", function() {
alert("You clicked region number " + index);
});
});
This is what I'm using for div id's:
var array = ['all', 'what', 'you', 'want'];
function fName () {
for (var i = 0; i < array.length; i++)
document.getElementById(array[i]).addEventListener('click', eventFunction);
};
Good Luck!
A simple way to do this, is by calling a querySelectorAll() on all
the elements and using a loop to iterate and execute a function with the data of that specific array index once the EventListener is
triggered by the element clicked.
Snippet
Retrieving the id attribute of the clicked element
document.querySelectorAll('li').forEach(element => {
element.addEventListener('click', () => {
console.log(element.getAttribute('id'))
})
})
li{cursor:pointer}
<ul>
<li id="id-one">One</li>
<li id="id-two">Two</li>
<li id="id-three">Three</li>
<li id="id-four">Four</li>
</ul>
I declare a variable, outside of a function like so:
var vitalsValuesChecked = [];
then inside of a function I do:
vitalsValuesChecked.push('foobar');
In a later function I need to loop through the array for the items pushed, and constantly am not getting the result I expect. So inside that same function, I added console.log(vitalsValuesChecked); which returns [].
EDIT Code sample below;
EDIT 2 Fixed code below
var vitalsValuesChecked = [];
$(document).delegate("#hv-byresult-btn", "click", function () {
var vitalsTypeList = ['bp', 'ht', 'wt', 'pulse', 'resp', 'temp'];
vitalsValuesChecked = [];
for (var i = 0;i < vitalsTypeList.length;i++) {
if (document.getElementById(vitalsTypeList[i]).checked == true) {
vitalsValuesChecked.push(vitalsTypeList[i]);
console.log(vitalsTypeList[i] + " is checked. Adding to global array");
}
}
$('#vitals-measures-content').empty();
navigate("#vitals-measures");
for (var i = 0;i < vitalsValuesChecked.length;i++) {
console.log("vitalsValuesChecked at index " + i + " is " + vitalsValuesChecked[i]);
}
readRec('clinicalObservation', null, sortVitalsByResult);
});
function foobar() {
console.log(vitalsValuesChecked); //return []
for (var i=0;i < vitalsValuesChecked.length;i++) {
var valueSelected = vitalsValuesChecked[i];
console.log("Value of vitalsValuesChecked at index " + i + " is " + vitalsValuesChecked[i]);
}
}
You have defined vitalsValuesChecked twice which is a problem. One is global and one is local to the delegate() callback. The local definition overrides the global definition so when you thought you were setting values into the global variable, you were not - you were just changing the local variable which has a limited lifetime and thus your data was not available later in the global variable.
You should remove the
var vitalsValuesChecked = [];
inside the delegate handler so all modifications occur on the single global variable.
The var vitalsValuesChecked = []; inside the function will create a local variable. I don't think you want this if you're trying to push to the global variable.
I am new to knockoutjs and I have an uber-basic question for you:
I have been able to successfully subscribe to a user changing the on screen twitter handle AND successfully fetch the tweets and display the last recent tweet of a user using console.log(json.results[0].text); However I am not confident that my observable array is working, when I push the json.results into recent tweets: recent_tweets.push(json.results[0].text) I see an [] empty array.
What is going on? Is logging ko.observable array possible?
console.log("TwitterFeedComponent loaded")
TwitterFeedComponent = function(attributes) {
if (arguments[0] === inheriting)
return;
console.log("TwitterFeedComponent() loaded")
var component = this;
var url = 'https://twitter.com/search.json?callback=?';
this.attributes.twitter_user_handle.subscribe(function(value) {
alert("the new value of the twitter handle is " + value);
console.log("I have loaded")
var url = 'https://twitter.com/search.json?callback=?';
var twitter_parameters = {
include_entities: true,
include_rts: true,
q: 'from:' + value,
count: '3'
}
$.getJSON(url,twitter_parameters,
function(json) {
result = json.results[0].text
recent_tweets.push(json.results[0].text);
console.log(recent_tweets);
console.log(json.results[0].text);
});
});
};
To access the actual values of an observable whether it's an array or not you need include parenthesis. For example the following will work:
var recent_tweets= ko.observableArray(["hello", "hi", "how are you"]);
console.log(recent_tweets());
The same is true when assigning variables.
Here is an example of a regular scalar value:
var myObservableName = ko.observable("Luis");
myObservableName("Dany"); // changes the name to: Dany
var Name = myObservableName(); // gets the value and assigns it to the variable Name (in this case the value is "Dany")
To answer this a little differently, you could always use Knockout's subscribe() functionality. Let's assume you have the following view-model:
App.MyViewModel = function() {
var self = this;
self.TestProperty = ko.observable(null);
}
For demonstrations sake, let's assume this property is bound to a text field, as follows:
<input type="text" id="TestPropertyField" data-bind="textInput: TestProperty" />
Now let's assume that you'd like to log any time this value changes. To do this, simply update your view-model as follows:
App.MyViewModel = function() {
var self = this;
self.TestProperty = ko.observable(null);
self.TestProperty.subscribe(function(newValue){
console.log("The new value is: " + newValue);
});
}