Alright, so I'm making a Facebook-style chat. But that doesn't really matter.
See here:
http://jsfiddle.net/SkHme/7/
Nice and pretty, right? Well, there's a problem. Notice this line:
<div class="conversation EmperorCuzco" onclick="setActive('EmperorCuzco')">
See the onclick attribute? Well, it's not working. However, I have confirmed that the function itself DOES work. (if you run it just like that in the JavaScript, it runs like a dream) I have further confirmed that the function is not the problem by attempting to replace the onclick value with a simple alert('blah'), but that doesn't work either.
So, what's up? I'm guessing that something in my JavaScript is somehow disabling something, but I have absolutely no idea what it could be, nor how I could go about fixing it. I did some web searching, but couldn't find anything that helps.
What's going on?
Your setActive function is defined inside the scope of the $(document).ready handler. Move the function outside that function so that it is in the global scope.
Right now it looks like this:
$(document).ready(function()
{
// ...
function setActive(new_conversation)
{
// ...
}
});
Now change that to:
$(document).ready(function()
{
// ...
});
function setActive(new_conversation)
{
// ...
}
Really though, you should separate your content from your interactions and bind those event handlers in your script itself. Something like:
// Refers to the last clicked conversation *button*
// Initialize to empty jQuery object
var $active_conversation = $([]);
// Binding on #chat, targeting click events on .conversation_button children
$("#chat").on("click", ".conversation_button", function() {
// Hide currently active conversation
$active_conversation.hide();
$active_conversation.siblings('.conversation_button').removeClass("selected");
// Set as currently active conversation button
// Note: this refers to the clicked <div class="conversation_button">
var $this = $(this);
$active_conversation = $this.siblings('.conversation');
// Show this conversation
$active_conversation.show();
$this.removeClass("alert").addClass("selected");
});
Some advantages of this approach:
You don't need different classes for different conversations. By storing the actual conversation DOM element (as a jQuery object) in $active_conversation, the conversation can be identified without any extra processing.
You can add and remove whole list items with a conversation without assigning new event handlers. In the sample above, the event handler for all .conversation_button elements is defined at the level of #chat. For more about this binding mechanism, read up on .on (or .delegate for pre-1.7).
Here, have an updated fiddle! :-)
If all you say is really true (bad mistakes happen), the only thing that can make this is that an other event handler which takes your event before uses stopPropagation() or return false;
A quick check that can do is replace onclick with onmousedown or onmouseup, and see if you alert become visible.
Related
I have a Chrome extension that intercepts and checks tweets before they get posted. To do this, I've add an event listener to the Tweet button. Sine the content is dynamic, I use the solution proposed in this thread:
initialize : function() {
let that = this;
let jsInitChecktimer = setInterval(checkForJsFinished, 111);
function checkForJsFinished () {
if (document.querySelector("div[data-testid='tweetButtonInline']")) {
clearInterval (jsInitChecktimer);
console.log("Button found");
that.addSubmitNewTweetClickHandler();
}
}
},
addSubmitNewTweetClickHandler : function() {
let that = this;
let buttonSubmitTweet = document.querySelector("div[data-testid='tweetButtonInline']");
buttonSubmitTweet.addEventListener('click', function(e) {
console.log("CLICK");
// Stop default event from happening
e.preventDefault();
e.stopImmediatePropagation();
// Do stuff
});
},
If the tweet passed the checks alright, it gets submitted by programmatically triggering the event using .trigger('click').
This works fine, but only once. After a tweet has been submitted and posted, the event listener on the Tweet button is gone, and I cannot intercept the next tweet to check it. I've tried calling initialize() after submitted again -- maybe the button gets removed and newly added to the DOM (it actually disappears fire a moment when submitting a tweet) -- but the querySelector finds the button immediately. But even after calling initialize() again, no click even on the Tweet button fires.
What could be the issue here? My problem is that I don't even know where to look for and how to debug this.
After many more hours, I've finally figured it out. The problem was essentially the highly dynamic content of the new Twitter website. After submitting a tweet, the Tweet button gets indeed removed and added again. In needed to do a serious of changes:
Use a MutationObserver to keep track of any changes. Every time there's a change, call the initialize() function. To avoid too many calls, I do this in case of certain changes (unnecessary detail here)
Change the addSubmitNewTweetClickHandler() method so that the event listener first gets removed in order to avoid duplicate listeners (please note that I use objects hence the use of this compared to my original question)
addSubmitNewTweetClickHandler : function() {
let that = this;
let buttonSubmitTweet = document.querySelector("div[data-testid='tweetButtonInline']");
buttonSubmitTweet.removeEventListener('click', this.handleSubmitNewTweetClick );
this.handleSubmitNewTweetClick = this.handleSubmitNewTweetClick.bind(this)
buttonSubmitTweet.addEventListener('click', this.handleSubmitNewTweetClick );
},
This change required to create the reference function handleSubmitNewTweetClick
Overall, it's still not a perfect solution since I call initialize() many unnecessary time. However, I failed to reliably identify when the Tweet button was added to the document. When I used the MutationObserver none of the added nodes had the attribute data-testid which I need to identify the correct button. I have node idea why this attribute was not there. Maybe the attribute is added some times after added to button, but even with an additional MutationObserver looking for attribute changes I could detect this.
Anyway, it works now and it's only for a prototype.
I am not great at javascript/jquery for the most part but I know how to get some software to work. But my issue is that I have a whole bunch of
$("body").on("click", "button#thisid", function(event) {
//do stuff here
});
There are alot of the on clicks that use jquery post and get functions but they all have tiny and simple differences that i need to have get sent through. I dont want every single button to have an onclick event but I am not sure how to bind the event to a large list of items that need to have it attached to.
I have been trying to come up with some way to slim all these down but I want to have the best approach instead a whole bunch of crash and fails. So I am reaching out to people who know more than me in order to lead me in the correct path.
Please help
Considering your elements are dynamically injected, you will need to apply the click handler to an element that always exist on page load:
$(document).ready(function() {
$(document).on("click", "button.target", function() {
console.log($(this)[0].id);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="1" class="target">One</button>
<button id="2" class="target">Two</button>
In the above example, the click handler is applied to document, and triggers whenever a button element with the class of target is clicked, running the code inside the function.
To combine the .get() and .post() functions, you'll have to find synonymous data. Keep in mind that you have access to $(this), so you can extract the relevant ID if need be :)
Hope this helps!
I dont exactly get what you want to do...
But the $("body") is the jquery selector which defines on which elements your listener will be bound to.
So if you want to create a listener for more different elements the probably easiest solution is creating a class like "listenedElement" which you give to every element you want the listener to react to and write write the selector like this
$('.listenedElement').on( "click", function() {});
If you just look for click listeners this one looks pretty nice as well:
https://api.jquery.com/click/
A nice way I find is having a one line for each button like so:
$('#model').on('click', openModel());
$('#openDropdown').on('click', openDropdown());
$('#shoppingCart').on('click', shoppingCart());
And then defining each of these functions:
function openModel() {
// Stuff here
}
function openDropdown() {
// Stuff here
}
function shoppingCart() {
// Stuff here
}
So instead of writing the function as a parameter, I find it neater to just do it separately and call it like above.
I just got through figuring out that I need to watch out for duplicate event handlers in jquery if I'm dynamically assigning them multiple times as described here: http://www.parallaxinfotech.com/blog/preventing-duplicate-jquery-click-events
Do I need to watch out for this or handle it somehow if I'm declaring a function dynamically within another function multiple times? How does JavaScript really handle this? Does it only use the last function that was called or does it only instantiate a function once at load time? From what I can tell it's not running the function multiple times.
$(document).on("click",".button",function() {
function alertThem()
{
alert('Clicked!');
}
alertThem();
});
JavaScript will remember every function you're assigning it.
$('button').click(function(){
alert('hi')
})
$('button').click(function(){
alert('hi')
})
The code above will alert "hi" twice. If you're assign new function and you want to clear the old one, you can do unbind().click(). what it will do is it will unbind all events, or you can do unbind('click') which will unbind just the click. see https://jsfiddle.net/rznbtc1p/
$('button').click(function(){
alert('hi')
})
$('button').unbind().click(function(){
alert('hi')
})
The link you provided does not work (gives me timeout) so I hope I understood what you asked.
About what happens there:
In your script you created a closure and bound it to a click event. Each time you click on the element with class button, the anonymous function is triggered. Each time is triggered it defines function alertThem(), and calls it. Only once defines it, only once calls it. The scope of that function is its parent, the closure. That function is not defined outside that scope, so no need to worry about double definition.
Side note here: Personally as a rule of thumb don't think is a good idea to define functions like this, but if it suits your project... go for it.
Now about duplication. Since I cannot see the link, I think you are referring to double event binding.
In js can bind any number of events to the same element. You can for example bind on click something that says "Hi, you clicked me", then bind also on click something that says "Hi, you received a message before saying you clicked me". When you click that element, you will see both messages.
This can actually become a problem. You have 3 options:
Be really careful how you bind events
Keep tracking of what you bound
Check if events are already bound (although that is a bit unreliable). You can check how here: jQuery find events handlers registered with an object
In your code snippet, you aren't creating duplicate event handlers.
What is happening in your snippet is that you are creating a new function alertThem within the scope of your click handler function and then executing it in the line below.
I wrote an alternative to the jQuery Accordion, as that didn't offer multiple open section support (any idea why they opted to not include support for that? What's the history there?). I did some research on StackOverflow, as well on Google to see what other options others came up. I needed something that could be used on the fly on multiple elements.
After seeing several solutions and experimenting with them, in the end, I wrote my own version (based on Kevin's solution from http://forum.jquery.com/topic/accordion-multiple-sections-open-at-once , but heavily modified).
jsFiddle can be found here: http://jsfiddle.net/3jacu/1/
Inline Code:
$(document).ready(function(){
$.fn.togglepanels = function(){
return this.each(function(){
h4handler = $(this).find("h4");
$(h4handler).prepend('<div class="accordionarrow">▼</div>');
$(h4handler).click(function() {
$(h4handler).toggle(
function() {
barclicked = $(this);
$(barclicked).find(".accordionarrow").html('►');
$(barclicked).next().slideUp('slow');
window.console && console.log('Closed.');
return false;
},
function() {
barclicked = $(this);
$(barclicked).find(".accordionarrow").html('▼');
$(barclicked).next().slideDown('slow');
window.console && console.log('Open.');
return false;
}
);
});
});
};
$("#grouplist").togglepanels(); }
Oddly, the accordion arrow at the right side stopped working once I pasted it in jsFiddle, while it works in my local copy.
In any case, the issue is that toggling isn't working as expected, and when it does, it fires duplicate toggle events which result in it closing, opening, then ultimately closing the section and it won't open from that point on (it toggles open then closes back). That's assuming it works! At first, it won't work as it doesn't respond. I think there's a logic error somewhere I'm missing.
From what I wrote/see in the code, it searches the given handle for the corresponding tag (in this case, h4), pops the handle into a variable. It then adds the arrow to the h4 tag while applying the accordionarrow class (which floats it to the right). It then adds a click event to it, which will toggle (using jQuery's toggle function) between two functions when h4 is clicked.
I suspect the problem here is that I may be mistakenly assuming jQuery's toggle function will work fine for toggling between two functions, that I'll have to implement my own toggle code. Correct me if I'm wrong though!
I'm trying to write the code so it'll be as efficient as possible, so feedback on that also would be appreciated.
Thanks in advance for your time, assistance, and consideration!
You have the toggle binding (which is deprecated by the way) inside of the click binding, so a new event handler is getting attached every time you click the header.
As a random aside you should also fire events within the plugin (where you have the console lines would make sense) so that external code can react to state changes.
I believe your issue is the $(h4handler).click(function() { you have wrapped around the toggle listener. Essentially what this was doing was making so every click of the tab was adding the toggle listener, which was then also firing an event. Removing the click listener will have the behaviour you expect.
You forgot to paste the trailing characters ); to close the function call to jQuery function ready. Fixed: http://jsfiddle.net/LeZuse/3jacu/2/
UPDATE: I've just realised I did not really answer your question.
You are duplicating the .toggle functionality with binding another .click handler.
The doc about .toggle says:
Description: Bind two or more handlers to the matched elements, to be executed on alternate clicks.
Which means the click event is already built in.
NOTE: You should use local variables instead of global, so your plugin won't pollute the window object. Use the var keyword for this:
var h4handler = $(this).find("h4");
I have a div that when the page is loaded is set to display:none;. I can open it up using this simple code:
$(".field-group-format-toggler").click(function()
{
$(".field-group-format-wrapper").css({'display':'block'});
});
Once it's opened, I'd like the user to be able to close it so I tried using the .is(':visible') function and then wrapping my original code in an if statment but this time using display:none;
if($('.field-group-format-wrapper').is(':visible')){
$(".field-group-format-toggler").click(function()
{
$(".field-group-format-wrapper").css({'display':'none'});
});
}
This does not seem to work though and I am not getting any syntax errors that I know of.
I also tried this:
if ($('.field-group-format-wrapper').is(':visible'))
$(".field-group-format-toggler").click(function () {
$(".field-group-format-wrapper").css({'display':'none'});
});
... but that did not work either.
You can just use the toggle function:
$(".field-group-format-toggler").click(function()
{
$(".field-group-format-wrapper").toggle();
});
This will show the '.field-group-format-wrapper' elements if they are currently hidden and hide them if they're currently visible.
FYI the reason your code snippet in your question wasn't working is because you're only checking the visibility of the elements on dom ready, rather than on each click - so the event handler to show the elements will never be attached.
I guess your function is only being called on page load at which time all divs are hidden.
Why not check the visibility in the click event handler?
$('.field-group-format-toggler').click(function(){
var $wrapper = $('.field-group-format-wrapper'); //Maybe $(this).parent()?
if($wrapper.is(':visible'))
$wrapper.hide();
else
$wrapper.show();
As already mentioned, you can use the toggle function to achieve what you want.
To add a bit of extra information, when attaching events like you're doing, you're actually using a subscription model.
Registering an event puts it in a queue of events subscribed to that handler. In this case, when you add the second event to change the CSS, you're adding an event, not overwriting the first one.
Whilst thing isn't actually causing your problem, it's worth being aware of.