I'm trying to make a dropdown which is populated from an array with Javascript. Each Item needs to have an event trigger attached, but it currently only attaches the event to the last element. I have tried the examples based on fixing closures but is still only attaches to the last element.
https://jsfiddle.net/z3h1uux4/
var ArrayUName = ["A","B","C"]
var ArraySlug = ["Q","W","E"]
for (i = 0; i < ArrayUName.length; i++) {
var GoalID = ArrayUName[i] + '-' + ArraySlug[i];
document.getElementById("TheContent").innerHTML +=
'<a class="GoalIDBtn" id="' + GoalID + '">' + ArrayUName[i] + ' / ' + ArraySlug[i] + '</a></br>';
(function(_i, _GoalID)
{document.getElementById(_GoalID).addEventListener(
"click",
function() {alert("Click Made : " + _i)}
);
})(i, GoalID);
console.log("Loop #" + i);
}
That's because innerHTML is a destructive property. It recreates the set content and creates new elements, the newly generated elements do no have any click handlers bound to them. You should create a node (element) instead of using innerHTML.
You can use the document.createElement and HTMLElement.appendChild methods instead:
var a = document.createElement('a');
a.className = 'GoalIDBtn';
a.id = GoalID;
a.textContent = ArrayUName[i] + ' / ' + ArraySlug[i];
document.getElementById("TheContent").appendChild(a);
(function(_i /*, _GoalID*/) {
a.addEventListener("click", function() {
alert("Click Made : " + _i);
});
})(i);
Here is a demo on jsfiddle. Note that it doesn't add the br elements and you can use the similar DOM APIs for creating them.
Related
I was given this task with some existing code to change the string color of each of three selector.value(s) that is output onto an input element to three different colors. The code boils the three selectors into a single output variable. Without destroying the code, I cannot figure out how to select each individual variables prior to condensing them.
If I could use the fontcolor() method, my life would be great but it's 2018 and I can't. Is there any way you can think of to solve this issue?To clarify, I need to alter the colors of the strings that belong to output(red), select1.value(blue) and select2.value(black.
Most of the action for this is happening in the parseOutput() function but I'm just stuck and don't think it's possible without rewriting the entire program.
function updateSelector(result){
var options = result.options;
var elementId = "select" + result.element;
var logger = document.getElementById('logger');
var selector = document.getElementById(elementId);
//logger.innerHTML = JSON.stringify(elementId);
selector.innerHTML = options;
selector.disabled = false;
}
google.script.run.withSuccessHandler(updateSelector).processOptions(0);
plate();
function resetAll(){
for (var i = 0;i<3;i++){
var selector = document.getElementById('select' + i);
selector.disabled = true;
selector.innerHTML = "";
}
google.script.run.withSuccessHandler(updateSelector).processOptions(0);
}
function finalSelection(){
var output = document.getElementById('out');
//output.focus();
output.select();
}
function plate(){
var plate = document.getElementById('plate');
plate.innerHTML = atob('Q3JhZnRlZCBieTogWmFjaGFyeSBTdGFjaG93aWFr');
}
//Adds the location as initial output, followed by divider, application, and issue if select1 is selected
//else statement added so if select0 is [Costco Website Name], to ommit the " - "
function parseOutput(){
var output = "";
if (select1.value.length > 0 && select0.value !== "[Costco Website Name]"){
output = output + ' - ' + select1.value + ' // ' + select2.value;
} else{
output = output + select1.value + ' // ' + select2.value;
}
out.value=output.trim();
}
And this is the Div that displays the output:
<div class="wide"><p><input class="wide" type="readonly" id="out" onfocus="this.select();"></p></div>
A modern replacement for fontcolor would use a span and a style (or class), e.g.:
function modernFontColor(str, color) {
return '<span style="color: ' + color + '">' + str + '</span>';
}
or
function modernFontClass(str, cls) {
return '<span class="' + cls + '">' + str + '</span>';
}
...where the class defines the styling.
I'm trying to append an increasing number to elements on click. I can't seem to make it work.
My code:
$('#on').click(function() {
$("b").click(function(e) {
var numCount = ($("[span class='num'>").length + 1);
var element = $("<span class='num'>" + numCount + "'>" + numCount + "</span>");
$(this).append(element);
});
});
I think It's a simple syntax error in my code, but I'm learning here so I could be completely wrong. It's important for the class to be added too.
Here's a Fiddle
Change
var numCount = ($("[span class='num'>").length + 1);
to
var numCount = ($(".num").length + 1);
You need to find .num elements, not create new ones.
In addition, your element line doesn't create valid HTML either. Try the following:
var element = $("<span class='num'>" + numCount + "</span>");
I need some help. I am trying to iterate through some Json items, create some li's with some information from the items, but also attach click handlers for the li's, but with a value transmited as parameter.
The problem is that the last value of that parameter is set for or the li's in the list. From what i searched i understood that it has something to do with javascript closure, but can't seem to understand how to fix it.
I am using jQuery and here is the code:
for (var i = 0; i < items.length; i++)
{
// information that will be displayed for each video
var entry = items[i];
var title = entry.title;
var image = entry.thumbnail.hqDefault;
var id = entry.id;
var li = $("<li class='video-single'>");
li.append("<img src='" + image + "' alt='" + title + "'>");
li.append("<h4>" + title + "</h4>");
$(li).click(function() {
displayPopUp(id);
});
ul.append(li);
}
Could anyone please help me fix this code?
Best regards, Marius.
The issue is that JS is function scoped so the id within your closure is the last id from your for loop. To fix this use forEach so you run each iteration of your loop in a separate scope e.g.
items.forEach(function (el, i) {
// information that will be displayed for each video
var entry = items[i];
var title = entry.title;
var image = entry.thumbnail.hqDefault;
var id = entry.id;
var li = $("<li class='video-single'>");
li.append("<img src='" + image + "' alt='" + title + "'>");
li.append("<h4>" + title + "</h4>");
$(li).click(function() {
displayPopUp(id);
});
ul.append(li);
});
you need delegated event as elements are created dynamically, you can use class that is being added on li which is video-single:
$(document).on('click','.video-single',function() {
displayPopUp(id);
});
you can read about Delegated Events HERE
in order to bind an event to a dynamically added element you need to delegate it as below:
for (var i = 0; i < items.length; i++)
{
// information that will be displayed for each video
var entry = items[i];
var title = entry.title;
var image = entry.thumbnail.hqDefault;
var id = entry.id;
var li = $("<li class='video-single'>");
li.append("<img src='" + image + "' alt='" + title + "'>");
li.append("<h4>" + title + "</h4>");
$(document).bind('click',li,function() {
displayPopUp(id);
});
ul.append(li);
}
I'm trying to clone to divs in and append them to to other divs (their parents). I'm using clonenode for this but it doesn't seem to work. It clones the div in the first function and appends it to the parent of the div in the second function! Not sure what I'm doing wrong.
Here's the code (*EDIT:*var added):
function cloneQ() {
//Cloning questions and shit
cloneQ.id = (cloneQ.id || 0) + 1;
var question = document.getElementById("question");
var clone = question.cloneNode(true);
var numberOfQuestions = $('.question').length;
var id = "questioncon" + cloneQ.id;
clone.id = id;
question.parentNode.appendChild(clone);
var inid = "question" + cloneQ.id;
var optionid = "optionsdiv" + cloneQ.id;
$('#' + id + ' ' + '.' + 'questionin').attr('id', inid);
$('#' + id + ' ' + '.' + 'options').attr('id', optionid);
$('#' + id + ' h2').html('Question ' + cloneQ.id);
//Question Cloned
}
function cloneforPrint() {
cloneforPrint.id = (cloneforPrint.id || 0) + 1;
var questionprint = document.getElementById("questionprint");
var cloneprint = questionprint.cloneNode(true);
var printid = "questionprint" + cloneforPrint.id;
cloneprint.id = printid;
questionprint.parentNode.appendChild(clone);
var printinid = "thequestionprint" + cloneforPrint.id;
$('#' + printid + ' ' + '.' + 'thequestionprint').attr('id', printinid);
}
LIVE here: http://bit.ly/R8hB2m
Edit : Global vars are the problem.
You aren't putting var in front of your variables, making them global. The cloneForPrint function is picking up vars defined in cloneQ.
Init all the variables properly and you'll get some errors indicating where the problems are.
CloneQ is indeed appending to questions parent, but cloneForPrint then moves it somewhere else.
-- Old answer --
There's not enough here to work out what the problem is. My 1st guess is that the question element has the same parent as the questionprint element.
Based on the code given, cloneQ should definitely append to questions parent. So to give the appearance you've specified the DOM probably doesn't look like what you expect.
Given:
// Positions the bars relative to scale
this.gDrawBars = function() {
// Go through all the bars
for (i = 0; i < this.gData.length; i++) {
// Check part of it is within range
if (this.gDisplayFrom < this.gData[i][2] || this.gDisplayTo > this.gData[i][1]) {
// Is the entire bar showing
var isEntireBarInRange = (this.gDisplayFrom < this.gData[i][2] && this.gDisplayTo > this.gData[i][1]);
var div = document.createElement('div');
div.id = "gBar" + i;
div.className = 'gBar';
div.innerHTML = this.gData[i][0];
var self = this;
div.onmouseover = function() {
gBarHighlight(this, this.gData[i][1], this.gData[i][2]);
};
div.onmouseout = function() {
gBarUnHighlight(this, this.gData[i][1], this.gData[i][2]);
};
this.gContainer.appendChild(div);
//this.gContainer.innerHTML += "<div id=\"gBar" + i + "\" class=\"gBar\" onmouseover=\"gBarHighlight(this, '" + this.gData[i][1] + "', '" + this.gData[i][2] + "')\" onmouseout=\"gBarUnHighlight(this, '" + this.gData[i][1] + "', '" + this.gData[i][2] + "')\">" + this.gData[i][0] + "</div>";
The commented line at the bottom works fine, but I'm trying to change it to add these functions dynamically. It needs to pass this.gData[i][1] into the functions but it can't, because the i value has no meaning outside the loop.
How can I get around this? IE, make the function recognise it's being passed a value to use and not a reference.
You need to retain the value of i in a new execution context.
Place the code that assigns the handlers into a named function, and call that in the loop, passing i as an argument.
Place this function before the for loop:
function setupMouseOverOut( el, i ){
el.onmouseover = function() {
gBarHighlight(this, this.gData[i][1], this.gData[i][2]);
};
el.onmouseout = function() {
gBarUnHighlight(this, this.gData[i][1], this.gData[i][2]);
};
}
...then call it in the for loop:
setupMouseOverOut( div, i );
This way the value of i that you passed out of the for loop is retained in the new execution context of the setupMouseOverOut() function call, and the new functions you set as handlers will refer to that local variable.
It's not a function, it's an event. You need to add it as an event to the element:
div.addEventListener('mouseover', function() {
// ...
});
Note that when you do it this way you don't have that 'on' word there.