4

In Django, how can you handle the fact that you need to wait for that a JS file is loaded before actually using it?

let's see the problem with this example:

base.html

<!DOCTYPE html>
<html>

<head>...</head>
<body>
    {% include "content.html" %}

    <script src="jquery.js"></script>
    <script src="awesome-script.js"></script>
</body>
</html>

content.html

<script>
    $(document).ready(function(){
        ...
    });
</script>

This logically fail ($ is undefined). I could load jQuery before calling the script, but I'm trying to avoid loading JS file before my main content to keep the website loading as fast as possible.

So, what can I do? Thanks.

David Dahan
  • 10,576
  • 11
  • 64
  • 137
  • Ii really do not understand your code as you have posted it. How is `content.html` related with `base.html`? If it provides `{% block main-content %}` then clearly the script is called before jQuery is loaded. – Wtower Jul 03 '15 at 09:51
  • Yes of course it is called before (I updated the code with a more obvious case of this) The whole point is we need to load the content before calling the JS (to not slower the page) but we have to wait all JS are loaded before calling the script in content.html. – David Dahan Jul 03 '15 at 09:54
  • Or maybe it's ok if jquery is the ONLY script loaded before content? – David Dahan Jul 03 '15 at 09:55
  • You cannot clearly expect a jQuery script to be executed before jQuery is loaded. You have to change your script order. Updated answer. – Wtower Jul 03 '15 at 09:56
  • Of course if I load jquery, then my content, then my script, this works well. But the question is: is that acceptable to load a BIG Javascript file BEFORE loading my content? I guess it's not a good practice, and I hoped there would be a better solution, I suppose I was wrong :) – David Dahan Jul 03 '15 at 10:00
  • I have edited the answer below to provide more information regarding your latest comment. It certainly is a good practice to load scripts as late as possiple, but you need to load jquery first before other scripts. – Wtower Jul 03 '15 at 13:05

2 Answers2

12

Extending Wtower's suggestion - keep his accepted.

I would really insist on using the template inheritance based approach in his examples. I would like to introduce a few more elements to that approach, to cover some other common needs :

<!DOCTYPE html>
<html>
<head>{% block scripts-head %}{% endblock %}</head>
<body>
    {% block content %}{% endblock %}
    {% block scripts %}
        <script src="jquery.js"></script>
    {% endblock %}
    <script>{% block script-inline %}{% endblock %}</script>
</body>
</html>

There are 3 ideas here:

  • Adding a placeholder in the header, in case you could need scripts there at some point. Self explanatory.
  • Including common scripts in the base file. If they are common, the belong in the base file, you should not have to repeat yourself in every template. Yet you put it inside the block, so it can be overriden along the hierarchy.

    {% extends "base.html" %}
    {% block scripts %}
        {{ block.super }}
        <script src="a-local-lib.js"></script>
    {% endblock %}
    

    The key is in using {{ block.super }} to bring any script that was defined in the parent template. It works especially well when you have several levels of inheritance in your templates. You get to control whether script go before or after inherited scripts. And of course, you can completely override the block, not including {{ block.super }} if you so wish.

  • Basically the same idea, but with raw javascript. You use it the same way: every template that needs to include some inline javascript will have its {{ block script-inline }}, and will start with {{ block.super }} so whatever the parent put in there is still included.

    For instance, I use Ember in my project, and have a couple of initializers to setup project settings and load bootstrap data. My base app-loading templates has a global project settings initializer, and child templates define local settings and data.

spectras
  • 13,105
  • 2
  • 31
  • 53
5

Since your script uses jQuery, you can simply use the $(document).ready() and $(window).load() functions of jQuery to bind a function on the event that DOM is ready and all window contents have been loaded, respectively.

If you do not use jQuery, take a look at these relative questions to understand how to imitate the above behaviour with pure JS:

EDIT 1: The inclusion order matters. You have to include the jQuery scripts before any scripts that require jQuery are executed.

EDIT 2: You can organize your templates better by keeping the scripts separately from the main content, either with a second template:

base.html

<!DOCTYPE html>
<html>

<head>...</head>
<body>
    {% include "content.html" %}
    {% include "js.html" %}
</body>
</html>

js.html

<script src="jquery.js"></script>
<script src="awesome-script.js"></script>
<script>
    $(document).ready(function(){
        ...
    });
</script>

(in this case you render base.html)

Or with blocks (recommended):

base.html

<!DOCTYPE html>
<html>

<head>...</head>
<body>
    {% block content %}{% endblock %}
    {% block scripts %}{% endblock %}
</body>
</html>

content.html

{% extends 'base.html' %}
{% block content %}
    ...
{% endblock %}
{% block scripts %}
    <script src="jquery.js"></script>
    <script src="awesome-script.js"></script>
    <script>
        $(document).ready(function(){
            ...
        });
    </script>
{% endblock %}    

(in this case you render content.html)

Community
  • 1
  • 1
Wtower
  • 18,848
  • 11
  • 103
  • 80
  • Thanks. I do use jQuery but it's not loaded yet so '$' is not defined when calling `$(document)` – David Dahan Jul 03 '15 at 09:35
  • Welcome, you simply have to include the jquery script before your script. – Wtower Jul 03 '15 at 09:41
  • It's already the case. In base.html, Jquery is loaded, then my script is loaded. But when entering into my main content, no JS is loaded at all (which is normal) – David Dahan Jul 03 '15 at 09:43
  • What do you mean "entering main content"? Post the lines where you include jquery please. – Wtower Jul 03 '15 at 09:45