How to defer JavaScript when using Django template variables? - javascript

I have a problem. I am doing all of this new fancy defer stuff when I load my JavaScript, as recommended by lighthouse while working on PWA.
{% block head_content %}
<script defer src={% static 'js/jquery.min.js' %}></script>
{% endblock %}
If I was just doing this in a Django tempalte, it would not be a problem, I could just move the <script> content to a .js file and defer that also:
{% block content %}
<script>
$( jquery thing
let x = 0; // Do jQuery stuff with NO json_data
);
</script>
{% endblock %}
However, I have a Django application that is doing something like:
{% block content %}
<script>
$( jquery thing
{{ json_data|safe }} // Do jQuery stuff with json_data
);
</script>
{% endblock %}
So if I try and move the script to a separate .js file I get: SyntaxError: expected property name, got {.
This very popular Q&A seems not to work if you get $ is not defined due to using defer, as noted by the top comment. Is my only option to put the script above the portion of the code that uses jquery in the body with no defer? If so, this limits the usefulness of Django's template inheritance.
{% block content %}
<script>
<script src={% static 'js/jquery.min.js' %}></script> // Add above each first-use of jQuery (along with every other relevant library)
$( jquery thing
{{ json_data|safe }} // Do jQuery stuff with json_data
);
</script>
{% endblock %}

I haven't been using Django in a while now, but I don't think you can "defer" that script tag inside your template. That said, you could try one of the following:
01: Load your json_data into a variable that you can access from an external .js file.
Something like:
var myJsonData = "{{ json_data|safe }}";.
Check this thread here for more info.
02: Another option is to create an endpoint that will return that json_data and call it from an external .js using ajax. Then you can use the data to do whatever you need.
Something like
$.ajax({
url: "my_django_endpoint",
success: function(result){
// Do jQuery stuff;
}
});
Check this thread here for more info.
We used a framework called T3JS that has a neat way of implementing Option 01 using context. (if you wanna take a look under the topic getConfig).
Hope it helps :)

Related

JavaScript only runs when both the code is inside the HTML and is called externally with <script src=...>

My JavaScript code only runs when the code is both inside the HTML file and called externally via
<script type="text/javascript" src="{{ url_for('static', filename='index.js') }}"></script>
<script>
var scroller = document.querySelector("#scroller");
...
</script>
I am using Flask and Jinja, with a file structure of:
/app
/static
index.js
/templates
base.html
myfile.html
routes.py
__init__.py
...
The code inside index.js is the exact same code between the <script> tags inside the HTML.
In terms of jinja and using block tags, base.html:
<body>
{% block content %}
<!-- typical HTML stuff here -->
{% endblock %}
<!-- some Bootstrap tags -->
<script ... ></script>
{% block script %}{% endblock %}
</body>
myfile.html:
<body>
...
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='index.js') }}"></script>
{% endblock %}
<script>
...
</script>
The code itself works, and it worked not too long ago without this issue; I don't know what I changed to cause this, nor can I even imagine what could cause this. If there is more code that is required, I can easily share it.
Is there something I not understanding?
To note: I have had a similar issue trying to including external JavaScript code inside my HTML; at one point it wouldn't work, then it did, now it behaves the way I have described.
To further note: I have another .html file with its own external .js file that works fine.
Mr.#JakeJackson, Script in externl file never requires the same content to be available inside your inline code.
May be you are trying to process some elements and your script got executed before those elements are mounted to the document object.
A lazy solution to that problem is moving the external file linking tag to the bottom your HTML.Body.
OR
You can use defer attribute to your script element https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
OR
If you have some libraries like jQuery included in your page, You can use the document.ready implementations in that
OR
you can implement your own document.ready like below
function myReady() {
return new Promise(function(resolve) {
function checkState() {
if (document.readyState !== 'loading') {
resolve();
}
}
document.addEventListener('readystatechange', checkState);
checkState();
});
};
myReady().then(function() {
// Put your app custom code here
});

Adding javascript to an extended django template for google analytics

I have a nice little index.html file, it is an extended template and its parent is a base.html file (well in my case base2.html). I was trying to add a google analytics code snippet to some files on my site and it turns out, if I add anything in the tag on my extended templates, it is like the script code is ignored. Is there something I am doing wrong? I know I can add it just fine to base2.html, but then that would track for ever single hit since that is the parent for a lot of my pages on the site.
Without seeing the base2.html and index.html, it's pretty tough to answer, but the most likely culprit is that you forgot to put the <script></script> inside of a {% block %} that exists in the parent template. Remember that for template inheritance in Django, the parent defines a "skeleton" of all the blocks, and children must override those blocks— you can use {{ block.super }} to inherit all of the parent template's content for that block, then add any additional content. Any content in the child template that is not in a block in the parent "skeleton" template that it's extending doesn't get rendered.
# base2.html
<body>
{% block content %}
{% endblock content %}
{% block footer_scripts %}
<script src="foobar"> ...</script>
{% endblock footer_scripts %}
</body>
If you were to just add this, it wouldn't work:
# index.html - DOES NOT WORK
{% extends 'base2.html' %}
<script>
// GA javascript
</script>
You need to include it in a block:
# index.html - DOES WORK
{% extends 'base2.html' %}
{% block footer_scripts %}
{{ block.super }}
<script>
// GA javascript
</script>
{% endblock %}

Django/jQuery: handling template inheritence and JS files loading

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.
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.
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:
pure JavaScript equivalent to jQuery's $.ready() how to call a function when the page/dom is ready for it
Javascript - How to detect if document has loaded
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)

Call JavaScript function from Twig

I'm working on a Symfony2 project and I want to call a JavaScript function and pass an array of JSON objects (which I get from a controller) from a Twig.
But a first, very simple test already failed, like:
main.js:
function helloWorld(name) {
console.log("hello " + name);
}
linked to main.js in the twig and called the function:
<body>
<script>helloWorld("world!")</script>
{% block javascripts %}
<script src="{{ asset('js/main.js') }}"></script>
{% endblock %}
</body>
which results in a ReferenceError:
"Uncaught ReferenceError: helloWorld is not defined"
What do I have to do differently to make this work?
EDIT: Thanks to the two who took the time to answer. The described twig actually consists of a bunch of nested twigs and the placement of the javascript include was based on the Symfony documentation, guess that's why I didn't see the obvious. Should have detected the problem myself when phrasing the question though....
Invert the order of the functions:
<body>
{% block javascripts %}
<script src="{{ asset('js/main.js') }}"></script>
{% endblock %}
<script>helloWorld("world!")</script>
</body>
The script with the definition of the helloWorld definition is put after the function is executed. This means JavaScript doesn't yet know the function and triggers this error.
Solution: Put your javascript below your imports (before </body>, in the javascripts block for instance) or put the imports before the page javascript (in the head for instance).

Uncaught ReferenceError: $ is not defined in twig template with jQuery import

I have this code in my twig view:
{% extends "MyBundleBundle::layout.html.twig" %}
{% block content %}
<div class="page-header">
<h4>add an equipement</h4>
</div
<select id="selectEquipement">
<option selected disabled>choose an equipement</option>
<option value="{{ path('addEquipement1') }}">Equipement 1</option>
<option value="{{ path('addEquipement2') }}">Equipement 2</option>
</select>
<div id="formEquipement"></div>
<script type="text/javascript">
$(document).ready(function() {
$('#selectEquipement').change(function() {
var url = $(this).val();
$.get(url , {} , function(formulaire) {
$("#formEquipement").html(formulaire);
})
})
});
</script>
{% endblock %}
When i load the page content of this view I have this error on the first line of my script $(document).ready(function() { :
Uncaught ReferenceError: $ is not defined in twig template
I understand that jQuery is not load. When I add <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> this tag on this view, all works well, I have no error.
But, in fact I already import in my application (local) the jquery script I need. So In my main layout I have imported the script like this:
{% block script %}
{% javascripts
'#MySpaceMyBundle/Resources/public/js/jquery-2.1.1.min.js'
'#MySpaceMyBundle/Resources/public/js/jquery-ui.min.js'
'#MySpaceMyBundle/Resources/public/js/bootstrap.min.js'
'#MySpaceMyBundle/Resources/public/js/applydataTables.js'
'#MySpaceMyBundle/Resources/public/js/jquery.form.min.js'
'#MySpaceMyBundle/Resources/public/js/jquery.dataTables.min.js'
'#MySpaceMyBundle/Resources/public/js/dataTables.colVis.min.js'
'#MySpaceMyBundle/Resources/public/js/jquery.sumoselect.min.js'
'#MySpaceMyBundle/Resources/public/js/main.js'
'http://cdn.datatables.net/responsive/1.0.3/js/dataTables.responsive.js'%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
Someone know why? I noticed that I have some errors like these since I upgrade my symfony application to the 2.6 version.
Moreover, when I use Ajax call in my application, in the symfony toolbar, the notification for Ajax request does not work like before (no notifications), but when I debug in the network tab of my browser all works well.
Try replacing:
$(document).ready(function() {
with
$(window).load(function() {
window.onload() fires later than document.ready() when all elements, including the dynamically generated ones (or included) have been loaded.
EDIT: Can you try moving your code after the jQuery code, in the footer?
The right way to do it would be to have a separate .js file and call it in the footer, right after jQuery.

Categories

Resources