Having a frustrating time, made even worse that the examples I see everywhere are not working in this instance. My assumption is because I am not understanding what elements are being loaded in which order.
The question is common enough, I have a button that I want to pass data from into the modal it opens. Let's say I want to pass in the id. My first idea was something like this which is the typical answer on Stack Overflow:
HTML:
<button class="btn btn-primary btn-block" data-toggle="modal" data-target="#myModal" id="product-button">Product Name</button>
JavaScript:
$(function() {
$('#product-button').click(function() {
var productId = $(this).attr('name');
$(".modal-body #testContainer").val(productId);
});
});
This is seen here:
Passing data to a bootstrap modal
However, this appears not to work for me. The kicker for my scenario is that the buttons are generated dynamically and I am wondering if because I've wrapped everything in a helper function and it ties itself to the page before the dynamic buttons are created and therefore doesn't know they exist and to listen for a .click event (they are created through a search, the buttons are the results of the search)
Anybody have any ideas on how I can go about passing data to a modal with this limitation?
If the buttons are generated dynamically, you have to use event-delegation to make it work:
$(function() {
$(document).on('click','#product-button',function() {
var productId = $(this).attr('name');
$(".modal-body #testContainer").val(productId);
});
});
Event Delegation:
Event delegation refers to the process of using event propagation (bubbling) to handle events at a higher level in the DOM than the element on which the event originated. It allows us to attach a single event listener for elements that exist now or in the future.
Related
I'm experiencing some (to me) unexpected behavior from event listeners using jQuery, but I expect it's just the underlying Javascript functionality that's confusing me. I think there's a good chance that the answer to my problem is fairly simple, but I can't figure it out.
I have a <div> with a status in it, which is 'New' by default. I want to be able to click on that <div> and bring up a pair of buttons, one which allow you to replace the 'New' status text with 'Complete', and another which will cancel the action, removing the buttons and displaying the 'New' status text again.
I can get everything to work, and it all works fine if I use a different element to display the buttons, but when I try to temporarily replace the text in the <div>, something strange happens.
When I try to reset the original click event for the <div>, what appears to happen is that the click on the cancel button also triggers the just-set click event as well. This re-displays the buttons, so it looks like the cancel button just didn't work. (I was stuck on that for a while...)
I've made a very simple JSFiddle that shows the problem I'm having:
https://jsfiddle.net/pn1q658w/4/
And there's also a slightly modified version that shows things working as long as I've got the cancel button in a different element than the element where I'm resetting the original click function:
https://jsfiddle.net/pn1q658w/3/
I assume this has something to do with the fact that I don't completely understand the way that the timing of these events works. The same click appears to be firing off an event that is set during an action taken because of that click, which I didn't expect, but may very well be how things are supposed to work.
My question is how can I have a click event which is trying to set a click listener on a parent element not fire off the new click at the same time?
Or, if I've completely misunderstood the nature of my mistake, an indication of what I have done wrong would be much appreciated. Thank you!
UPDATED
Just like #charlietfl said, I bleive that the simplest way is to separe the elements everyone with his own class, See WORKED FIDDLE HERE :
HTML :
<div id="elementId">
<span class='text'>New</span>
</div>
JS :
$(function(){
var newBtn = '<button class="btn btn-default complete">Complete?</button><button class="btn btn-default cancel">X</button>';
//Handle the click in the text span
$("#elementId").on("click","span.text",function(e){
$(this).html(newBtn);
});
//Handle the click in the complete button
$("#elementId").on("click","button.complete",function(e){
$('#elementId').html("<span class='text'>Completed</span>");
});
//Handle the click in the cancel button
$("#elementId").on("click","button.cancel",function(e){
$('#elementId').html("<span class='text'>New</span>");
});
});
I hope that this code help you.
The problem is you rewrite alaways content of div because you don't stop propagations.
I fix your problem with this code:
var oldCellContent = $('#elementId').text();
var newButtons = '<button class="btn btn-default complete">Complete?</button><button class="btn btn-default delete">X</button>';
$("#elementId").click(function(){
$(this).html(newButtons);
});
$("#elementId").on("click","button.complete",function(e){
console.log('The complete function fired.');
$('#elementId').text('Completed');
e.stopPropagation();
});
$("#elementId").on("click","button.delete",function(e){
console.log('The cancel function fired.');
$('#elementId').text(oldCellContent);
e.stopPropagation();
});
Need to understand that when you click on an element inside another element...you are still clicking on the parent as well.
Events "bubble" or "propagate" up the DOM tree all the way to the document...unless told to stopPropagation()
What you are seeing is expected behavior.
You can prevent that bubbling by:
$(selector).click(function(event){
event.stopPropagation();
});
There are some oddities in your code that could defintiely be streamlined
First you pass an element to the handler...then take properties of that elemnt to create a selector to find exactly the same element again in the DOM
var cellId = element.id;
$('#' + cellId)
Can be simplified to
$(element);// doesn't require searching for the ID
And can also be stored as a variable instead of doing the same selector over and over
var $el = $(element);
$el.off('click');
var oldCellContent = $el.text();
These simplifications are more efficient and easier to read
The simplest solution to all of this is wrap the "New" text in it's own element and put the "show buttons" click handler on that element and simultaneously hide "new".
Code will be much simpler and there won't be any propagation issues or need to use off() and then add same handler again.
I am having hard time while building e-commerce cart module with jquery.
Lets say that if i write a tags in html like this:
<div class="add-to-cart">+</div>
and then target it in my app:
this.$products,
this.$pa,
this.$ip,
this.$products = $('.shopperProducts'),
this.$pa = this.$products.find('.shopperAdd');
var self = this;
this.$ip = function() {
var init = function(action, product) {
/.../
};
self.$pa.on('click', function(event) {
event.preventDefault();
init('add', this);
});
};
This method is possible while im displaying products because they are displayed by php on page refresh so i have all the + links generated by php on html.
The problem is on the checkout file, this is the page when i display entire cart filled with products, cart must be generated and handled in jQuery and AJAX.
And code that i showed you doesnt work in cart page beacuse those links are appended for each product via jQuery into the DOM.
I have been study possible methods and there are few, the most in favour is to do this:
$(document).on('click', self.$pa, function(event) {
The problem with that solution is that it also is considered practice to be avoided due to high resources drain, i can see the difference in execution time myselfe, it takes a lot longer on low end devices. Is there some neat trick that can be used or method that is considered good practice to do in that situation?
<--- EDIT (Solution) --->
Instead of calling:
this.$products = $('.shopperProducts'),
this.$pa = this.$products.find('.shopperAdd');
on the beginning, i have to call it after i load elements into DOM and then they became targetable, then i just have to use self.$ip(); and event handlers can be attached. Without using any sort of workarounds, the solution was just to change order of executing commands.
There are two main strategies that you can use for adding click handlers for elements that you dynamically add to the dom.
One, You can add click handlers to the DOM element each time you create one
var addToCartButton = $('<div class="add-to-cart">+</div>');
addToCartButton.on('click', function(){
init('add', this);
};
// then you add your DOM element to the page
$('.container').append(addToCartButton);
Two, you can have a master click event listener on the page listen for all clicks where your buttons fall, and in your click handler, figure out whether the user is clicking on your element or not. This is ultimately more efficient and you don't have to add or remove event handlers each time you add elements to your page. This pattern is called event delegation, and here's another post on Stack that probably explains it better than I can
What is DOM Event delegation?
$('.container').click(function(event){
if ($(event.target).is('.add-to-cart') || $(event.target).parents().is('.add-to-cart')) {
// handle add to cart
}
})
BTW, your use of the self variable doesn't actually do anything, and neither does declaring this.$pa. You're basically accessing the property "$pa" of your this object, but not doing anything it.
I'm using the Knockout webmail tutorial as a template for a Single Page Application I'm working on.
However I'm having trouble implementing the click binder, I've had this sort of thing working before but his time the click events are being hijacked.
My view model has a function:
var self = this;
self.goToItem = function(item) { location.hash = "#/Item/" + item.id };
which is standard.
<h2>Saved Items</h2>
<div data-bind="foreach: savedItems">
<div data-bind="click: $root.goToItem">
<p data-bind="text: name"></p>
</div>
</div>
I've tried the:
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
but, tellingly, this came back empty although the foreach has data to loop over.
The problem now is that the mousevent click is now caught by jQuery, I haven't had to delve into this before, but I am sure knockout runs without jQuery. At the moment the only jQuery I use is for the Ajax calls.
So where do I go to inspect the events on DOM objects, preferably in Chrome?
How can jQuery butt in?
Have you tried to use clickBubble binding?
Preventing the event from bubbling
By default, Knockout will allow the click event to continue to bubble up to any higher level event handlers. For example, if your element and a parent of that element are both handling the click event, then the click handler for both elements will be triggered. If necessary, you can prevent the event from bubbling by including an additional binding that is named clickBubble and passing false to it, as in this example:
<div data-bind="click: myDivHandler">
<button data-bind="click: myButtonHandler, clickBubble: false">
Click me
</button>
</div>
Normally, in this case myButtonHandler would be called first, then the click event would bubble up to myDivHandler. However, the clickBubble binding that we added with a value of false prevents the event from making it past myButtonHandler.
More details about click binding is here http://knockoutjs.com/documentation/click-binding.html
If I replaced
click: $root.goToItem
with
click: goToItem
it works again.
It seems the jQuery event firing was a red herring, I compared it to a working system and it started off in jQuery. I had to brush up on my DOM debugging, which is no bad thing.
Is the $root superflous, it took someone else to point this out to me. I was sure that in a foreach loop you could only access the $root functions via $root.
I have the following code:
var $reviewButton = $('span.review_button');
$reviewButton
.live('click',
function(){
$('#add_reviews').show();
}
)
Later in the script, I use an AJAX call to load some content and another instance of $('span.review_button') enters the picture. I updated my code above to use '.live' because the click event was not working with the AJAX generated review button.
This code works, as the .live(click //) event works on both the static 'span.review_button' and the AJAX generated 'span.review_button'
I see however that .live is depracated so I have tried to follow the jquery documentations instructions by switching to '.on' but when I switch to the code below, I have the same problem I had before switching to '.live' in which the click function works with the original instance of 'span.review_button' but not on the AJAX generated instance:
var $reviewButton = $('span.review_button');
$reviewButton
.on('click',
function(){
$('#add_reviews').show();
}
)
Suggestions?
The correct syntax for event delegation is:
$("body").on("click", "span.review_button", function() {
$("#add_reviews").show();
});
Here instead of body you may use any static parent element of "span.review_button".
Attention! As discussed in the comments, you should use string value as a second argument of on() method in delegated events approach, but not a jQuery object.
This is because you need to use the delegation version of on().
$("#parentElement").on('click', '.child', function(){});
#parentElement must exist in the DOM at the time you bind the event.
The event will bubble up the DOM tree, and once it reaches #parentElement, it is checked for it's origin, and if it matches .child, executes the function.
So, with this in mind, it's best to bind the event to the closest parent element existing in the DOM at time of binding - for best performance.
Set your first selector (in this case, div.content) as the parent container that contains the clicked buttons as well as any DOM that will come in using AJAX. If you have to change the entire page for some reason, it can even be change to "body", but you want to try and make the selector as efficient as possible, so narrow it down to the closest parent DOM element that won't change.
Secondly, you want to apply the click action to span.review_button, so that is reflected in the code below.
// $('div.content') is the content area to watch for changes
// 'click' is the action applied to any found elements
// 'span.review_button' the element to apply the selected action 'click' to. jQuery is expecting this to be a string.
$('div.content').on('click', 'span.review_button', function(){
$('#add_reviews').show();
});
I have a link which has an onclick attribute, and there is a toggle event that I bind to the link (I know I'm committing a sin here by mixing these two, but there is nothing I can do about it.)
Now to this background, I have 2 scenarios:
The user clicks on the link - The order of execution of events is :- onclick first, then the toggle event bound via jQuery
Fire the click event via jQuery - The order of execution here is different, the bound event fires first then the onclick.
Something goes horribly wrong because of these 2 scenarios and the flipping of the order. I need the bound events to run first before the onclick. Is there any better way to do this than removing the onclick attribute on init and saving them to the link via .data() and then handling through the toggle event?
Another thing that I need to take care of (life is complicated), the link can be toggled through the querystring. i.e. if the user comes in from another page via another link, there will be a querystring parameter with the link id, which is read by another JavaScript function that does scenario 2 mentioned above.
So if the onclick is to be removed, it will have to be done on init.
What can I do to untangle this mess?
What is so wrong to remove the .onclick function and re-bind it afterwards (after you bound all your methods which should fire before) ?
HTML
<div id="foo" onclick="inline();">click me</div>
Javascript
function inline() {
alert('I was bound through onclick=');
}
$(function() {
var $foo = $('#foo'),
stored = $foo[0].onclick;
$foo[0].onclick = null;
$foo.bind('click', function() {
alert('I was bound via jQuery');
});
$foo.bind('click', stored);
});
After that code, the order of alerts would be:
'I was bound via jQuery'
'I was bound through onclick='
Demo: http://jsfiddle.net/3MKWR/