Updated
TL;DR
Using $(document).ready
is equivalent to placing the JavaScript at the end of the document…
JSBin (which is were the OP posted his sample code) will execute the JavaScript after all the HTML elements render, but before the $(document).ready
event. Binding the jQuery.animate
in $(document).ready
is the same as firing it anytime after the view is appended to the DOM.
The simple and stable solution is to simply invoke
$("#foo").animate({width: '200px'});
on the last line of the OP's code. (Read the end of the Answer to see a more formal way of binding the jQuery.animate
function)
To answer the OP's original quesiton: setTimeout()
works in your case because of the way async functions are queue in the JavaScript runtime. If the <body>
has already been loaded, then using setTimeout(0)
the way the OP does, will have the same effect as placing the animation binding in $(document).ready
.
Why setTimeout(0) works
The first thing to understand is that JavaScript is not a multi-threaded framework. While you certainly can invoke non-synchronous functions, asynchronous, async functions don't actually run parallel to the synchronous operations. Instead, async functions are queued to run as soon as the runtime is free.
For example, take the following three synchronous functions.
function1();
function2();
function3();
As you'd expect function1will fire first, followed, in order, by
function1,
function2then
function3. However, if I place
function1` in an asynchronous call
setTimeout(function1,0);
function2();
function3();
then function1
will be placed on a queue, leaving function2
and then function3
to fire. As soon as the event loop is finished function1
is invoked. That is, it fires last! You can see this in action in this fiddle.
In the OP's example, setTimeout(function() { $("#foo").animate({height: '100px'});}, 0);
was fired immediately after the runtime executed $(document.body).append(combinedView);
and so jQuery was able to find the #foo
element, so technically this is a correct way to do what the OP wants. This is true because of the way JSBin works. That is, it loads the JavaScript from its JavaScript Module after the DOM has loaded (but before the $(document).ready
event).
Edited:
Do not use the $(document).ready
function...in general
I think there's some confusion regarding how $(document).ready
function solves the OP's problem. Most of the confusion probably stems from the complexity of how different web page elements (HTML, CSS, JavaScript) affect the rendering of the DOM.
There is a main parsing and rendering thread used by browsers. This is where your HTML is processed, your CSS stylesheets are fetched and parsed, and your JavaScript is fetched/parsed. All of these operations are executed as they are encountered and will be blocking (unless async
or defer
is specified in your <link>
/<script>
tags).
The order in which you pace all of these tags is essential. If your script tag is written at the top of your document (say within the <head>
tag) it will be executed before any HTML is injected into the DOM.
In essence, using $(document).ready
is equivalent to placing the JavaScript at the end of the document… Since JSBin (which is were the OP posted his sample code) will execute the JavaScript after all the HTML elements render, but before the $(document).ready
function, binding the jQuery.animate
in $(document).ready
is the same as firing it anytime after the view is appended to the DOM.
Instead, the simple and stable solution is to simply invoke
$("#foo").animate({width: '200px'});
after both the fooView
and the barView
have been attached to the DOM. Or more formally:
var BarView = Backbone.View.extend({
render: function() {
// process your html here
return this;
}
bindTransitions: function {
$("#foo").animate({width: '200px', height: '100px'});
}
});
var fooView = new FooView();
var barView = new BarView();
var combinedView = $(fooView.render().el).append(barView.render().el);
$(document.body).append(combinedView);
barView.bindTransitions();