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 :)
Related
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!
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.
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();
});
Wondered if there was good way to do this, thought I would post to the SO community...
There is a 3rd party web page that I have no control over how it renders, but they allow me to add JQuery.
Using the JQuery, I am creating a nav menu on the side of the page, it will be a list of links. The onclick event of these links I get from existing onclick events already on the page, but when I do a:
var linkLoc = $('#theLink').attr("onclick");
linkLoc returns:
function onclick(event) {
handleJumpTo("com.webridge.entity.Entity[OID[E471CB74A9857542804C7AC56B1F41FB]]", "smartform");
}
instead of what I would expect:
handleJumpTo("com.webridge.entity.Entity[OID[E471CB74A9857542804C7AC56B1F41FB]]", smartform");
I think JQuery is trying to get the event for binding, but I need the actual Javascript markup since I'm creating the HTML dynamically. I guess I could substring the "function onclick(event) {" out, but seems kind of hacky.
Any ideas of an elegant way I could get the onclick markup?
$("#theLink") would return a jQuery object whereas $("#theLink")[0] would give a DOM object. This is a resson that $("#thelink")[0].getAttributeNode('onclick').value would work.
The type of $('#theLink').attr("onclick") is a function, so you can just use that when you bind events to the links.
var linkLoc = $('#theLink').attr("onclick");
$('a#link1').live('click', linkLoc);
Example: http://jsfiddle.net/BdU6f/
You can also run other code in the click handler too, if you need:
var linkLoc = $('#theLink').attr("onclick");
$('a#link1').live('click', function(e){
// Code...
linkLoc(e);
});
Example: http://jsfiddle.net/BdU6f/1/
The "onfoo" attributes have values that are functions, not strings. The semantics of:
<whatever onclick='code code code'>
are that the browser constructs a function object as if you had code that did this:
document.getElementById('whatever').onclick = new Function("event", "code code code");
Thus you don't really need the raw string, since you've got something better: the function itself, ready to be called. You can then bind it as a handler to other elements via JavaScript code, not HTML (which is really a better way to do things anyway). You're using jQuery, you say, so you can use the jQuery ".bind()" API to bind those functions to whatever elements you need.
You should also be aware that there are other ways of binding event handlers to elements, ways that will leave the "onfoo" attributes completely unset.
If I understand where you're going with this, you should be able to assign the returned onclick function straight through to the onclick of your new nav element...
$('#NewNavElement').click($('#theLink').attr('onclick'));
If you need to add additional code to the handler, you can just bind another click handler.
try this;
$('#theLink').getAttributeNode('onclick').value
Revised as per comment:
$('#theLink').get().getAttributeNode('onclick').value
I just wrote a $().bind('event') function and then got concerned that this kind of a call might be very expensive if jQuery has to run through each element in the DOM to bind this event.
Or maybe, it's just about as efficient as an event could be. The jQuery docs I've read aren't making this clear. Any opinions?
There are two things that can make your event binding code slow: the selector and the # of bindings. The most critical of the two is the # of bindings, but the selector could impact your initial performance.
As far as selectors go, just make sure you don't use pure class name selectors like .myclass. If you know that the class of myclass will always be in a <div> element, make your selector be div.myclass as it will help jQuery find the matching elements faster. Also, don't take advantange of jQuery letting you give it huge selector strings. Everything it can do with string selectors it can also do through functions, and this is intentional, as it is (marginally, admittedly) faster to do it this way as jQuery doesn't have to sit around to parse your string to figure out what you want. So instead of doing $('#myform input:eq(2)'); you might do $('input','#myform').eq(2);. By specifying a context, we are also not making jQuery look anywhere it doesn't have to, which is much faster. More on this here.
As far as the amount of bindings: if you have a relatively medium-sized amount of elements then you should be fine - anything up to 200, 300 potential element matches will perform fine in modern browsers. If you have more than this you might want to instead look into Event Delegation.
What is Event Delegation? Essentially, when you run code like this:
$('div.test').click(function() {
doSomething($(this));
});
jQuery is doing something like this behind the scenes (binding an event for each matched element):
$('div.test').each(function() {
this.addEventListener('click', function() {
doSomething(this);
}, false);
});
This can get inefficient if you have a large amount of elements. With event delegation, you can cut down the amount of bindings done down to one. But how? The event object has a target property that lets you know what element the event acted on. So you could then do something like this:
$(document).click(function(e) {
var $target = $(e.target);
if($target.is('div.test')) { // the element clicked on is a DIV
// with a class of test
doSomething($target);
}
});
Thankfully you don't actually have to code the above with jQuery. The live function, which is advertised as an easy way to bind events to elements that do not yet exist, is actually able to do this by using event delegation and checking at the time an action occurs if the target matches the selector you specify to it. This has the side effect, of course, of being very handy when speed is important.
The moral of the story? If you are concerned about the amount of bindings your script has just replace .bind with .live and make sure you have smart selectors.
Do note, however, that not all events are supported by .live. If you need something not supported by it, you can check out the livequery plugin, which is live on steroids.
Basically, you're not going to do any better.
All it is doing is calling attachEventListener() on each of your selected elements.
On parse time alone, this method is probably quicker than setting inlined event handlers on each element.
Generally, I would consider this to be a very inexpensive operation.