I have a loop that creates Html links and assign click events to them in the same loop. The click event seems to work only for the last element that the loop creates.
<div id="list">
</div>
<script type="text/javascript">
users = {
1 : { id : 1 },
2 : { id : 2 },
3 : { id : 3 },
4 : { id : 4 }
};
for (var index in users) {
var user = users[index];
document.getElementById("list").innerHTML += '' + user.id + '';
var element = document.querySelector("#user-" + user.id);
console.log(element);
element.addEventListener("click", function(event){
console.log(this);
event.preventDefault();
});
}
</script>
Here only the last link is click-able. However if the links have already been created then all the event listeners will work.
<div id="list">
1
2
3
4
</div>
<script type="text/javascript">
users = {
1 : { id : 1 },
2 : { id : 2 },
3 : { id : 3 },
4 : { id : 4 }
};
for (var index in users) {
var user = users[index];
var element = document.querySelector("#user-" + user.id);
console.log(element);
element.addEventListener("click", function(event){
console.log(this);
event.preventDefault();
});
}
</script>
This seems to work without any issues. Is there away to make sure that anevent is attached to the element at creation?
Note: I don't want to use JS framework for this.
I know that this can be done easily using JQuery.
$( "body" ).on('click', "#user-" + user.id ,function(){});
Note:
The code I posted is a simple code I created on the fly to illustrate the problem. I am using a template engine that return a string, that's why I am using
document.getElementById("list").innerHTML += '' + user.id + '';
Your problem is in .innerHTML which breaks the DOM with every iteration, which means the previously added events are wiped out and you only have the listener attached to the very last element. You can use DOM API to create and append these elements and everything will work fine: jsfiddle.net/HtxZb
I would, however, recommend using event delegation. Without jQuery you can implement it like this:
document.getElementById('list')
.addEventListener('click', function(event){
var elem = event.target;
console.log( elem );
if( elem.nodeName.toLowerCase() === "a" ){ // a link was clicked
/* do something with link
for example
switch( elem.id ){
}
*/
event.preventDefault();
}
});
Its better to attach the onclick handler before appending to the DOM element.
This should work
<script type="text/javascript">
users = {
1 : { id : 1 },
2 : { id : 2 },
3 : { id : 3 },
4 : { id : 4 }
};
for (var index in users) {
var user = users[index];
var a = document.createElement("a");
a.href='#';
a.id = "user-" + user.id ;
a.onclick = function(event){
console.log(this);
event.preventDefault();
}
a.innerHTML = user.id;
document.getElementById("list").appendChild(a);
}
</script>
Try the following at this fiddle:
<div id="list">
</div>
users = { 1 : { id : 1 },
2 : { id : 2 },
3 : { id : 3 },
4 : { id : 4 } };
for (var index in users) {
var user = users[index];
var element = document.createElement("a");
element.id = "user-" + user.id;
element.innerHTML = "user-" + user.id;
document.getElementById("list").appendChild(element);
console.log(element);
element.onclick = function(event){
console.log(this);
event.preventDefault();
};
}
Related
I am testing some generic function building and came across this problem - cannot select first cloned element using jQuery syntax. Is it due to eventListeners not being present in the first please? If so, how to add an eventListener to something that is not present in DOM until cloned?
var cloneAndAppendCounter = 0;
function cloneAndAppend (what, target, maxClones) {
var id = what.attr('id');
var clone = what.clone(true);
var target = target;
if ( cloneAndAppendCounter < maxClones ) {
clone.attr('id', id + cloneAndAppendCounter);
clone.appendTo(target);
cloneAndAppendCounter++;
}
};
function destroyClonedElement (originalElement, when) {
var originalElementId = originalElement.attr('id');
var clonedElementId = originalElementId + cloneAndAppendCounter;
var cloned = $('#' + clonedElementId);
console.log('clonedElementId:', clonedElementId);
console.log(cloned);
if ( (cloned) && cloneAndAppendCounter > 0 ) {
cloned.remove();
cloneAndAppendCounter--;
console.log('counter: ', cloneAndAppendCounter);
};
};
$('.clone-button').click(function () {
cloneAndAppend($('#app'), $('.container'), 4);
});
$('.destroy-button').click(function () {
destroyClonedElement($('#app'));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="clone-button">clone button</button>
<button class="destroy-button">destroy button</button>
<div class="container">
<div id="app">test</div>
</div>
No, the problem is not in elements being bound or not bound to listeners.
The problem is in this line
cloneAndAppendCounter++;
or more precisely in the place you had put it, because it plays dramatic role as turned out.
So the thing is tha you append an element with a specific id to target then you increase the counter. So by the time you are pushing remove button the counter is more than the number of appended children by one and so that is why the first click has not effect - because it goes in vain.
Here is a working script(I rearranged problematic line to the place it best fits in and besides that changed initial counters):
var cloneAndAppendCounter = -1;
function cloneAndAppend (what, target, maxClones) {
var id = what.attr('id');
var clone = what.clone(true);
var target = target;
if ( cloneAndAppendCounter < maxClones ) {
cloneAndAppendCounter++;
clone.attr('id', id + cloneAndAppendCounter);
clone.appendTo(target);
// console.log('counter: ', cloneAndAppendCounter);
}
};
function destroyClonedElement (originalElement, when) {
var originalElementId = originalElement.attr('id');
var clonedElementId = originalElementId + cloneAndAppendCounter;
var cloned = $('#' + clonedElementId);
// console.log('clonedElementId:', clonedElementId);
// console.log(cloned);
if ( cloned && cloneAndAppendCounter > -1 ) {
cloned.remove();
cloneAndAppendCounter--;
// console.log('counter: ', cloneAndAppendCounter);
};
};
$('.clone-button').click(function () {
cloneAndAppend($('#app'), $('.container'), 4);
});
$(document).on('click','.destroy-button', function (e) {
e.preventDefault();
destroyClonedElement($('#app'))
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="clone-button">clone button</button>
<button class="destroy-button">destroy button</button>
<div class="container">
<div id="app">test</div>
</div>
I've downloaded this script for use conditional fields in forms:
(function ($) {
$.fn.conditionize = function(options) {
var settings = $.extend({
hideJS: true
}, options );
$.fn.showOrHide = function(is_met, $section) {
if (is_met) {
$section.slideDown();
}
else {
$section.slideUp();
$section.find('select, input').each(function(){
if ( ($(this).attr('type')=='radio') || ($(this).attr('type')=='checkbox') ) {
$(this).prop('checked', false).trigger('change');
}
else{
$(this).val('').trigger('change');
}
});
}
}
return this.each( function() {
var $section = $(this);
var cond = $(this).data('condition');
// First get all (distinct) used field/inputs
var re = /(#?\w+)/ig;
var match = re.exec(cond);
var inputs = {}, e = "", name ="";
while(match !== null) {
name = match[1];
e = (name.substring(0,1)=='#' ? name : "[name=" + name + "]");
if ( $(e).length && ! (name in inputs) ) {
inputs[name] = e;
}
match = re.exec(cond);
}
// Replace fields names/ids by $().val()
for (name in inputs) {
e = inputs[name];
tmp_re = new RegExp("(" + name + ")\\b","g")
if ( ($(e).attr('type')=='radio') || ($(e).attr('type')=='checkbox') ) {
cond = cond.replace(tmp_re,"$('" + e + ":checked').val()");
}
else {
cond = cond.replace(tmp_re,"$('" + e + "').val()");
}
}
//Set up event listeners
for (name in inputs) {
$(inputs[name]).on('change', function() {
$.fn.showOrHide(eval(cond), $section);
});
}
//If setting was chosen, hide everything first...
if (settings.hideJS) {
$(this).hide();
}
//Show based on current value on page load
$.fn.showOrHide(eval(cond), $section);
});
}
}(jQuery));
I'm trying this because I need to use conditionize() in one of my tabs and when I reload the tab, all works but if I go to other tab and I return to the previous tab(where I need this works), I get that error.
When I change tabs, I'm only reloading one part of the page.
When I load the page this works perfectly, but if I try to call function again from browser console, it tells me that TypeError: $(...)conditionize() is not a function.
I have included the script in header tag and I'm calling it with this script on the bottom of body:
<script type="text/javascript">
$('.conditional').conditionize();
</script>
EDIT:
I have written
<script type="text/javascript">
console.log($('.conditional').conditionize);
setTimeout(function () {console.log($('.conditional').conditionize);}, 2);
</script>
and this print me at console the function, and when 2 milliseconds have passed, it print me undefined
I have found the solution.
Because any reason, the $ object and jQuery object are not the same in my code.
I have discovered it using this on browser console:
$===jQuery
This return false (This was produced because in other JS, I was using the noConflict(), which give me the problem)
Explanation: noConflict()
So I have solved it changing the last line of my JS by:
//Show based on current value on page load
$.fn.showOrHide(eval(cond), $section);
});
}
}($));
Putting the $ instead of 'jQuery'
I am trying to make a quiz app and i want the score to update. The change event for radio button in not triggered on clicking next question.
https://codepen.io/abhilashn/pen/BRepQz
// JavaScript Document
var quiz = { "JS" : [
{
"id" : 1,
"question" : "Inside which HTML element do we put the JavaScript?",
"options" : [
{"a": "<script>",
"b":"<javascript>",
"c":"<scripting>",
"d":"<js>"}
],
"answer":"<script>",
},
{
"id" : 2,
"question" : "What is the correct JavaScript syntax to change the content of the HTML element below.",
"options" : [
{"a": " document.getElementById('demo').innerHTML = 'Hello World!';",
"b":" document.getElementByName('p').innerHTML = 'Hello World!';",
"c":" document.getElement('p').innerHTML = 'Hello World!';",
"d":" #demo.innerHTML = 'Hello World!';"}
],
"answer":" document.getElementById('demo').innerHTML = 'Hello World!';",
}
]
}
var quizApp = function() {
this.score = 0;
this.qno = 1;
this.currentque = 0;
var totalque = quiz.JS.length;
this.displayQuiz = function(cque) {
this.currentque = cque;
if(this.currentque < totalque) {
$("#qid").html(this.qno++);
$("#question").html(quiz.JS[this.currentque].question);
$("#question-options").html("");
for (var key in quiz.JS[this.currentque].options[0]) {
if (quiz.JS[this.currentque].options[0].hasOwnProperty(key)) {
//console.log(key + " -> " + quiz.JS[this.currentque].options[0][key]);
$("#question-options").append(
"<div class='form-check option-block'>" +
"<label class='form-check-label'>" +
"<input type='radio' class='form-check-input' name='option' id='q"+key+"' value='" + quiz.JS[this.currentque].options[0][key] + "'>" +
quiz.JS[this.currentque].options[0][key] +
"</label>"
);
}
}
} else {
return alert("Your score: " + this.score) ;
}
}
this.checkAnswer = function(option) {
var answer = quiz.JS[this.currentque].answer;
option = option.replace(/\</g,"<") //for <
option = option.replace(/\>/g,">") //for >
console.log(answer);
console.log(option);
if(option == quiz.JS[this.currentque].answer) {
this.score = this.score + 1;
console.log(this.score);
}
}
this.changeQuestion = function(cque) {
this.currentque = this.currentque + cque;
this.displayQuiz(this.currentque);
}
}
var jsq = new quizApp();
$(document).ready(function() {
jsq.displayQuiz(0);
$('input[type=radio][name=option]').change(function(e) {
e.preventDefault();
if (this.checked) {
jsq.checkAnswer(this.value);
}
});
});
$('#next').click(function(e) {
e.preventDefault();
jsq.changeQuestion(1);
});
You are only applying the event handler on the elements that are already present, and not on the one that will be created later.
The event handler should be on an element that is a parent to all the future elements.
Like this:
$('#question-options').on('change', 'input[type=radio][name=option]', function(e) {
// code
});
From jQuery documentation on on:
Event handlers are bound only to the currently selected elements; they must exist at the time your code makes the call to .on(). To ensure the elements are present and can be selected, [...] use delegated events to attach event handlers.
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers.
I have a server that dynamically(asp.net ) generate webpages that I can't alter.
On all pages I would like to capture all buttons clicked.
In JSFiddle https://jsfiddle.net/forssux/aub2t6gn/2/ is an example..
$(".checkout-basket").click (function ()
The first alert shows the 3 possible values,
but not the chosen item..
$(".button.button-dl").click(function ()
In jsfiddle this part doesn't get executed
Strangely on my real webpage I get the button clicked...but when I put it in the If then construction it fails to console.log the chosen item..
I hope somebody can explain me how to get these..
Kind Regards
Guy Forssman
//$("div.detail-info,table.checkout-basket").click(function () {
// var knopje = $(this).attr("class")//.split(" ");
// console.log(knopje + " knopje was clicked");
// if(knopje.indexOf("detail-info") > -1) {
// console.log("div class detail-info is clicked");
// }
// else if (knopje.indexOf("checkout-basket") > -1) {
// console.log("table class checkout-basket is clicked");
// }
// else {
// alert ("er is op iets anderes gedrukt");
// }
// capture click on download button in checkout-basket page
$(".checkout-basket").click (function () {
basket =[];
item="";
str = $(this).text();
str = str.replace(/\s\s+/g, ' ');
var str = str.match(/("[^"]+"|[^"\s]+)/g);
console.log("Array ",str);
for(var i=0;i<str.length;i++){
if(str[i] === "verwijder"){
console.log("Item= ",str[i+1]);
item = str[i+1];
basket.push(item);}
}
console.log("Basket contains ",basket);
//console.log("idValBasket ",idVal);
var test = idVal.replace(/\$/gi, "_").slice(0,-6);
console.log("test ",test);
var element = test.substr(test.length - 2)-1;
console.log("element ",element);
element=element-1;
item = basket[element];
console.log("Item finaal is ",item);
});
$(".button.button-dl").click(function () {
var addressValue = $(this).attr('href');
console.log("addresValue Basket",addressValue );
var re = /'(.*?)'/;
var m = addressValue.match(re);
console.log (" m basket is ",m);
if (m != null)
idVal = (m[0].replace(re, '$1'));
console.log("idVal Basket",idVal);
});
//This section captures the download in the detail page
$(".button").click(function () {
var downloadItem = document.getElementsByTagName("h1")[0].innerHTML
console.log("addresValue detail",downloadItem );
});
I never use click function, use on(*event*,...) instead:
$(".checkout-basket").on("click", function (){ /* CODE */ });
Check if visually there are a layout over the a layuot (a div, span, etc.)
Maybe a strange question and maybe i got it wrong, but why do you use push ?? if you want to delete an item ? btw also the example isn't working so maybe that is your problem
I am trying to make a seatchart. All seats are elements. I click first seat and after that i click the other one. Second one changed its colour but first one is not working. Why?
function seatObject(id, label, status, token){
this.id = id;
this.label = label;
this.status = status;
this.token = token;
}
var seats = [];
var currentSeat;
function initAll() {
$(".seatObj").each(function() {
var id = $(this).attr("id");
var temp = new seatObject("#" + id, "label" + id, "available", "");
seats[id] = temp;
$("#" + id).click(function () {
currentSeat = $(this).attr("id");
if (seats[currentSeat].status == "selected")
{
dequeueSeat();
}
else
{
enqueueSeat();
//alert($(this).attr("inkscape:label"));
}
});
});
}
No problem with your code dude...
You need little changes...
Ordinary Click does not attach an event handler functions for one or more events. So use on method - Attach an event handler function for one or more events to the selected elements.
$(".seatObj").on('click',"#" + id, function () {
currentSeat = $(this).attr("id");
if (seats[currentSeat].status == "selected")
{
dequeueSeat();
}
else
{
enqueueSeat();
//alert($(this).attr("inkscape:label"));
}
});
You can use class instead of id in on method.
Try wrapping the function in $( document ).ready(function() {}
works for me.