Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

(document).ready() & Ordering

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

When we use this javascripts block thing:

... lines 1 - 62
{% block javascripts %}
{{ parent() }}
<script>
$('.js-delete-rep-log').on('click', function() {
console.log('todo delete!');
});
</script>
{% endblock %}

It adds our new JavaScript code right after the main script tags in the base layout:

<!DOCTYPE html>
<html lang="en">
... lines 3 - 19
<body>
... lines 21 - 90
{% block javascripts %}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

View the HTML source and scroll to the bottom see that in action. Yep, jQuery and then our stuff.

Tip

In new Symfony projects, the <script> elements are added up inside <head> but with a defer attribute. This causes the JavaScript to be executed in the same order (and at the same time) as what we will see here.

Our JavaScript lives at the bottom of the page for a reason: performance. Unless you add an async attribute, when your browser sees a script tag, it stops, waits while that file is downloaded, executes it, and then continues.

But not everyone agrees that putting JS in the footer is the best thing since Chuck Norris. After all, if your page is heavily dependent on JS, your user might see a blank page for a second before your JavaScript has the chance to execute and put cool stuff there, like a photo of Chuck Norris.

So, there might be some performance differences between putting JavaScript in the header versus the footer. But, our code should work equally well in either place, right? If I move the block javascripts up into my header, this should probably still work?

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 16
{% block javascripts %}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
{% endblock %}
... lines 21 - 22
</head>
... lines 24 - 96
</html>

We still have 3 script tags, in the same order, just in a different spot.

Well... let's find out! Refresh! Then click delete. Ah we broke it! What happened?!

Running JavaScript Before the DOM

This may or may not be obvious to you, but it's worth mentioning: our browser executes JavaScript as soon as it sees it... which might be before some or all of the page has actually loaded. Our code is looking for all elements with the js-delete-rep-log class. Well, at this point, none of the HTML body has loaded yet, so it finds exactly zero elements.

This is the reason why you probably already always use the famous $(document).ready() block. Move our code inside of it, and refresh again:

... lines 1 - 62
{% block javascripts %}
... lines 64 - 65
<script>
$(document).ready(function() {
$('.js-delete-rep-log').on('click', function () {
console.log('todo delete!');
});
});
</script>
{% endblock %}

Yes!

Very simply, jQuery calls your $(document).ready() function once the DOM has fully loaded. But it's nothing fancy: it's approximately equal to putting your JavaScript code at the absolute bottom of the page. It's nice because it makes our code portable: it will work no matter where it lives.

We could even take the script tag, delete it from the block, and put it right in the middle of the page:

... lines 1 - 2
{% block body %}
<div class="row">
<div class="col-md-7">
... lines 6 - 12
<table class="table table-striped">
... lines 14 - 47
</table>
<script>
$(document).ready(function() {
$('.js-delete-rep-log').on('click', function () {
console.log('todo delete!');
});
});
</script>
{{ include('lift/_form.html.twig') }}
</div>
... lines 60 - 66
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
{% endblock %}

Now in the HTML, the external script tags are still on top, but our JavaScript lives right, smack in the middle of the page. And when we refresh, it still works super well.

Thinking out JavaScript Ordering

Of course, the only problem is if someone comes along and decides:

Hey, you know what? We should really put our JavaScript in the footer! Chuck Norris told me it's better for performance.

<!DOCTYPE html>
<html lang="en">
... lines 3 - 19
<body>
... lines 21 - 90
{% block javascripts %}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

Now, we have a different problem. In the source, jQuery once again lives at the absolute bottom. But when we refresh the page, error! Our browser immediately tells us that $ is not defined.

This comes from our code, which still lives in the middle of the page. And yea, it makes sense: as our browser loads the page, it sees the $, but has not yet downloaded jQuery: that script tag lives further down.

So there are two things we need to worry about. First, any JavaScript that I depend on needs to be included on the page before me. And actually, this will stop being true when we talk about module loaders in a future tutorial.

Second, before I try to select any elements with jQuery, I better make sure the DOM has loaded, which we can always guarantee with a $(document).ready() block.

Let's put our JavaScript back into the block so that it's always included after jQuery, whether that's in the header of footer:

... lines 1 - 61
{% block javascripts %}
{{ parent() }}
<script>
$(document).ready(function() {
$('.js-delete-rep-log').on('click', function () {
console.log('todo delete!');
});
});
</script>
{% endblock %}

Go back, refresh, and life is good again.

Next, let's talk about bubbles! I mean, event bubbling!

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.1.*", // v3.1.10
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.0
        "symfony/monolog-bundle": "^2.8", // 2.12.0
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.1
        "symfony/phpunit-bridge": "^3.0" // v3.1.6
    }
}
userVoice