Prevent on click function from firing multiple times - javascript

I am trying to just make a simple click function but when I do it, it fires the exact amount of times of how many items are in the array. So if I click the button I display it fires off 3 times because there's 3 items in the array (lakers, cavs, suns). My question is how do I prevent it from firing off that x number times?
var TeamPlayers = [{
team: 'Lakers',
Players: ['Russell', 'Clarkson', 'Ingram', 'Randle', 'Zubacs']
}, {
team: 'Cavs',
Players: ['Irving', 'Smith', 'LeBron', 'Love', 'Thompson']
}, {
team: 'Suns',
Players: ['Ulis', 'Booker', 'Warren', 'Chriss', 'Len']
}]
for (var i = 0; i < TeamPlayers.length; i++) {
var TeamPlayersVar = TeamPlayers[i].team
// console.log('outside loop',TeamPlayers[i].team);
$('.leftPlayer').append('<button class="leftButtons">' + TeamPlayers[i].team + '</button>' + '<br>')
$(document).on('click', '.leftButtons', function(){
console.log(this)
});
}

I will suggest to use a id to do it . That should fix your issue. as the click will be registered with the id
for (var i = 0; i < TeamPlayers.length; i++) {
var TeamPlayersVar = TeamPlayers[i].team
// console.log('outside loop',TeamPlayers[i].team);
$('.leftPlayer').append('<button id="btn'+i+'" class="leftButtons">' + TeamPlayers[i].team + '</button>' + '<br>')
$("#btn"+i).click(function(){
console.log(this)
});
}

This is happening because you are assigning on click listener to a class and there are 3 elements with the specified class name, in order to make it work only one time, please use ID or use unique class names

You only need to place your click handler code outside the loop i.e.
var TeamPlayers = [{
team: 'Lakers',
Players: ['Russell', 'Clarkson', 'Ingram', 'Randle', 'Zubacs']
}, {
team: 'Cavs',
Players: ['Irving', 'Smith', 'LeBron', 'Love', 'Thompson']
}, {
team: 'Suns',
Players: ['Ulis', 'Booker', 'Warren', 'Chriss', 'Len']
}]
for (var i = 0; i < TeamPlayers.length; i++) {
var TeamPlayersVar = TeamPlayers[i].team
// console.log('outside loop',TeamPlayers[i].team);
$('.leftPlayer').append('<button class="leftButtons">' + TeamPlayers[i].team + '</button>' + '<br>')
}//for()
$(document).on('click', '.leftButtons', function(){
console.log(this)
});

Related

It is recommended use a large object where keys are string Codes for better access instead array that needs iterations?

I have a data structure like this
{
"stores": [
{ code: 'A7-22', name: 'market'},
{ code: 'A8-22', name: 'drugstore'},
... 250 items
]
}
Then in the code I search a store using:
const code = 'A7-22';
const store = data.stores.find(store => store.code === code)
But I think that could be a good idea to change the data structure to
{
"stores": {
'A7-22': {name: 'market'},
'A8-22': { name: 'drugstore'},
... 250 items
]
}
and now the code it is more easy to handle:
const code = 'A7-22';
const store = data.stores[code]
Now, consider that this code is running in a backend artifact with high traffic and in the future, the store could be > 10.000.
The question is:
There are some performance issues related to this code if I take the option 2 using a large object with "key value strategy"?
Yes, absolutely the second option is faster. Here is the challenge for two cases:
var code = 'A7-22', store={};
var data = {
"stores": [
{ code: 'A8-22', name: 'drugstore'},
]
};
for(var i=1; i<10000; i++) {
data.stores.push({ code: 'x'+i, name: 'v'+i});
}
data.stores.push({ code: 'A7-22', name: 'market'});
var t1 = new Date();
for(var i=1; i<1000; i++) {
store = data.stores.find(store => store.code === code);
}
var t2 = new Date();
var dt = (t2.getTime() - t1.getTime());
document.write("<p> <b>Option 1 (using find):</b> " + code + ": " + store.name + ', dt: '+ dt + " msec</p>");
// -------------------
var data2 = {
"stores": {
'A8-22': {name: 'drugstore'}
}
};
for(var i=1; i<10000; i++) {
data2.stores['x'+i] = {name: 'v'+i};
}
data2.stores['A7-22'] = { name: 'market'};
var t1 = new Date();
for(var i=1; i<1000; i++) {
store = data2.stores[code]
}
var t2 = new Date();
var dt = (t2.getTime() - t1.getTime());
document.write("<p> <b>Option 2 (using array index):</b> " + code + ": " + store.name + ', dt: '+ dt + "msec</p>");
Note that the high traffic you mentioned is not important here as this code is run in clients.

retrieving multiple levels, API

I've accessed 5 followers of a GitHub user using AJAX. I'm trying to go three levels deep on each of the first 5 followers returning 5 followers of each. So, initially, return 5 followers then for each of the five followers go three levels deep returning 5 more followers at each level.
How would I go about this? Recursion? Nested for loops?
Also, when I render the first five followers they render all on the same line in my html. Trying to simply append an html break tag on each loop but doesn't seem to work.
Thanks.
$(function() {
$("#submitbtn").on("click", function(e) {
e.preventDefault();
var username = $("#userID").val();
console.log("username " + username);
var userFollower =
"https://api.github.com/users/" + username + "/followers";
$.getJSON(userFollower, function(data) {
console.log(data);
for (i = 0; i < 5; i++) {
var br = "<br>";
var follower = data[i].login;
console.log("Follower " + follower);
$(".follower").append(follower);
$(".container").append(br);
}
});
});
});
Here is an example using some basic recursion. Note that I also modified the DOM manipulation so that each follower was on a new line, in its own div.
I set the depth to 3 so that I could limit the hits to github.
UPDATE [1]
Noticed that you wanted depth = 3 and 5 followers each, and those numbers were not linked. Modified the snippet to unlink those numbers from each other.
var depth = 3;
var number_per_level = 5;
//var tst = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
$(function() {
$("#submitbtn").on("click", function(e) {
e.preventDefault();
var username = $("#userID").val();
getFollowers(0, $(".container"), username);
});
});
function getFollowers(count, $container, username) {
if (count >= depth) {
return false;
}
//console.log("username " + username);
var userFollower =
"https://api.github.com/users/" + username + "/followers";
$.getJSON(userFollower, function(data) {
//console.log(data);
for (let i = 0; i < number_per_level; i++) {
var follower = data[i].login; //tst[i]; //data[i].login;
var $mine = $("<div class='follower'>");
//console.log(`Follower ${follower} follows ${username}`);
$mine.append(follower).appendTo($container);
getFollowers(count + 1, $mine, follower);
}
});
}
.follower {
padding-left: 1em;
border: 1px solid #c0c0c0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="userID" value="tim" /><button id="submitbtn">GO</button>
<div class="container"></div>
If you know how many levels you have to deep dive, then its better to take nested loops rather than recursions.
var git = [{
name: 'a',
fol: [{
name: 'b',
fol: [{
name: 'c'
}]
}]
}, {
name: 'r',
fol: [{
name: 't',
fol: [{
name: 'e'
}]
}]
}];
git.forEach((firstlevel) => {
console.log(firstlevel.name);
firstlevel.fol.forEach((seclevel) => {
console.log(seclevel.name);
seclevel.fol.forEach((thirdlevel) => {
console.log(thirdlevel.name);
});
});
});
Improper handling of recursion leads to infinite calls and may lead to javascript heap out of memory. And also loops are faster than recursions.

JavaScript loop inside tinymce button

My JavaScript loop is not working properly inside a tinymce button.
I set a variable n which is the array size that I get from my html input.
var n = $('#total').val();
Then, I create the array of tinymce buttons: var menuItems = [];
In my tinymce editor init, I create the buttons:
editor.on('init', function (e) {
for (var i=1; i<=n; i++){
var obj = {
text: 'Item ' + i,
onclick: function() {
var msg = ' <strong>#item' + i + '#</strong> ';
editor.insertContent(msg);
}
}
menuItems.push(obj);
}
});
Last step is add the menuItems to the tinymce buttons:
editor.addButton('myButton', {
type: 'menubutton',
text: 'Items',
icon: false,
menu: menuItems
});
The buttons are displaying correct with the correct label. I have the buttons:
Item 1
Item 2
Item 3
However, doesn't matter which button I click, the text displayed in the editor is item3. It always get the last button text.
Does anyone know why it is happening?
Thanks
Use let instead of var since let would keep its lexical block scope where var would not:
editor.on('init', function(e) {
for (let i = 1; i <= n; i++) { // <-- use let here
var obj = {
text: 'Item ' + i,
onclick: function() {
var msg = ' <strong>#item' + i + '#</strong> ';
editor.insertContent(msg);
}
}
menuItems.push(obj);
}
});
Here is the documentation on let

Nested variable getting incorrectly incremented

I have the following code:
'use strict';
// Data
class cat {
constructor(name, picture, clicks) {
this.name = name;
this.picture = picture;
this.clicks = clicks;
}
};
var cat1 = new cat('Mercury', 'cat1.jpg', 0);
var cat2 = new cat('Venus', 'cat2.jpg', 0);
var cat3 = new cat('Mars', 'cat3.jpg', 0);
var cat4 = new cat('Jupiter', 'cat4.jpg', 0);
var cat5 = new cat('Saturn', 'cat5.jpg', 0);
var cat6 = new cat('Neptune', 'cat6.jpg', 0);
var cats = [cat1, cat2, cat3, cat4, cat5, cat6];
// Program
for (var i = 0; i < cats.length; i++) {
// Current cat
var icat = cats[i];
$('#cat_list').append('<li id="cat' + (i + 1) + '">' + icat.name + '</li>');
$('#cat' + (i + 1)).on('click', (function(iSaved, icatSaved) {
return function() {
console.log('You clicked on cat' + (iSaved + 1) + ' from the list!');
$('#cat_title').text(icatSaved.name);
$('#cat_image').attr('src', 'img/' + icatSaved.picture);
$('#catClicks').text(icatSaved.clicks);
$('#cat_image').on('click', function() {
icatSaved.clicks++;
$('#catClicks').text(icatSaved.clicks);
});
};
})(i, icat));
};
It works great except for the embedded click handler that increments the click count for the cat object in question. It seems that the click incrementing ends up impacting more than one of the cat objects and also causes some of them to increment by 2, 3, or 4 when clicking on the picture. I must be doing something dumb to get this behavior but I'm not sure what. Is this because I’ve embedded a click handler within the list click handler?
–Jim
You are setting an event listener inside another event listener. Each time #cat{N} get clicked, you add another event listener for #cat_image. Thus clicking it over and over again setup more and more event listeners for the same element. When you click #cat_image, all those event listeners get invoked, and since they are all incrementing the clicks property, the latter get incremented a lot of times by just one click.
The best way at approaching this is to use event delegation:
for (var i = 0; i < cats.length; i++) {
var icat = cats[i];
// add a class of .cat and a data-index attribute to all cats
$('#cat_list').append('<li class="cat" data-index="' + i + '">' + icat.name + '</li>');
// ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
};
var catSaved = null; // move this outside the event listener to be visible for both event listeners
$('#cat_list').on('click', '.cat', function(e) { // whenever a .cat element get clicked from within #cat_list element
var index = $(this).data('index'); // get the index of that cat from cats array (previously stored in data-index)
catSaved = cats[index]; // get the cat itself
$('#cat_title').text(catSaved.name);
$('#cat_image').attr('src', 'img/' + catSaved.picture);
$('#catClicks').text(catSaved.clicks);
});
$('#cat_image').on('click', function() { // move this event listener outside, to set it only once
if(catSaved) { // if we have a catSaved element
catSaved.clicks++; // ...
$('#catClicks').text(catSaved.clicks);
}
});

JQuery click eventhandler: register event on dynamically added element

I would like to dynamically build a page from 2 hashes (in my example c and d).
var c = {
cluster_1 : { list_datasets: [ "a", "b", "c"]},
cluster_2 : { list_datasets: [ "b", "c"]},
};
var d = {
a : { title: "A", content: "aaaaaaaaaaaaaa"},
b : { title: "B", content: "bbbbbbbbbbbbbb"},
c : { title: "C", content: "cccccccccccccc"},
};
so that I first get the list of clusters, and then by clicking on the cluster, I get the list of their respective content. This works fine until here.
But now if I want to go a step further end by clicking on each dataset, I would like to have the dataset description. The jquery selection operation $('#a') is empty and nothing is shown. Here a little standalone example that shows the problem
Thanks a lot for you help or any information on that topic.
Kind regards
Antoine
Since these elements are created dynamically, you have to use delegate event handler like
$(document).on('click', '#'+key, function(event){
$("aside").html(value.content);
});
JSFiddle
Your problem is that you call second each
$.each(d, function(key, value){ ..
when there are no elements printed on the page.
Elements get printed only when you click on any of clusters. So you should wrap second each with a function e.g.
function getValues(){
$.each(d, function(key, value){
$('#' + key).each(function(){
console.log(value.content);
});
});
};
and call it at the end of click function
$('#' + cluster).click(function( event ) {
var content_list = "<ul>";
for (var i = 0; i < value.list_datasets.length; i++) {
var dsName = datasetName = value.list_datasets[i];
if(d.hasOwnProperty( datasetName ) ) {
var datasetName = d[datasetName].title;
}
content_list = content_list + "<li><a id='" + dsName + "' href='#foo'>" + datasetName + "</a></li>";
}
content_list = content_list + "</ul>";
$("section").html(content_list);
getValues();
});

Categories