This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed 4 years ago.
I am trying to make a follow button - and when clicked it sends an ajax request to my backend script and if it executes it returns a value that triggers the button's text to go from "follow" to "following" - and that part works just fine. But the jquery part where I send the user data in a data-attribute "data-follow" won't toggle with "data-unfollow". It only works when you refresh the browser which means you can only click the follow button, see it and the data attribute to change to "unfollow" but if you click once more it does not work. I cant figure it out and i've done my searching at the stack.
ERROR
TypeError: $(...).attr(...) is undefined
HTML
<button id="follow" data-follow="1, username1, username2">
<div><img src="images/loggedin/follow.png"></div>
<div>Follow</div>
</button>
JQuery
$("button[data-follow]").click(function(){
var button = $(this);
var data = $(this).attr("data-follow").split(",");
alert(data);
button.find(" > div:last-child").animate({opacity: "0"},200, function(){
$(this).animate({opacity: "1"},200);
$(this).html("Following");
var dataValue = $(this).closest("#follow").attr("data-follow");
$(this).closest("#follow").attr("data-unfollow", dataValue);
$(this).closest("#follow").removeAttr("data-follow");
});
});
$("button[data-unfollow]").click(function(){
var button = $(this);
var data = $(this).attr("data-unfollow").split(",");
alert(data)
button.find(" > div:last-child").animate({opacity: "0"},200, function(){
$(this).animate({opacity: "1"},200);
$(this).html("Follow");
var dataValue = $(this).closest("#follow").attr("data-unfollow");
$(this).closest("#follow").attr("data-follow", dataValue);
$(this).closest("#follow").removeAttr("data-unfollow");
});
});
$(document).on('click', 'button[data-follow]', function() {...});
$(document).on('click', 'button[data-unfollow]', function() {...});
Try it this way.
More Generic answer!
You can simply change the state of a button by removing, adding and toggling css classes via jQuery.
At (document.ready) check if follwing or unfollowed state via your ajax request and change the button's css class.
if(following){
$("#buttonID").toggleClass('following_btn');
} else{
$("#buttonID").toggleClass('unfollowing_btn');
}
Once clicked, process your code in the web service. Follow if unfollowed or Unfollow if followed. Then if you're rturning the updated state use that to re-update your button's css class accordingly. Or perform ajax call to verify the new state after the button click's ajax call finishes.
CSS:
.following_btn{
//your following style
}
.unfollowing_btn{
//your un-following style
}
Hope it helps!
Related
To prevent answers like: 'is the JavaScript file loaded?' -> Yes, it is loaded, at the footer part of the page! I have checked that with a simple message to the console, which is displayed!
But:
I've got a page with a button:
<button id="portfolio-posts-btn">Load portfolio related blog posts</button>
And a file main.js:
var portfolioPostsBtn = document.getElementById('portfolio-posts-btn');
var portfolioPostsContainer = document.getElementById("portfolio-posts-container");
if (portfolioPostsBtn) {
portfolioPostsBtn.addEventListener("click", function () {
console.log("the button was clicked!");
});
}
The text the button was clicked! should be displayed in the console, but it stays empty!
Apparently, the button click is not recognized, and thus, the var portfolioPostsBtn is false, or NULL... -> the method addEventListener() is not fired ?
I don't see the cause for this; I checked the spelling, should I use single or double quotes? Please help?
Thank you!
I've had this happen to me before, since theres two ways to do this I just used the other.
The first is onclick="function()", this is used as an attribute inside the element. Ex:
function clicked(){
alert("button clicked");
}
<button onclick="clicked();">Press me</button>
exaplaination: When you add this attribute to this element and I do believe some others when the button is clicked the specified code inside the quotes of the attibute will run. It doesn't have to be a number, e.g. onclick="alert(12+4/2);". But this is more of HTML than JavaScript using this version
The other way is using what you've got which (to me) is a lot more difficult then it needs to be. Heres my example
var b = document.getElementById("btn");
b.addEventListener("click", blogged);
function blogged(){
alert("this post has been blogged");
}
<button id="btn">Blog it</button>
This side of things has more to do with JavaScript and Event listeners. But the problem with you're code is that you're putting the event listener after you call the if statement. Here's my solution
var portfolioPostsBtn = document.getElementById('portfolio-posts-btn');
portfolioPostsBtn.addEventListener("click", function(){
check();
});
function check(){
if(portfolioPostsBtn){
console.log("posted");
}
}
<button id="portfolio-posts-btn">press this to post<button>
Presumably you have made a decision not to use jQuery. You'll need to wrap your code in an event listener so that the code is executed when the DOM is ready.
document.addEventListener("DOMContentLoaded", function(event) {
var portfolioPostsBtn = document.getElementById("portfolio-posts-btn");
var portfolioPostsContainer = document.getElementById("portfolio-posts-container");
if (portfolioPostsBtn) {
portfolioPostsBtn.addEventListener("click", function () {
console.log("the button was clicked!");
});
}
});
The answer is found in the uploading of the file page-portfolio.php!
I found out that the id="portfolio-posts-btn", added later, was not updated - could be my mistake, or the SFTP upload extension in Brackets - I did not see an error message!
Anyway, the issue is solved!
One more question: "are there methods to check if an id exists?". That could make live easier!
All contributors, thank you for your answers!
Here is my HTML entry that fires the GenerateBill() Javascript at the moment :
<a class="btn btn-primary" id="loading-example-btn" data-loading-text="Loading..." onclick="GenerateBill()">Generate Bill</a>
Here is the GenerateBill() method, this all works fine, all I want to do is add the button state feedback
function GenerateBill() {
var url = '/PremiseProvider/GenerateBill';
var data = {
StartDate: $('#from').val(),
EndDate: $('#to').val(),
premiseProviderId: $('#PremiseProviderId').val()
};
$("body").load(url, data);
};
Here is a code snippet from the Bootstrap 3 official Site on how to implement the button state feedback:
<script>
$('#loading-example-btn').click(function () {
var btn = $(this)
btn.button('loading')
$.ajax(...).always(function () {
btn.button('reset')
});
});
</script>
My Question is how can I implement in my GenerateBill script, the bootstrap example uses an Ajax call, can I make it work without making too many changes to what I have?
If I might make a few suggestions that will both fix your issue and improve your code.
Instead of using an onclick event, add an event listener in your javascript, and call the function from there.
Add the .button('loading') call to that same event listener.
Don't leave off the href for an <a> tag. It will cause some browsers to not show the pointer correctly on hover.
Your link will look as follows:
Generate Bill
Leaving your GenerateBill() logic alone, the listener you need to add to your javascript:
$('#loading-example-btn').click(function () {
$(this).button('loading');
GenerateBill();
});
A working example of this code (with GenerateBill() simplified) is available here: http://www.bootply.com/VTSNA1XMcm
Comment box is not hiding after posting any thing.
currently using AJAX to post data. Just want to hide input area just after submitting it.
here is fully
working example
I also this code to open the comment box.
$(document).ready(function() {
$(".comment_button").click(function() {
var element = $(this);
var I = element.attr("id");
$("#slidepanel" + I).slideToggle(300);
$(this).toggleClass("active");
return false;
});
});
suggesting you check the example Here
Add the following to the success callback:
$("#slidepanel" + Id).hide();
Or, to match the fade in:
$("#slidepanel" + Id).slideToggle(300);
See here for updated jsFiddle. Note the success callback will not be called in jsFiddle, since the php page does not exist at http://fiddle.jshell.net/DkAMN/1/show/insertajax.php.
Im building a small application and I have some click events binded to some span tags that trigger AJAX requests to a PHP file which queries a MySQL database and spits out the results to populate the targeted area.
However, sometimes i will be clicking the buttons and I have conditionals in place to stop multiple clicking to prevent duplicate content being added numerous times.
I click on a button and firebug tells me that the ajax request had actioned more than once, sometimes it will multiply - so it will start by doing it 2 times or another time it will carry our the request 8 times on one click and obviously flood my content area with duplicate data.
Any ideas?
EDIT
Code for a button is as follows:
<span class="btn"><b>Material</b></span>
This would be enabled by
$('.btn').bind('click', matOption);
and this would be controlled by something like this
var matOption = function() {
$(this).addClass('active');
// remove colours if change of mind on materials
if($('#selectedColour').val() >= 1) {
$('.colour').slideUp(500).children().remove();
$('#selectedColour').val('');
$('.matColOpt .btn').html('<b>Material Colour</b>').removeClass('active').css('opacity', 0.55);
$('.btn').eq(2).unbind('click', colOption); // add click to colour
$('#stage h1 span').eq(2).fadeOut(500);
$('.paperOpt .btn').css('opacity', 0.55).unbind('click', selectPaper);
}
// ajax req for available materials
var cid = $('#selectedColour').val();
var target = $('#notebookOpts .matOpt ul');
$.ajax({
type: "GET",
url: ajaxFile+"?method=getMaterials",
beforeSend: function() {if($('.mats').children('li').size() >= 1) { return false; }},
success: function(data) {
target.append(data).slideDown(500);
$('.mats li').bind('click', matSelect);
},
error: function() {alert('An unexpected error has occurred! Please try again.');}
});
};
You're probably binding your matOption function more than once.
if(!window.matOptionBound){
$('.btn').bind('click', matOption);
window.matOptionBound = true;
}
If you have a code that binds an event handler to a DOM element repeatedly then that event handler does gets executed repeatedly on the event. so if your code such
$("span").click(myHandlerFunction)
gets executed thrice, then you have just told jQuery to fire myHandlerFunction thrice on every click of span. It would be good to make sure there is no such condition goign on in your code. If that is not true then please post your code so that I can help further.
PS: The safest way to do this will be as
$("span").unbind("click",myHandlerFunction).bind("click",myHandlerFunction)
Code:
$(".box-collapse").live("click",function(){
if(updating()) return false;
var b = $(this).parent().parent();
b.find(".album-div").stop(true,false).slideToggle();
$.ajax({
url: addNonce($(this).attr("href")),
success:function(data){
c = b.attr("id");
var resp = $('<div />').html(data);
var col = $("#"+c+" .box-collapse");
var ncol = resp.find("#"+c+" .box-collapse");
col.html(ncol.html());
col.attr('href',ncol.attr('href'));
resp.remove();
clearUpdate(data);
wait = false;
}
});
return false;
});
This click is being fired twice.
What happens:
Click to collapse and it sends a
response to save that collapse.
Click again to expand and it sends a
request to save the expand.
Click again to collapse and it is
fired twice (leaving it expanded, but collapse is saved).
Click again and it fires 4 times.
It only begins multiple firing AFTER the second click. It's baffling me.
If I remove the servicing of the data, it doesn't fire twice. What am I doing wrong on the service? (i.e. I only leave wait = false in success)
The other functions that I use in this call:
function updating(){
if(!wait) {
$("#loading").html('Updating...');
wait = true;
return false;
}
else {
$("#loading").html('Slow down...');
return true;
}
}
function clearUpdate(data){
var resp = $('<div />').html(data);
//alert(resp.find("#nonce").val() + " "+$("#nonce").val());
$("#loading").html(resp.find("#loading").html());
if(typeof(resp.find("#nonce").val()) == 'undefined'){
alert(data);
$("#loading").html("Fatal error. Your session could have ended. <a href='javascript:location.reload()'>Refresh</a>");
resp.remove();
return false;
}
else if(resp.find("#errorcode_").val() == "refresh"){
location.reload();
}
resp.find(".image-box").each(function(){
$("#"+$(this).attr("id")).find(".image-count").html($(this).find(".image-count").html());
});
$("#nonce").val(resp.find("#nonce").val());
wait = false;
resp.remove();
}
The wait flag prevents a request from being sent before the last has been serviced. I do this because I track a nonce and I have to get a new nonce after each request.
Again, if I remove the data servicing, it works fine. But I need to service the data to get the fresh nonce.
Plus, I'm not seeing it crash anywhere. Any help would be great. I'll post any other functions if needed. My full script is pretty large.
Here is the HTML of .box-collapse parent:
<div class='box image-box album-marker-visible image-box-w-20 image-box-list-0' id='portrait'>
<h2 class='box-header image-box-header'>
<input type='checkbox' name='albums[portrait]'>
<span class='image-box-header-move'>
<span class='image-album-number'>1</span>. Portrait <input type='hidden' class='image-album-anchor' value='portrait'>
<input type='hidden' class='image-album-portfolio' value='1'>
<span class='image-count'>(20)</span>
<span class='box-mover'>Move to: <a href='images?n=bridals,weddings,engagement,portrait&port=2&nonce=I8FX2BH841' title='Move to Wedding Portfolio'>Wedding Portfolio</a> Move: <a href='images?n=story,portrait,capture,press,concerts&port=1&nonce=I8FX2BH841'>down</a></span></span>
<span class='album-marker'> | <a href='images?ia=3&action=hide&nonce=I8FX2BH841' title='Mark album as hide' class='album-mark-hide album-mark'>hide</a></span>
<a class='box-mover-show box-collapse' href='images?expand=portrait&nonce=I8FX2BH841' title='Expand Portrait'>expand</a></h2>
There are multiple instances of .box and I didn't show the content after the h2 tag (that's why my div isn't closed).
As requested, this is the step by step process of what SHOULD be happening:
I click collapse on a .box-collapse instance. It sends its href.
jQuery toggles its slide.
On call back I get the new href from the link I just clicked. It should have changed query strings from expand=an_album to collapse=an_album or vise-versa. I also change the state from 'expand' to 'collapse'. I am searching through the response based on the id of the containing .box I just clicked. I am getting the correct response (collapse will change to expand), but jQuery slideToggles twice.
In clearUpdate() I update my nonce from the received data. If I don't receive a nonce, I die. I also update the image count for each .box.
I have placed an alert in the success, and on the first click, I get one alert. Click again, I get one alert. Click an THIRD time, I get two alerts. Fourth time, 4 alerts.
I have also done alert($(".box-collapse").length); in my clearUpdate() and the size does not change.
The error lies in the success function and/or clearUpdate. Since I am receiving the exact same HTML as I already have (minus the changes above), is it possible that I am reparsing the javascript and re-binding a click? But this doesn't seem feasible because I should be firing multiple times after the FIRST click.
Update
I added an alert in the $(document).ready() and I get the alert every time I get a response. So it is re-parsing the javascript and re-binding the click. My immediate fix will be to change it to : $(".box-collapse").die("click").live("click",function()) but I will add a query to disable the javascript header.
Thanks for the help!
This most likely creates the second click event if data contains the .box-collapse class
var resp = $('<div />').html(data);
Do you really need the class selector? Identifiers should be unique
that makes b.html() == $("#"+c+" .box-collapse").html() and b == col[0]
var col = $("#"+c+" .box-collapse");
This is worrying. It means that within resp there is an element with the same id as within b.
It is most likely the cause for when switching to click from live that the click event
happens twice.
var ncol = resp.find("#"+c+" .box-collapse");
Too little known about the content of .box-collapse or data to understand
col.html(ncol.html());
col.attr('href',ncol.attr('href'));
Now I think I finally understand.
Change the data to contain a JSON object or a simpler HTML structure and it should work like this.
Good example
data == { "innerText":"expand", "url":"images?expand=portrait&nonce=I8FX2BH841" }
col.html(data.innerText);
col.attr('href', data.url);
Bad Example
server response:
data == <span class="innerText">expand</span><span class="url">images?expand=portrait&nonce=I8FX2BH841</span>
var div = $("<div />").html(data);
col.html(div.find(".innerText").html());
col.attr(div.find(".url").html());
and adjust the clearUpdate function