Javascript: Calling a function from an array of objects - javascript

I have an array of objects and I am building a page with those objecs
The objects have an image, image will be clickable and I want to count the clicks per image.
I am trying to assign the value for clicks from each object to "tclicks" and than hand "tclicks" over to the onclick function to count the clicks separately.
I am not sure if that works.
My current problem is that the value appears as NaN when the onclick function gets executed.
I am aware that I need to assign a starting value to clicks.
I tried it in various places.
In the object, in the function outside of the function. Either the value was not counting up, or I got an error as the element was set to 0
Where can I assign the starting value?
This is the array
var things =
[
{img : "cat.jpg",
tclicks: "cleo_clicks",
id : "cleo"
},
{img : "shimi.png",
tclicks: "shimi_clicks",
id : "shimi"
}
]
This is how I am building the page
for ( var i= 0; i < things.length; i++){
var x = document.createElement("IMG");
x.setAttribute("src", things[i].img);
x.setAttribute(tclicks, things[i].clicks);
x.setAttribute("onclick", "countClicks(tclicks)");
document.body.appendChild(x);
}
And this is my onclick functions
function countClicks(clicks){
clicks ++;
console.log(clicks)
}

There's no reason you can't assign clicks=0 where you define the array of objects like this:
var things = [
{
img: "cat.jpg",
tclicks: "cleo_clicks",
id: "cleo",
clicks: 0
},
{
img: "shimi.png",
tclicks: "shimi_clicks",
id: "shimi",
clicks: 0
}
];
...but that's only half the problem.
In your click-increment function:
function countClicks(clicks) {
clicks++;
console.log(clicks);
}
...you will never affect a change on any object's click property because Javascript passes function parameters by value (https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_value).
You need to pass in the entire object to the function and allow it to modify the property value instead:
function countClicks(someObject) {
someObject.clicks++;
console.log(someObject.clicks);
}
...which means you need to change how the function is called, something like:
x.setAttribute("onclick", "countClicks(things[i])");
Note: above assumes things array is global, otherwise you'll need to further refactor.
Final note, calling JS pass-by-value is simplifying a little. You ca dive into the issue more here: Is JavaScript a pass-by-reference or pass-by-value language?

Yep. Use an object and don't write it hard as onClick. It's too dangerous. You could override it. Use an eventListener instead.
function CountClicks(config){
var img = config.img;
var tclicks = config.tclicks;
var id = config.id;
var clicks = 0;
return{
countUp: function(){
clicks++;
},
countDown: function(){
clicks--;
},
getCount: function(){
return clicks;
}
}
}
x.counter = new CountClicks(things[i]);
x.addEventListener('click', function(){this.counter.countUp()});

Related

Javascript event listener structure

I’m working on a code to get the index of a clicked element so it can add or remove a class to display or hide the information. For it I used for for iteration. But I don’t understand why is there an (i) after the event handler. I’m kind a newbie to coding so I want to understand everything.
Here’s the JavaScript code:
for (let i = 0; i < questions.length; i++) {
questions[i].addEventListener(‘click’,((e) => {
return function() {
if (clic[e].classList.contains(‘q-answered)) {
clic[e].classList.replace(‘q-answered’, ‘q-answeredno’);
} else if (clic[e].classList.contains(‘q-answeredno’)) {
clic[e].classList.replace(‘q-answeredno’, ‘q-answered’);
}
}
})(i))
}
Let's start by looking at what's happening as though you were using var to iterate through your questions
Put simply, it's making it an immediately-invoked function expression (or IIFE for short) and passing in a parameter you normally wouldn't otherwise have access to.
When a click event handler callback expects a function with a single variable. When the event is handled, the function is invoked and the JS runtime provides a pointer event back to your function to do something with. That's all well and good, but here you want to know the offset of the clicked element in the array from information gleaned out of the scope of this callback.
So you instead change the callback shape. You pass in your own function and wrap it in parentheses. In JS, you pass in the arguments to the function in parentheses following the definition. Your example uses the lambda syntax, so you start with the function itself:
(e) => {return ...;}
But if this is all that were passed in here, you'd get a PointerEvent assigned to e as it matches the anticipated callback shape. So you instead need to wrap this in parentheses:
((e) => {return ...;})
Great, but you want this function to have a very particular value passed in when it executes, so you define the arguments at the end. You're using i as the variable identifying the index of the offset element, so we'll pass that in here.
((e) => { return ...; })(i)
Now, this means that when your event handler function is invoked for the first element, it'll practically look like the following:
((e) => {return ...; })(0); //Zero for the first zero-based index
This precludes the event handler callback assigning its own value to your first variable and means that now the function will be invoked, you'll pass the 0 (or otherwise set index property value) to your e argument and the remainder of the return statement will execute accordingly.
What's this closure thing I've heard of and how might it apply here?
#t.niese brings up a great point in the comments that I originally missed about closures and why they're quite relevant here.
Put simply, in JavaScript, you can refer to variables that are defined within a function's scope, to variables of the calling function's (e.g. parent) scope or any variables on the global scope. A closure is any function that's able to keep these references to these variables regardless of whether the parent has already returned or not.
If I have something like the following:
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
console.log(`I like ${fruits[a]}${suffix}`);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
As you'd expect, despite assigning suffix outside of the loop, I'm able to use it within the loop to make each fruit name plural. But I can do this using an inner function as well:
function listFruits(fruits) {
var suffix = "s";
function pluralizeName(name) {
return `${name}${suffix}`;
}
for (var a = 0; a < fruits.length; a++) {
var pluralName = pluralizeName(fruits[a]);
console.log(`I like ${pluralName}`);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
Again, despite suffix being assigned in the parent of the pluralizeName function, I'm still able to assign it in the return string to log to the console in my loop.
So, let's put a timeout callback in there and see what happens:
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
setTimeout(function() {
console.log(`I like ${fruits[a]}${suffix}`);
}, 1000);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like undefineds"
// >> "I like undefineds"
// >> "I like undefineds"
Why didn't we list the fruit we like here as before? a is still being defined in the parent and it does attach the suffix value as it should, so what gives?
Well, our loop starts at a = 0, sets the timeout to execute the callback in 1000ms, then increments a to 1, sets the timeout to execute the callback in 1000ms, then increments a to 2, sets the timeout to execute the callback in 1000ms, then increments a to 3, sets the timeout to execute the callback in 1000ms, then increments a to 4 at which point a is greater than the number of fruits passed in (3) and the loop breaks out. But when the timeout callbacks run then, a now has a value of 4 and fruits[4] is undefined, so we see that in the console logs.
Ok, but we want to be able to reference the value of our iterating index locally so the first callback works against the first object, the second against the second and so on, so how do we make that available to the callback? We use the IIFE approach covered above.
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
(function() {
var current = a;
setTimeout( function() {
console.log(`I like ${fruits[current]}${suffix}`);
}, 1000);
})();
}
}
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
It works here because we create a new closure for each of the functions created by the loop. When the function is created, we take the current value of a from the parent and assign it to a local variable so that when the setTimeout method fires later, it uses that local current value to properly access the intended index of the fruits array.
But rather than capture the variable as I do above in var current = a;, I can instead pass the a variable into the IIFE as a parameter and it will have exactly the same effect:
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
(function(current) {
setTimeout( function() {
console.log(`I like ${fruits[current]}${suffix}`);
}, 1000);
})(a);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
Our IIFE populates the current variable with the argument a passed in making it available locally and we get the expected outcome.
But my sample uses let, so how does that change anything?
Prior to ES6, we had only the global and function scopes requiring the use of the IFFEs to introduce local function scopes as we saw above. With ES6, we got a new "block scope" which essentially scopes everything within two curly braces, including any number of child blocks within that, but only if the variable is assigned using the const or let keywords. var still only assigns to a global or function scope.
Let's revisit the example above in which we received all the undefined values and replace our use of var with let.
function listFruits(fruits) {
var suffix = "s";
for (let a = 0; a < fruits.length; a++) { //Note the change to 'let' here
setTimeout(function() {
console.log(`I like ${fruits[a]}${suffix}`);
}, 1000);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
And this time it works because the value of a persists as assigned to the setTimeout callback's block scope.
Ok, so what about my sample?
Let's bring it back full-circle then. If you were using var, your sample scopes the present value of i to the IIFE so each of your event handler callbacks can access the appropriate offset node.
But since you're using let, the use of an IFFE certainly won't hurt anything, but it's unnecessary as the intended value of i is available to your callback functions via their block scope. As such, you can simplify to remove the IIFE and suffer no consequences.
for (let i = 0; i < questions.length; i++) {
questions[i].addEventListener(‘click’, function() {
if (clic[i].classList.contains(‘q-answered)) {
clic[i].classList.replace(‘q-answered’, ‘q-answeredno’);
} else if (clic[i].classList.contains(‘q-answeredno’)) {
clic[i].classList.replace(‘q-answeredno’, ‘q-answered’);
}
});
}
Should you have questions about any of this, please leave a comment and I'd be happy to edit to address it.

How to read a dynamic string as an object reference

I need to set a button's value as a dynamic string. This string should be turned into an object reference. When the button is clicked the object will be loaded into a new variable. As of right now, it is reading in the string and not reading it as a reference.
Object:
var Wall ={
"Option1":{},
"Option2":{},
"Option3":{
"Option1_3":{
Option1_1_3:{
aTotal:100,
Total_Something_Else:20,
Another_Total:40,
More_totals:20,
Total:20,
},
"Option2_1_3":{},
"Option3_1_3":{}
},
"Option2_3":{},
},
"Option4":{},
"Option5":{}};
Code to create the button:
var options = ['1','2','3','4']
for (var a = 1; a < 2; a++) {
for (var b = 3; b <4; b++) {
for (var i = 0; i < 1; i++) {
document.getElementById('Option1_3').innerHTML = (`<button class="button" value='${JSON.stringify('Wall.Option'+options[i]+'.Option'+a+'_'+b)}' onclick= "PopulateGraph(this.value)">Wall Time</button>`);
}
}
}
function PopulateGraph(val){
console.log(Wall.Option3.Option1_3); //The ouptut of this is what I want
console.log(JSON.parse(val));
}
The above code needs to behave like this piece of code:
Code that works the way I need it to
Output: The top output is what I would need:
Output
Please be mindful that I am very new to coding and javascript. If you have any suggestions at all to make my code better in the long run or have references you think will be helpful please do not be shy. I apologize if this seems like a no-brainer. I have tried a few different solutions to my issue (using eval() and scope[] )and I have yet to figure out a solution. I am giving a very simplified version of my code but the issue is the same.
Use the bracket notation [] to access the properties.
JSON.stringify(Wall['Option'+options[i]]['Option'+a+'_'+b])
Additionally i would create a variable with the text to avoid doing all that work in the same line
for (var i = 0; i < 1; i++) {
const optionValue = Wall['Option' + options[i]]['Option' + a + '_' + b];
const optionJson = JSON.stringify(optionValue);
document.getElementById('Option1_3').innerHTML = (`<button class="button" value='${optionJson}' onclick= "PopulateGraph(this.value)">Wall Time</button>`);
}
At least these issues:
The reference you try to build with i, a and b does not exist in your object. Your object has only a nested object for "Option3", but as i is initialised in the for loop as 0, you are not targeting that property.
The argument passed to JSON.stringify should be the nested value in the object, but you are passing a string, and so that string will be stringified (to yet another string).
I understand you have used single quotes to delimit the value of the value attribute, but (if the previous points are fixed) the targetted value within the Wall object could be a string having a single quote, and then your HTML will still be broken, as that will end the value attribute which you had delimited with single quotes.
As the Wall object is dynamically populated, you risk to be building the buttons too soon -- before the Wall object is populated -- and so the value attributes might still be wrong even when the above issues are resolved.
These complexities can be avoided by not building your button as a HTML string, but using the DOM methods to create the button element together with its attributes.
Furthermore, it seems that the information you store in the value property can be derived dynamically without that attribute:
We can find the parent element of the clicked button, get it's id attribute, and then we know the second-level property in the Wall object.
The Wall object seems to be structured in a way that the first-level property name can be derived from a second-level property name.
Also, you can iterate the properties in the Wall object without the need of a or b integers.
Here is how it could be done:
var Wall ={
"Option1":{},
"Option2":{},
"Option3":{
"Option1_3":{
"Option1_1_3":{
aTotal:100,
Total_Something_Else:20,
Another_Total:40,
More_totals:20,
Total:20,
},
"Option2_1_3":{},
"Option3_1_3":{}
},
"Option2_3": {
"Option2_1_3":{
test:1
}
},
},
"Option4":{},
"Option5":{}
};
for (let option of Object.values(Wall)) {
for (let id in option) {
let container = document.getElementById(id);
if (container) {
let button = document.createElement("button");
button.className = "button";
button.textContent = "Wall Time for " + id;
button.addEventListener("click", PopulateGraph);
container.appendChild(button);
}
}
}
function PopulateGraph() { // no argument.
// Drill down in the Wall object, based on the id of the button's container
let val = this.parentNode.id.match(/\d+/g).reduceRight(
(acc, _, i, arr) => acc[`Option${arr.slice(i).join("_")}`],
Wall
);
console.log(val);
}
<div id="Option1_3"></div>
<div id="Option2_3"></div>

Detect a button and then press it in JavaScript

I want to make a function that would detect a button on a web page and then click it. But I want it to click a specific item.
function imready()
{
var btn = document.getElementsByClassName('text-xxxs mb-02');
for (var i = 0; i < btn.length; i++)
{
if (btn[i].innerText.indexOf('AK-47') > -1)
{
console.log('runtime');
chrome.runtime.sendMessage({ type: 'dontrun', update: 1 }, function (response) {
});
btn[i].click();
pressok();
}
}
How do I make it so that the var "btn" should equal to document.getElementsbyClassName('x') and also a different className ('y')?
Quoting from https://stackoverflow.com/a/29366682/10450049
getElementsByClassName() returns an HTMLcollection object which is similar to an array but not really an array so you can't call
array methods using the returned value. One hack is to use Array's
prototype methods along with .call()/.apply() to pass the returned
object as the context.
var elems = document.getElementsByClassName("royal") ;
var collapsedElems = document.getElementsByClassName("collapsed");
var earray = Array.prototype.slice.call(elems, 0);
var concatenated = earray.concat.apply(earray, collapsedElems) ;
console.log(concatenated)
Demo Fiddle
As far as i understand your question, you can use document.querySelector('.classX.classY') to select the needed button with both classes.
That works for the case if you only need one button on the page selected, from your code i assume exactly that.

Is it possible to use more than one changeable divName in getelementbyid?

In this sort of function
function someFunction(divName)
{
document.getElementById(divName).style.someproperty = 'something'
document.getElementById(divName).style.someotherproperty = 'somethingelse'
}
called like this
onClick = "someFunction('someid')
Is there any way to have more than one divName that can be specified in the html?
e.g.
onClick = "someFunction('someid','someotherid')
The first getelementbyid being performed on 'someid' and the second on 'someotherid'
Preferably without jQuery
Edit:
I don't want to do the same thing to each of the two elements, I want to do something different to each individual element, but I want to be able to specify each element in the onClick ="..." instead of in the actual function, so that I don't have to write multiple functions for each combination of two target elements.
Sorry if that was unclear
The Answer (as it turns out, is really simple):
function someFunction(divName,divName2)
{
document.getElementById(divName).style.someproperty = 'something'
document.getElementById(divName2).style.someotherproperty = 'somethingelse'
}
html
onClick = "someFunction('someid','someotherid')"
When I tried it the first time, I wrote "someFunction('someid,someotherid')"
and when it didn't work I assumed that the solution wasn't as easy as divname1,divname2
Sorry, for making all of you run around writing fancy codes.
I assume you want to get an arbitrary number of arguments for a function. You can use the special arguments variable that comes in every called function. It's an array-like object that contains each argument passed into the function, in the order you placed them in the call.
You can do it like this:
function someFunction() {
for(var i = 0; i < arguments.length; i++){
document.getElementById(arguments[i]).style.someproperty = 'something'
}
}
However, classes might be better for this case, assuming you don't mind attaching classes to the target elements.
Give every your div a class name, for example "yourClass". Now you will have a function like this:
function someFunction(divClass) {
var eles = document.getElementsByClassName(divClass);
eles.style.someproperty = 'something';
eles.style.someproperty = 'something';
for(var i = 0; i< eles.length; i++){
// do some thing with each element
eles[i].style.someproperty = 'something';
}
}
Make the parameter an array and loop through it.
function someFunction(divIDs)
{
for (var i = 0; i < divs.length; i++)
{
document.getElementById(divIDs[i]).style.someProperty = 'something';
}
}
What you want to do is give all the elements you want to do something with a class which is the same for all of them, and an ID which is unique to all of them.
function someFunction(e) {
var action = false;
switch(e.target.id) {
case 'someid':
action = 'something';
break;
case 'someotherid':
action = 'somethingelse';
break;
case default:
break;
}
if(!action) return;
document.getElementById(e.target.id).style.someProperty = action;
}
You would then assign the onclick handler to the class:
onClick = "someFunction('theClassName')
If you have more than one element which you want to do the same thing to, instead of using an id, you would add a second class to those elements and just change the function to look for that class.

Can I loop through 2 objects at the same time in JavaScript?

related (sort of) to this question. I have written a script that will loop through an object to search for a certain string in the referring URL. The object is as follows:
var searchProviders = {
"google": "google.com",
"bing": "bing.com",
"msn": "search.msn",
"yahoo": "yahoo.co",
"mywebsearch": "mywebsearch.com",
"aol": "search.aol.co",
"baidu": "baidu.co",
"yandex": "yandex.com"
};
The for..in loop I have used to loop through this is:
for (var mc_u20 in mc_searchProviders && mc_socialNetworks) {
if(!mc_searchProviders.hasOwnProperty(mc_u20)) {continue;}
var mc_URL = mc_searchProviders[mc_u20];
if (mc_refURL.search(mc_URL) != -1) {
mc_trackerReport(mc_u20);
return false;
}
Now I have another object let's call it socialNetworks which has the following construct:
var socialNetworks = {"facebook" : "facebook.co" }
My question is, can I loop through both of these objects using just one function? the reason I ask is the variable mc_u20 you can see is passed back to the mc_trackerReport function and what I need is for the mc_u20 to either pass back a value from the searchProviders object or from the socialNetworks object. Is there a way that I can do this?
EDIT: Apologies as this wasn't explained properly. What I am trying to do is, search the referring URL for a string contained within either of the 2 objects. So for example I'm doing something like:
var mc_refURL = document.referrer +'';
And then searching mc_refURL for one of the keys in the object, e.g. "google.com", "bing.com" etc. 9this currently works (for just one object). The resulting key is then passed to another function. What I need to do is search through the second object too and return that value. Am I just overcomplicating things?
If I understand your question correctly, you have a variable mc_refURL which contains some URL. You want to search through both searchProviders and socialNetworks to see if that URL exists as a value in either object, and if it does you want to call the mc_trackerReport() function with the property name that goes with that URL.
E.g., for mc_refURL === "yahoo.co" you want to call mc_trackerReport("yahoo"), and for mc_ref_URL === "facebook.co" you want to call mc_trackerReport("facebook").
You don't say what to do if the same URL appears in both objects, so I'll assume you want to use whichever is found first.
I wouldn't create a single merged object with all the properties, because that would lose information if the same property name appeared in both original objects with a different URL in each object such as in an example like a searchProvider item "google" : "google.co" and a socialNetworks item "google" : "plus.google.com".
Instead I'd suggest making an array that contains both objects. Loop through that array and at each iteration run your original loop. Something like this:
var urlLists = [
mc_searchProviders,
mc_socialNetworks
],
i,
mc_u20;
for (i = 0; i < urlLists.length; i++) {
for (mc_u20 in urlLists[i]) {
if(!urlLists[i].hasOwnProperty(mc_u20))
continue;
if (mc_refURL.search(urlLists[i][mc_u20]) != -1) {
mc_trackerReport(mc_u20);
return false;
}
}
}
The array of objects approach is efficient, with no copying properties around or anything, and also if you later add another list of URLs, say programmingForums or something you simply add that to the end of the array.
You could combine the two objects into one before your loop. There's several approaches here:
How can I merge properties of two JavaScript objects dynamically?
var everything = searchProviders;
for (var attrname in socialNetworks) { everything[attrname] = socialNetworks[attrname]; }
for(var mc_u20 in everything) {
// ...
}
for (var i = 0; i < mc_searchProviders.length; i++) {
var searchProvider = mc_searchProviders[i];
var socialNetwork = mc_socialNetworks[i];
if (socialNetwork != undefined) {
// Code.
}
}
Or am i horribly misunderstanding something?

Categories