click function within another click function is called multiple times - javascript

i am in touch with jquery for the first time and ran into this: I am trying to create a dynamic input-form. A click function creates a new list-item with another click function nested into it (to provide a remove function for the clicked item).
When i execute the nested click function it appears to be called the number of instances that have been created of it.
Here is the code (i tried to remove as much as possible, but i am not quite sure where the error is - so i guess i left to much stuff in - sorry).
$("#addIngredient").click(function(e){
e.preventDefault();
var prefix = "form-"
var last = $("#IngredientForm ul li").last().html();
$("#IngredientForm ul").append("<li>"+last+"</li>");
var name = $("#IngredientForm ul li:last input").attr("name");
name = name.replace(prefix,'');
var count = parseInt(name[0]);
count += 1;
$("#IngredientForm ul li:last input").attr("name",prefix+count+"-weight")
$("#IngredientForm ul li:last select").attr("name",prefix+count+"-ingredient")
$("#IngredientForm ul li:last input").attr("id","id_"+prefix+count+"-weight")
$("#IngredientForm ul li:last select").attr("id","id_"+prefix+count+"-ingredient")
$("#id_form-TOTAL_FORMS").val(count+1);
$(".deleteIngredient").click(function(e){
e.preventDefault();
var aktuell = $(this).closest('li');
var formCount;
name = aktuell.children('input').attr("name");
name = name.replace(prefix,'');
counter = name.replace("-weight",'');
formCount = parseInt($("#id_form-TOTAL_FORMS").val());
aktuell.remove();
--formCount;
$("#id_form-TOTAL_FORMS").val(formCount);
for (var i = parseInt(counter); i<formCount; i++){
var newName = "form-"+(i);
$("#id_form-"+(i+1)+"-weight").attr("name",newName+"-weight");
$("#id_form-"+(i+1)+"-ingredient").attr("name",newName+"-ingredient");
}
});
});

This block
$(".deleteIngredient").click(function(e){...
attaches a clickevent to all .deleteIngredient elements, also those created before.
You have to put this block outsite the click event of #addIngredient. You can make the delete event to be attached also to every element added in the future.
$("#addIngredient").click(function(e){
// ...
});
$(document).on("click", ".deleteIngredient", function(e){
// ...
});

As the other answers have noted, the click handler is adding a click handler to every .deleteIngredient element every time you run it, which adds multiple handlers to all the previous elements.
When you add a new item to the list, you don't have to add a click handler to it. You can use delegation to create a handler one time that will apply to dynamically-added elements:
$("#IngredientForm").on("click", ".deleteIngredient", function(e) {
...
});
See Event binding on dynamically created elements? for more information.

Every time that outer "click" event happens, you're adding another "click" handler for the ".deleteIngredient" element(s). The .click function does not remove previously-assigned event handlers.
You can get rid of old handlers with .unbind or, preferably with new versions of jQuery, .off:
$('.deleteIngredient').unbind('click').click(function(event) {
// ...
});
No, the thing is, here I think you probably want to bind to the .deleteIngredient button that you're adding for the new ingredient. The code you've got — based on the reference to $('.deleteIngredient') — will affect all of the elements on the page with that class. My guess here is that you're adding a button or something for each <li>. Thus, what you should probably be doing is finding the button inside the newly-added structure:
$('#IngredientForm ul li:last .deleteIngredient').click(function(event) {
// ...
});

Use this format instead
$("#addIngredient").on('click', function() {
$(this).off();
});

Related

why doesn't my jquery click function work after changing element values?

So I'm making a small quiz app with object oriented JS using Object.create cloning method. I have an ol, and a function called showVals() that populates it with lis. That seems to be working fine. What I'm having trouble with is: my li click function to give the attr of ".selected' class seems to work intitially, but after I click to proceed and qn2.showVals() is called it is no longer giving the lis a class of selected when clicked.
The data for qn2 is there. Everything looks normal, except for the click function no longer working (giving the lis the class).
$(document).ready(function(){
qn1.showVals();
qn1.setAns(1); // calling question1 answer for now
$('li').click(function(){
$('li').removeAttr("class");
$(this).attr({"class": "selected"});
});
$('.proceed').click(function(e){
e.preventDefault();
if ($('.selected').html() == qn1.ctAns) {
if (confirm("You are correct")){
qn2.showVals();
qn2.setAns(3);
};
};
});
});
var qn1 = {
title:"The Mouth of Sauron",
qn: "How did 'The mouth of Sauron' meet his demise?",
img: "images/mouth-sauron.gif",
ans: ["Shot in the back", "Beheaded", "Drowned in a swamp", "Sacrificed"],
setAns: function(x) {
this.ctAns = this.ans[x]; //setting correct answer
},
showVals: function(){
$('#slide-title').text(this.title);
$('.question-box > p').text(this.qn);
$('#obj-img').attr("src", this.img);
$('ol').html('<li>'+this.ans[0]+'</li>'+'<li>'+this.ans[1]+'</li>'+
'<li>'+this.ans[2]+'</li>'+'<li>'+this.ans[3]+'</li>')
}
}
var qn2 = Object.create(qn1);
qn2.title = "Golemn";
qn2.qn = "What this dude's name?";
qn2.ans= ["Golemn", "Gimli", "Goober", "Poop"]
qn2.img = "images/golemn.gif";
This is likely because your li elements are dynamically added.
You should try using jQuery on(), which allows you to bind an event handler to the parent element which must already exists in your DOM, and then you can specify the child/descendant selector that will call the event handler. This child element may still be non-existent at the time you do the event binding. In such a case, you call on() like:
$('ol').on('click', 'li', function () {...});
where ol already exists.
Alternatively, you could always bind your click handler to your dynamically generated li elements after you have added them to your DOM. Although I think that is more processor-time consuming as I assume you have to do this for all quiz questions you ask your user.

Deactivate part of page on click

I'm trying to create a quiz in the style of Buzzfeed. Is there a way to deactivate a part of the page when the user clicks on an answer, so that users can no longer click on alternative options of the same question and thus distort the final score of the test.
I've found some similar topics here and here but I don't want to add overlays and I'm not using inputs in my code so I was wondering if there is an alternative route.
I created a codepen here http://codepen.io/kkoutoup/pen/ByGEoQ
$(document).ready(function(){
//create an array to store correct answers
var totalCorrect = [];
$('li').click(function(){
//caching variables
var $parent = $(this).parent();
var $span = $(this).find('.fa');
//check for .correct class
//if yes
if($(this).hasClass('correct')){
//add .correctAnswer class
$(this).addClass('correctAnswer');
//find next span and change icon
$span.removeClass('fa fa-square-o').addClass('fa fa-check-square-o');
//reduce opacity of siblings
$(this).siblings().addClass('fade');
//show answerText
var $answerReveal= $parent.next('.answerReveal').show();
var $toShowCorrect = $answerReveal.find('.quizzAnswerC');
var $toShowFalse = $answerReveal.find('.quizzAnswerF');
$toShowCorrect.show();
$toShowFalse.remove();
//add 1 to total correct array
totalCorrect+=1;
//get array's length
var $finalScore = totalCorrect.length;
console.log($finalScore);
}else{
//add .wrongAnswer class
$(this).addClass('wrongAnswer').addClass('fade');
//change icon
$span.removeClass('fa fa-square-o').addClass('fa fa-check-square-o');
//reduce opacity of its siblings
$(this).siblings().addClass('fade');
//show wrong Message
var $answerReveal= $parent.next('.answerReveal').show();
var $toShowCorrect = $answerReveal.find('.quizzAnswerC');
var $toShowFalse = $answerReveal.find('.quizzAnswerF');
$toShowCorrect.remove();
$toShowFalse.show();
//locate correct and add respective class
$parent.find('.correct').addClass('correctAnswer');
};
});
});//end dom ready
Any ideas?
Thanks
What we're doing is removing the click function from the click event of the selected set of options - in this case, all the children of the elements parent. Thus when the user tries to click again, the click function would not be called on the click event.
Add the following line in click function because we want to trigger the disable as soon as any option is clicked on
$(this).parent().find('li').off("click");
Here is the updated codepen
Be aware of one thing - .off('click') removes all event listeners of type click from the element. If you want remove just this function, assign the function to a variable and then use the variable in the on and off calls. Like below:
event_fce = function(event) {
//do stuff here
$(this).off('click', event_fce);
}
$('li').on('click', event_fce);
You could turn off click as soon as one option is selected (right/wrong), like below:
Codepen updated
$(this).siblings().off("click"); //add this to where appropriate, such as either in `if` or `else` or both.

Writing this jQuery click function in JavaScript

I am slowly making my way from jQuery to vanilla JS, but I am struggling with wanting to write the following in JavaScript, would I have to setup a 'for' loop for each element? How would I target the child elements an an ID?
Here's what I would want to understand:
$('#id li').click(function(){
$(this).addClass('active');
});
I understand document getElementById to grab the '#id' but how would I achieve getting the child <li> elements? And how would I get the 'onclick' function to work for ANY list element clicked? Would I have to setup a 'for' loop for each element? Any help much appreciated!
Here is a JSFiddle that does what you want:
http://jsfiddle.net/digitalzebra/ZMXGC/10/
(function() {
var wrapper = document.getElementById("id");
var clickFunc = function(event) {
var target = event.originalTarget || event.target;
target.className = "active";
};
wrapper.addEventListener("click",clickFunc);
})();
A little bit of an explanation is in order...
First, I'm using a self executing function to fetch the wrapper div, using getElementById(). This is equivalent to using an id selector in jQuery: $('#id')
Next, I'm attaching a click handler to the element using the function addEventListener() and passing in an event type of click.
This binds the click handler function to the div element, the same as jQuery's click() would do. I'm using something called event bubbling, where a click event on any of the children of the wrapper will call the click handler attached to the wrapper.
Once the user clicks on the element, our function is called. originalTarget or target is the element that the user actually clicked in to, which in this case will always be an li. I'm then setting the class on that element to active.
Here is example for above question
http://jsfiddle.net/G3ADG/2/
(function() {
var id = document.getElementById("id");
var liele = id.getElementsByTagName("li");
for (var i = 0; i < liele.length; i++) {
liele[i].addEventListener("click", function () {
this.className = "active";
})
}
})();
Well I really liked Polaris878 solution as he is not using any loop inside it. In my solution first get HTML node information using document.getElementById this works similarly to $("#id"). than in next step I am fetching all tag type of "li" which are children of "id", than on this li tag array adding event listener to listen click functionality
className is one of attribute that allow to add class on that Node
I have tested above code in mozilla and chrome
This will work (IE >= 10), not want to search classList.add() replacement for IE<10,
var elems = document.querySelectorAll('#id li');
for (var i = 0; i < elems.length; i++) {
var elem=elems[i];
elem.addEventListener("click", function (e) {
this.classList.add('active');
});
}
fiddle

jquery actions firing once, but not again

forgive the code being bulkier than necessary, will tidy it up in due course.
everything seems to be working and yet, when you click a link after it's content has already been 'active' nothing happens.
i'm sure it's something simple but i can't see it.
EDIT: the following code now works in FF and Chrome, but does not work in IE8. Any ideas?
$(function(){
//initialize active link to not have a link on ready.
var linkId = $('#pricing_body div.active').attr('id');
var activeLink = $('#pricing_nav ul li#'+linkId+' a'); //just the link itself.
activeLink.parent().addClass('activeSection');
//var activeLinkContents = activeLink.parent().html(); //the link contained in the the list item and it's contents.
//alert(activeLinkContents);
var linkContents = activeLink.html(); //text content of the link.
activeLink.parent().html(linkContents);
//when link is clicked, store it's text for assignment after <a> is stripped out.
$('#pricing_nav ul li a').live('click',function(){
var clickedId = $(this).parent().attr('id');
var clickedLinkContents = $(this).html();
$(this).parent().addClass('activeSection');
$(this).parent().html(clickedLinkContents); //replaces <a><span>name</span></a> with just the span and text.
//fadeOut active div and give it inactive class. get list item with same id as div we are fading out.
$('#pricing_body div.active').fadeOut('500',function(){
$(this).removeClass('active').addClass('inactive');
var divId = $(this).attr('id');
var sisterLink = $('#pricing_nav ul li#'+divId);
sisterLink.removeClass('activeSection');
sisterLink.html(''+sisterLink.html()+''); //put link in between <li>.
//fadeIn the div with id of the link that has been clicked.
$('#pricing_body div#'+clickedId).fadeIn('500',function(){
$(this).addClass('active').removeClass('inactive');
var newActive = $('#pricing_nav ul li#'+clickedId);
});
});
});
});
Use live method to attach events to the elements. Here is the documentation.
Try:
$('#pricing_nav ul li a').live('click', function(){
---------
---------
---------
});
EDIT:
In reply of comment.
.live()
The .live() method is able to affect
elements that have not yet been added
to the DOM through the use of event
delegation: a handler bound to an
ancestor element is responsible for
events that are triggered on its
descendants.
.bind()
The .bind() method is the primary
means of attaching behavior to a
document. All JavaScript event types,
such as focus, mouseover, and resize,
are allowed for eventType.
Here is SO Question on this difference.

Issue with selectors & .html() in jquery?

The function associated with the selector stops working when I replace it's contents using .html(). Since I cannot post my original code I've created an example to show what I mean...
Jquery
$(document).ready(function () {
$("#pg_display span").click(function () {
var pageno = $(this).attr("id");
alert(pageno);
var data = "<span id='page1'>1</span><span id='page2'> 2</span><span id='page3'> 3</span>";
$("#pg_display").html(data);
});
});
HTML
<div id="pg_display">
<span id="page1">1</span>
<span id="page2">2</span>
<span id="page3">3</span>
</div>
Is there any way to fix this??...Thanks
Not sure I understand you completely, but if you're asking why .click() functions aren't working on spans that are added later, you'll need to use .live(),
$("#someSelector span").live("click", function(){
# do stuff to spans currently existing
# and those that will exist in the future
});
This will add functionality to any element currently on the page, and any element that is later created. It keeps you have having to re-attach handlers when new elements are created.
You have to re-bind the event after you replace the HTML, because the original DOM element will have disappeared. To allow this, you have to create a named function instead of an anonymous function:
function pgClick() {
var pageno = $(this).attr("id");
alert(pageno);
var data="<span id='page1'>1</span><span id='page2'> 2</span><span id='page3'> 3</span>";
$("#pg_display").html(data);
$("#pg_display span").click(pgClick);
}
$(document).ready(function(){
$("#pg_display span").click(pgClick);
});
That's to be expected, since the DOM elements that had your click handler attached have been replaced with new ones.
The easiest remedy is to use 1.3's new "live" events.
In your situation, you can use 'Event delegation' concept and get it to work.
Event delegation uses the fact that an event generated on a element will keep bubbling up to its parent unless there are no more parents. So instead of binding click event to span, you will find the click event on your #pg_display div.
$(document).ready(
function()
{
$("#pg_display").click(
function(ev)
{
//As we are binding click event to the DIV, we need to find out the
//'target' which was clicked.
var target = $(ev.target);
//If it's not span, don't do anything.
if(!target.is('span'))
return;
alert('page #' + ev.target.id);
var data="<span id='page1'>1</span><span id='page2'>2</span><span id='page3'>3</span>";
$("#pg_display").html(data);
}
);
}
);
Working demo: http://jsbin.com/imuye
Code: http://jsbin.com/imuye/edit
The above code has additional advantage that instead of binding 3 event handlers, it only binds one.
Use the $("#pg_display span").live('click', function....) method instead of .click. Live (available in JQuery 1.3.2) will bind to existing and FUTURE matches whereas the click (as well as .bind) function is only being bound to existing objects and not any new ones. You'll also need (maybe?) to separate the data from the function or you will always add new span tags on each click.
http://docs.jquery.com/Events/live#typefn

Categories

Resources