Why should $.click() be enclosed within $(document).ready()? - javascript

I am going through http://docs.jquery.com/How_jQuery_Works and find the requirement of enclosing $.click() event in $(document).ready() even a little strange. My difficulties are as follows:
When the document loads, the control will enter $(document).ready() function but will it proceed to execute $.click()? (Based on the behavior it wouldn't. But when the control enters a normal function, why wouldn't it enter $.click() function?)
Since the user can see the url only after the document is ready, is it really required to embed $.click() within $(document).ready()?

The How jQuery Works document uses the example of binding a .click() inside of $(document).ready() to be certain that the element to which the .click() event is bound has been created when the function executes.
The .click() called with a function as its parameter does not execute the .click() on the nodes matching its preceding selector, but rather binds the function to the matching nodes' onclick.
If you were to attempt to do something like this:
$('a').click(function(){
alert('clicked me');
});
...in the document <head> or before any <a> element had been rendered, the event would not get bound to any node since no node matching the $('a') selector existed at the time the function executed.
Furthermore, if you did it when some <a> tags had been created, only those already created would get the bound event. <a> tags created after the function was bound wouldn't have the .click() binding.
So in jQuery (and other JavaScript frameworks), you'll often see the convention of adding event handlers, binding widgets, etc, inside a $(document).ready(function(){});

It's not the actual call to .click() which is the concern here, but rather the selector for the element on which it's called.
For example, if there is an element with id="myButton" then you would reference it with:
$('#myButton')
However, if that element isn't yet loaded (that is, if the document isn't yet ready and you don't know what elements are currently loaded), then that selector won't find anything. So it won't call .click() on anything (either to bind the event or to fire it, depending on the argument(s) to .click()).
You can use other approaches, such as the jQuery .on() function, which can bind events to common parent elements and filter "bubbled up" events from ones added later later in the life of the document. But if you're just using a normal selector and calling a function then the selector needs to find something within the DOM in order for the function to do anything.

Yes, you need to make sure that the DOM element to which you are adding the click handler exists, any you know this by the document being ready.

The problem the $(document).ready(..) method is trying to solve is the tension between the execution of the javascript code for the page and the time at which controls in the page are bound. The ready function will fire once the document is ready and DOM elements are available hence the javascript code can make reasonable assumptions about the DOM
The most common example is the location of javascript code with respect to the DOM elements it's trying to operate on. Consider
<script>
$('#target').click(function() {
alert('It was clicked');
});
</script>
<div id="target">Click Me</div>
In this particular example the javascript will not function as intended. When the script block is entered the click line is run and there currently is no DOM element with the id target hence the handler binds to nothing. There is nothing really wrong with the code, it just had the unfortunate timing to run before the DOM element was created.
In this example the problem is obvious because the code and DOM element are visually paired together. In more complex web pages though the javascript is often in a completely separate file and has no visual guarantees about load ordering (nor should it). Using the $(document).ready(..) abstraction removes the question of ordering as a potential source of problems and facilitates proper separation of concerns

Javascript usually starts executing as soon as the tag is read. This means that in the standard practice of putting scripts in the head, there are NO elements created yet for you to bind a click handler to. Even the body element doesn't exist yet. Using jQuery.ready delays executing your code until all DOM elements have been loaded.

Related

Putting javascript inside HTML

I have two questions on placing JS inside HTML. I can't find where these two points were answered here.
Questions are basically
Let's say I put Javascript in the head, and say the script tries to get some HTML element by ID, and register a click handler for it. Now since, the HTML has not been loaded yet (because the script is called in head), will this work?
On the other hand, if register a function in head which gets called when DOM is loaded, and inside that function I put code which registers handler for click on some button, then as I understand user has chance to click button but the handler will not be called because the whole DOM was not loaded yet (and that is where the button click handler is registered). Am I correct?
What is the solution and best way to put Javascript in HTML considering above two points?
Let's say I put Javascript in the head, and say the script tries to
get some HTML element by ID, and register a click handler for it. Now
since, the HTML has not been loaded yet (because the script is called
in head), will this work?
No, this won't work. Whenever <script> tag is seen, javascript is executed right away before going further with DOM building. (if javascript is external, it will wait to fetch it over network and execute it) It will act upon the current snapshot of DOM if the javascript is using DOM APIs like getElementById. When script tries to get the element by id, it tries to get it from DOM, (All DOM APIs acts on DOM) which is being built right now. As the element is not yet added to DOM (we are processing head) it won't be able to get the element. As it cannot get the element it will throw error if you try to access addEventListener or onclick of null.
On the other hand, if register a function in head which gets called
when DOM is loaded, and inside that function I put code which
registers handler for click on some button, then as I understand user
has chance to click button but the handler will not be called because
the whole DOM was not loaded yet (and that is where the button click
handler is registered). Am I correct?
Yes, you are correct. DOM load generalises the condition that all elements are added to the DOM and is ready for any operation on any element defined by HTML.
Also, window.load will be triggered only after DOM is loaded and all the external resources like images, videos are also loaded. Using it can delay the event attachments further.
What if I want to add event listener immediately?
If you want to immediately bind an event you can do it with inline scripts, just after the element although usually not a requirement and neither a good practice.
<p>whatever</p>
<button id="mybutton">Click me</button>
<script>
document.getElementById('mybutton').addEventListener(...
</script>
<p>rest of HTML</p>
This also opens another possibility, if you put your scripts at the end of HTML instead of head, it makes sure that all elements are added to the DOM and DOM is practically ready for any operations using DOM APIs. This was used widely when listening to DOM load event was not that easy. Although I can't find the correct reference, this was a recommendation by Doug Crockford.

Why can $(document).on(..) be placed anywhere on a page while $(".cl").on(..) has to be placed in the $(document).ready callback or after the <body>?

Title question says it all. For the most part, I understand the latter case. When the Javascript is read, the references to the DOM have to mean something, which is why we wrap it in the $(document).ready callback, or shaft our scripts under the <body>. I really don't understand why $(document).on(...) seems to work regardless of position.
Any input would be dope.
When you do $(anything), the anything objects MUST exist at the time you run that selector.
When you're in the <head> section, the document object already exists as it's kind of the master parent for the whole page. But, nothing in the body exists yet so no $(“.class”) would exist yet and thus the selector would not find anything (or worse yet fail because the body doesn't even exist yet).
But $(document) does exist at the earliest time that you could possibly run scripts (in the <head> section so $(document) works and has something to attach the event handlers to.
If you are looking for elements in the <body> such as your example $(".class"), then you either have to wait for the <body> section to load with something like jQuery's .ready() (so those elements will exist before the script runs) or you have to physically place your script AFTER that element in the <body> so that the element you want has already been parsed by the time you run your script.
To add to this a little more, if you're using delegated event handling with .on() like this:
$(mySelector).on("click", ".myClass", fn);
Then the objects in $(mySelector) are what the event handlers will be directly attached to and those are what must exist at the time you run this line of code. The objects that match ".myClass" do not have to exist at the moment you run this code. So, if you do delegated event handling by attaching to the document object with:
$(document).on("click", ".myClass", fn);
Then, only the document object has to exist at the time you run this line of code. Since the document object is created first, it will already exist when you can run javascript code, thus it always seems to work.
This raises an interesting question about whether you can just attach all your event handlers to the document object. There are some tradeoffs if you do that (and some events only work properly if attached directly to the source object) so one shouldn't just blindly do that without understanding the consequences. See this answer for a more detailed discussion about tradeoffs in attaching all events to the document object.

jQuery event binding with dynamically loaded elements

I know of and research multiple ways to bind events .on(), .live(), .click(), etc. I know .live() is deprecated in 1.9+. The problem I'm encountering is binding dynamic DOM elements to events.
I have a div that contains links and is paginated. The links are dynamically loaded with AJAX. I want to overwrite binded events for these links so I used .unbind() and tried .on(). The problem is that these dynamically loaded links are not binded. I guess that's because the selector $('#id_of_links') is cached with the previous set of links.
QUESTION:
Is it possible to bind all elements that are loaded on the page, at any point in time, without having to set a callback when the user clicks next page on the links?
for dynamically added elements, you must bind the function to one of it's parents
$('#PARENT').on('click', '#DYNAMICALLY_ADDED_CHILD', function(){ CODE HERE });
Parent should already exist in the DOM tree...
More info on: http://api.jquery.com/on/
You cannot bind all elements, even those not loaded in the page yet, without having a callback method/function or a function that would loop in and keep checking if the elements with a specific attribute or characteristic would have the proper function binded to it, which would probably cause a memory leak over time.

Newly appended objects don't respond to jQuery events from page load

I am using Backbone here, but I've also experienced this issue with non-Backbone sites as well.
Essentially, my issue is that I define in one javascript file, base.js, a number of 'global' jquery functions. Then, I load new elements and append them to the page using Backbone, AJAX, or whichever asynchronous call you like. This works great, except the new objects don't seem to be linked to the jQuery events I declared on pageload. (Sorry for the layman language here - often I am a newbie at proper wording)
For example, let's say I declare in a js file loaded on pageload:
$('.element').hover(function(){alert('hi world')});
But then I append a new element after pageload, then this hover won't work.
Can anyone explain:
Why this is?
Whether I can force a new appended element to work with/listen to current events already declared?
I suspect (2) may not be possible (that I have to rewrite the events after appending), but am interested to know if there is some sort of solution.
Why this is?
Because when the event binding code is executed, elements that do not exist in the DOM already will not be affected (how could they be? They don't exist yet!) The "normal" event binding methods (e.g. .click() or .change()) will attach a copy of the event handler function to every element in the matched set.
I can force a new appended element to work with/listen to current events already declared?
You can indeed, by delegating the event handler higher up the DOM tree. Since most DOM events bubble up the tree from the element on which they originate, this works well. With jQuery, you can use the .on() (jQuery 1.7+ - for older versions use .delegate() instead) method to do this:
$("#someCommonAncestor").on("someEvent", ".element", function () {
// Do stuff
});
The element on which you call .on() must exist in the DOM at the time the code runs. When the event bubbles up far enough to reach whatever #someCommonAncestor may be, jQuery will check to see if the target element matched the selector you passed to .on(). If it does, it will execute the event handler. If it doesn't, the event will continue bubbling to the root of the document and not trigger any handler.

JavaScript handling events after the page loads

When I'm writing some JavaScript I have a set of interface buttons that have their events assigned when the page is loaded. My problem is anything created dynamically wont receive these events.
For example, I'm making a URL checker, whose job is to make sure that any link that goes to another domain brings up an exit interface letting the user know they are leaving. Any links created after the page is loaded, post ajax (no pun intended) or whatever wont have that event naturally as those that existed when the page loaded.
In practice, what's the best way to ensure any newly created items get these sorts of global events?
I like to use jQuery, but this is really a conceptual question.
Should I create a function to re-apply any global link effects, or is there a smarter way besides doing it piecemeal?
If using jQuery, you can use the .live() method.
Normally when binding an event handler, the event handler is bound to a specific set of elements. Elements added in the future do not receive the event handler unless it is re-bound.
jQuery's .live() method works around this by binding its own special event handler to the root of the DOM tree (relying on event bubbling). When you click on an element, if it has no event handler directly attached, the event bubbles up the DOM tree. jQuery's special event handler catches the event, looks at its target and executes any user-specified event handlers that were assigned to the target through .live().
Look into jQuery's live function. It will allow you to attach to events when control are created during load, and whenever new ones are created. There is a performance penalty, but it is not significant unless you are loading a lot of elements.
You can use the .live() jQuery method to add listeners to elements that are created after the page is finished loading. Using your example of the exit link (if I understand it correctly):
$(function(){
$('a.exitLink').live('click',function(event){ /* do stuff when a link of class exitLink is clicked */);
});
This will respond to the click event on any link of class exitLink, regardless of when it was created (before or after onload fires).
Hope this helps :)
Yes put simply, where you might have had this before:
$('selector').click(function () {});
Replace it with:
$('selector').live('click', function() {});

Categories