Explanation of 'bind to click event' with scoping - javascript

I have this problem, but in short: I have a self calling function (function TCC() {...})(); that generates two sections of elements, among other things. These elements are generated with the class btn. Another JQuery function of $('.btn').click(function () {...}; [with an appropriate console.log() ] is showing that all elements on the page with class btn are being clicked except the two aforementioned sections' elements.
One comment suggests "The issue occurs because you bind the click event, before the "column button generator" loop.", and a suggested solution of:
$.each(cols, function(i) {...}).done(function(){ //Add click event handler now });
First question is can someone elaborate on 'because you bind the click event, before the "column button generator" loop.'
Second question is about the proposed solution. Since I have two separate lines of code for generation, and the $('btn').on('click', function() {...}); block is over 50 lines, how do implement a solution without duplicating the lines of code, as I would assume the .done() method would require this. The page in question is here.
The current scope structure of the generation code relative to the .on('click') is as follows (sorry for pic, but StackOverflow didn't like the formatting for some reason):
Does the scope of the generation lines of code effect this, and if so, can I pull them out of the self calling function to fix the 'bind to click event' the suggestion spoke of?
I appreciate your help and expertise, as learning programming from scratch, these types of structure lessons are often left out of the online learning tools often included in textbooks.

You really should've posted the actual code. But no matter. I think the only issue why your click events are not being called, is that you've used an incorrect syntax.
The on function of jQuery, has the following syntax -
HTML
<div class='button_container'>
<button> This is a button</button>
<button> This is a button</button>
</div>
JavaScript
$('.button_container').on('click', 'button', function(){
console.log('I have been clicked!');
});
//Note the the second selector 'button' within the declaration
Since the on function basically replaces the live and delegate functions, the handler is now defined for all buttons in div.buttons, irrespective of when and how it was created (meaning dynamically created). This can be bit misleading.
If you do a simple select of the sort
$('button').on('click', function(){ /* do something **/ })
jQuery assumes that you are talking about direct event binding - i.e, the dom element is already loaded. It won't affect any dynamically created elements (mind you, if you attach the bindings after the creation code, it will still work on those elements, as they are already present in the DOM).
Unless you provide the second selector, this the default behavior. This is a bit confusing, but moving on. If you want to target those elements before actually creating them, do something of this sort
//Right syntax
$(document).on('click','button', function(){ /* do something **/ })
//This will bind to all buttons

Related

Assigning JQuery On Click Function in For Loop

I have a function that dynamically creates div elements based upon whatever input is given, and lets them choose certain items by clicking on each div. I have it so that if the div is clicked, a function (named checkToggle) is called that makes it looks like it is selected and adjusts some related variables. There is a checkbox in the div element that is toggled by this function (hence its name). Long story short, I had to jump through some hoops to get it to work, most of which I don't even remember. Please don't ask me about that.
The point of this question is this. I initially used the following JavaScript code to run the function when the checkbox was clicked. It was assigned by the main function, which created these div elements using a for loop.
document.getElementById(`${itemID}-checkbox`).onclick = function() {
checkToggle(`${itemID}-checkbox`);
};
This works, but I wanted to try to convert all of my onClick functions to JQuery. Here is the JQuery alternative I created.
$(`${itemID}-checkbox`).on(`click`, function() {
checkToggle(`${itemID}-checkbox`);
});
While the code itself seems to be fine, it does not work. It seems as if JQuery functions cannot be created like this in a for loop or something. It is applied after the element is created and put in its place, so I don't think it has anything to do with the element not being ready. I am also having the same issue with 2 other similar cases. Any idea as of why this isn't working?
Let me know if more information is needed and if so, what kind of information is needed.
You need to update the selector to Target HTML id using the # character. Simply prepend the character to the query:
$(`#${itemID}-checkbox`).on(`click`, function() { checkToggle(`${itemID}-checkbox`); });
It would also apply to DOM methods querySelector or querySelectorAll as well.
Hopefully that helps!

Attaching event listeners using OnClick= or with script

Although it has already been asked, I want to adress the issue of correct jQuery programming.
Method #1:
<script>
function DoClickAction() {
// Some work
return false;
}
</script>
Do some work
VS
Method #2:
<script>
$(function() {
$("#ActionButton").on("click", DoClickAction);
}
function DoClickAction() {
// Some work
return false;
}
</script>
Do some work
I'm having a discussion with my colleagues about this, and my opinion is that both methods have enough pro and cons to not be able to say "this is the right way", but if I have to choose I tend to prefer Method #1, this is why:
Method #1 pros:
When debugging someone else code, you can easily follow which jQuery code is executed when somebody presses the link.
When you dynamically load (AJAX call) the content, it will always work, no need to rebind your jQuery events.
Method #2 pros:
It will produce less HTML code for the browser to download, because the script file will be cached and the onclick attribute is not necessary. Although this example uses more code.
You can re-use the code easily by using the same attributes, although using the onclick with 1 function is kind of the same thing.
What are your thoughts on this?
Instead of listing the pro's of either method, let me focus on the con's of method 1:
Change a function name == change the entire markup
All event handlers reside in the global scope. Working with closures can be a bit of a pain, then.
adding new elements dynamically (through JS or via ajax response) means that you'll either have to parse the markup and add the attribute one by one, or you'll have to send markup containing, essentially, JS function calls. Not safe, not clean
Each attribute is a new listener. The more attributes you have, the heavier the event loop will become
Mixing JS and HTML is not considered good practice. Think of it as separation of concern. The markup is there to provide the client with a UI. JS's job (in a browser) is to enhance the user experience. They have to work together, but have different tasks. Hence, they should be treated as separate entities.
As far as the second method goes, the only "cons" I can think of are:
Your code is slightly harder to understand, but if somebody can't work out what an event listener is, he shouldn't be working on your code, IMO.
Debugging can be harder, and older browsers might leak (jQ does contain an awful lot of X-browser related code, so it doesn't apply here. It does when you're writing vanillaJS)
In addition to this, method2 has another major pro, that you've not listed: delegation. At first, delegation looks hard, but It's easy, jQuery's $.delegate makes it easier, still, using $.on with a selector also delegates the event.
Basically, delegation allows you to deal with all events, for example click, for the entire page, or a section of the page, using a single listener. This as opposed to binding the event to each and every element. Thus: 1 listener on the event loop versus tens/hundreds. It's pretty obvious which is the more performant way of doing things.
Suppose you have a navigation div on a page, that looks like this:
<div id='nav'>
<ul>
<li id='nav-home'>Some pseudo-link</li>
<li id='nav-page1'>Another</li>
</ul>
</div>
You want to pick up on the user, clicking one of the <li> tags. The first method you listed makes for a right mess: <li id='nav-home' onclick='clickNav(event, this)'>. I'm passing the event object and this (a DOM reference) to have access to everything delegation gives me access to.
Using delegation, I can simply do this:
//jQ
$('#nav').on('click','li',function(e)
{
$.ajax({//you know the gist
url: 'ajax/' + $(this).id().replace('nav-',''),
success: function(){}
});
});
//vanillaJS:
document.getElementById('nav').addEventListener('click',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (e.tagName.toLowerCase() === 'li')
{
//perform ajax call
}
},false);
I myself am very much partial to #2, as it provides a clean separation of JavaScript and HTML. The negatives of not having the action of a button be immediately visible in the HTML can be completely negated by browser plugins.
Furthermore, as you've already stated, sometimes I want to attach an onclick event to, say, every row of a table, and setting the OnClick attribute of an element on every row is much more wasteful than simply attaching a click handler to each of them with a single line of code elsewhere.

Jquery remove function follow up

I submitted this question last week:
chrome not working with jquery remove
and was able to resolve it (stupidity on my part really), however my example was very simple. Currently I'm trying to use .remove to eliminate a complete div from a page before sending an array of inputs to an ajax function. However, I am not able to get .remove to work at all.
Here's my latest try:
http://jsfiddle.net/CJ2r9/2/
I get function not defined on the jsfiddle on multiple browsers. On my application I get absolutely no errors, but nothing works either.
I'm relatively new to javascript scopes, so if the problem is scope-wise then please let me know how I'm screwing up.
I have also tried using the .on jquery function, but it's a bit more confusing considering my div ids are dynamically loaded from the server (jstl, spring MVC, etc). If that's a solution please let me know how I can get on the right track.
Thank you!
The two problems in your jsFiddle are:
Scope: removeElem is not in global scope, since you left the default configuration option to execute the code on DOM ready. You can change it to "no wrap" to make the funciton global.
The elements you want to remove don't exist. The div elements have IDs like "removeXXXp" and in your event handlers you pass "removeXXXs".
Here is an other, simpler solution (in my opinion) for element removal. Given your markup:
<div class="scheduleSet" id="remove315p">
<!-- ... -->
Remove
</div>
You can use .on like so:
$('.schduleSet a.optionHide').on('click', function() {
// traverses up the DOM tree and finds the enclosing .schduleSet element
$(this).closest('.scheduleSet').remove();
});
You don't even need IDs at all.
I made a simple fiddle, the inline onclick doesn't see the function defined in javascript so I get a ReferenceError: myRemove is not defined.
By adding the listener in js, .remove() works fine.
Sorry I don't know what causes the difference in behavior though.
Test it out: http://jsfiddle.net/xTv5M/1/
// HTML5
<div id="removeme">foo bar</div>
<button onclick="myRemove('removeme')">Go</button><br>
<div id="removeMe2">foo bar</div>
<button id="go2">Go Again</button>
// js
function myRemove(name){
$('#'+name).remove()
};
$('#go2').click(function(){ myRemove('removeMe2') });
I see that you are already using jquery. Why dont you do it this way:
<div id="foo">This needs to be removed</div>
Remove
function removeElem(element){
$('#'+element).remove();
}
$(function(){
$("#remove").click(function(){
removeElem($(this).data('remove'));
});
})
Fiddle here: http://jsfiddle.net/vLgpk/
They way this works is, using data-remove (can be anything like data-xyz btw), binds the remove link with the div. You can then read this binding later when remove is clicked.
If you are new to jQuery, and wondering what data-remove is, its just custom attribute that you can add to you code which can be later retrieved using the data() call on the element. Many great frameworks like Bootstrap use this approach.
Advantage of using this approach in my opinion is you can have the remove links anywhere in your UI and they don't need to be related structurally to your divs by siting inside them.

Handle stuff after dom changes

I've got a page with some Javascript / jQuery stuff, for example:
(function()
{
$('.tip').tooltip();
$('.test').click(function()
{
alert('Clicked!')
});
}();
On the page I insert some HTML with jQuery so the DOM changes. For example, I insert a extra element with the class "tip" or "test". The just new inserted elements doesn't work because jQuery is working with the non-manipulated DOM and the just inserted elements aren't there. So I've searched around and came to this solution for the "click":
$('body').on('click','.click',function()
{
alert('Clicked!')
});
I don't understand why, but this way it's working with the manipulated DOM and the jQuery stuff works on the new inserted elements. So my first question is, why does this work and just the click() function not? And the second question, why do I have to point to the "body"?
Finally, my third question is, how get this done with the tooltip?
I know that there is so many information about this subject (previous the delegate() and live() function I've read) but I can't found a explanation about it. And I can't get my third question solved with the information I found.
I'm looking forward to your responses!
Extra question:
4) Is it recommended to point always to the "body" for this kind of situations? It's always there but for possible performance issues?
So my first question is, why does this work and just the click()
function not?
Because the event handler is now delegated to a parent element, so it remains even after replacing/manipulating child elements.
Ancient article on event delegation for your perusal - but the concepts remain the same:
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
And the second question, why do I have to point to the "body"
You don't, any suitable parent element will do. For example, any direct parent (a div wrapper, for instance) which does not get replaced.
Finally, my third question is, how get this done with the tooltip?
You need to re-initialize your tooltip plugin on the newly inserted elements. For example:
$.get("foo.html", function (html) {
$("#someDiv").html(html);
$("#someDiv").find(".tip").tooltip();
});
The click() event doesn't work when you manipulate the DOM because JQuery is not watching for DOM changes. When you bind the click() event it is selecting the elements that are on the page at that time. New ones are not in the list unless you explicitly bind the event.
Because you have pointed the click() event on the body. JQuery then checks to see if the target of the click matches any of the event handlers (like what you have created) match the element clicked. This way any new elements will get the event 'associated' with them.
Because the tooltip isn't an event that you can place on the body, you will need to re-initialize it when the element is created.
EDIT:
For your fourth question, is it depends. The advantage of binding to the body is that you don't accidentally bind an event to an element more than once. The disadvantage is that you are adding event handlers that need to be checked on each event and this can lead to performance issues.
As for your concerns about DRY, put the initialization of the tooltips into a function and call that when you add them. Trying to avoid having the same function call is a little overkill in this regard, IMO.
Events are bound to the specific object you are binding it to.
So something like $('.tip').tooltip() will perform the tooltip() functionality on $('.tip') which is actually just a collection of objects that satisfies the css selector .tip. The thing you should take note of is, that collection is not dynamic, it is basically a "database" query of the current page, and returns a resultset of HTML DOM objects wrapped by jQuery.
Therefore calling tooptip() on that collection will only perform the tooltip functionality on the objects within that collection, anything that was not in that collection when tooltip is called will not have the tooltip functionality. So adding an element that satisfies the .tip selector, after the tooltip() call, will not give it the tooltip functionality.
Now, $('body').on('click','.click', func) is actually binding the click event to the body tag (which should always exist :P), but what happens is it captures whether the click event has passed through an element your target css selector (.click in this case), so since the check is done dynamically, new elements will be captured.
This is a relatively short summary of what's going on... I hope it helped
UPDATE:
Best way for your tooltip thing is to bind tooltip after you have added elements, e.g.
$('#container').load('www.example.com/stuff', function() {
$('.tip', $(this)).tooltip();
});

onclick event not firing javascript

EDIT #2:
Made a JS Fiddle... http://jsfiddle.net/N2p6G/ (I hardcoded some stuff that I'm certain works correctly, but the problem is still there)
Original:
So, I have written tens of thousands of lines of javascript, and used code that look like this a hundreds of times and I don't understand what's going on.
blacklistitembutton.onclick = function() {
console.log("clicked.");
}
The above code does not seem to be working... and I can't figure out why
In fact, I use the same method earlier in the same file... and it works fine!
settings.onclick = function() {
settings_popup.toggle();
}
EDIT:
Might it have something to do with the fact that it's being executed in a for loop?
Here is the code...
var blacklistButton = document.createElement('input');
blacklistButton.type = 'button';
blacklistButton.value = "Add Current Site to Blacklist";
blacklistButton.onclick = function() {
console.log('blacklistButton clicked');
}
for (var i=0;i<blacklist.length;i++) {
var blacklistitembutton = document.createElement('div');
blacklistitembutton.type = 'button';
blacklistitembutton.blacklistValue = blacklist[i];
blacklistitembutton.value = "X";
blacklistitembutton.onclick = function() {
console.log("clicked.");
}
}
Then both blacklistButton and all of the blacklistitembuttons are put into the document through element.appendChild (and they all show up successfully!)
The blacklistButton onclick fires just fine, and the blacklistitembutton onclick does not.
document.addEventListener('click', function(){
console.log('clicked');
}, false);
Edit:
Here is a re-write of your code in a fiddle: http://jsfiddle.net/N2p6G/1/
There are a lot of things in your code that worry me. Hopefully from my re-write you can see there are better ways to handle some things.
1) I'm not sure why you are using document.write() at the beginning. That has very little purpose.
2) You are modifying the DOM way too much. Some of the DOM elements you are creating in code are better-served as just being target locations in html. Only the dynamically-created input button elements need to be done in javascript. Remember, modifying the DOM should be done as little as possible.
3) Don't assign events using the onclick, onsubmit, onhover, etc syntax. Events should only be bound to DOM elements using addEventListener. The other benefit of doing it the proper way is that you can assign multiple events of the same type, if need be, to the same element. Also, with some extra state code that I haven't included, you can selectively remove particular events later if you need to.
4) There was a debate several years ago about whether using innerHTML and string templates was faster/better than using DOM creation methods. For a while, the best solution was to use documentFragments and a combination of the two methods. These days, it doesn't really matter anymore since all browsers are pretty damn fast, so for simplicity's sake is good to just go with innerHTML.
This also goes back to the rule of "don't touch the DOM too much". If you look at my code, you can see that I'm assembling the final html simply as an array of elements that gets joined as a single string at the end. Its then rendered to the DOM with a single innerHTML statement. I'm only touching the DOM one time, instead of multiple times.
5) The last bit goes into events again. At the beginning and end of the code you can see where and how I've added the events for the DOM elements. Indeed, the addEventListener at the beginning could be moved to the end to group all the event declarations together, but it doesn't really matter. I left it at the top to help you understand what's going on better.
Hope this helps.
For unlimited event bindings, either use addEventListener or attachEvent method. You cannot add more than one event of the same type using that traditional method.
I don't know if it's a typo in what you put here, but in the loop you are creating a "div" and then assigning it a type of "button". Does that work or is it throwing an error? If it is then that explains why the event handler is not getting the function. Try making it an "input" and see if it now works.
Fixed it!
blacklistitem.innerHTML += blacklist[i];
^ was messing it up, at this point in the code blacklistitem is still a javascript item, not yet appended to its to-be parent element in the document
So I just stuck blacklist[i] into a span tag and appended as a child and now it works fine :)

Categories