JQuery loading after my script inside Twig template - javascript

I have some Javascript code inside a Twig template that uses jQuery. The script seems to be loading before jQuery, so it throws a $ is not defined error. I can't figure out why it's loading before the main bundle that includes jQuery (compiled with webpack-encore).
JQuery does load because I can reference it from the console or wrap the script inside a setTimeout to force it to be loaded later.
I have this base template:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
{% block title %}Welcome!
{% endblock %}
</title>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
The page template:
{% extends 'base.html.twig' %}
{% block body %}
/// ...
<script>
$.change(/*...*/); // $ is not defined
</script>
{% endblock %}
webpack.config.js:
const path = require('path');
const Encore = require('#symfony/webpack-encore');
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
.setOutputPath('public/build/')
.setPublicPath('/build')
.enableSassLoader()
.addEntry('app', './assets/app.js')
.enableStimulusBridge('./assets/controllers.json')
.splitEntryChunks()
.enableSingleRuntimeChunk()
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
.configureBabel((config) => {
config.plugins.push('#babel/plugin-proposal-class-properties');
})
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
.autoProvidejQuery()
;
module.exports = Encore.getWebpackConfig();
app.js:
require('./styles/app.scss');
import $ from 'jquery';
global.$ = global.jQuery = $;
This was the generated HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/build/app.css">
<script src="/build/runtime.js" defer></script>
<script src="/build/app.js" defer></script>
</head>
<body>
<main class="col p-4 content flex-grow-1">
<!-- page content -->
<script>
// $ is not defined here
</script>
</main>
</body>
</html>

NOTE: You must note that you can’t use require and import at the same
time in your node program and it is more preferred to use require
instead of import as you are required to use the experimental module
flag feature to run import program.
--https://www.geeksforgeeks.org/difference-between-node-js-require-and-es6-import-and-export/
The article above also mentions that with require "you can directly run the code" in this case the code is defining the $ function, so you want to run that.
My projects are as follows with no problems:
app.js:
require('./styles/app.scss');
const $ = require('jquery');
window.$ = $;
Comment the following line in webpack.config.js:
//.autoProvidejQuery()

The problem was found in these lines from the generated HTML:
<script src="/build/runtime.js" defer></script>
<script src="/build/app.js" defer></script>
The defer attribute forces the Javascript files where jQuery is defined, to be loaded after the page is rendered. Since I'm adding inline Javascript in the page, it's loaded before app.js.
Since there is no way to add deferred inline Javascript in HTML, there are two alternatives:
Set the following configuration in config/packages/webpack_encore.yaml:
script_attributes:
defer: false
This disables the behavior described. This solution is not ideal because using defer is desirable in my case.
Define Javascript code outside of the template (import it with encore_entry_script_tags).
Save your Javascript file in assets/ and, if you have a similar base template as the one in the question, import it in your page template like this:
{% block javascripts %}
{{ parent() }}
{{ encore_entry_script_tags('your_file') }}
{% endblock %}
In webpack.config.js:
Encore
//...
.addEntry('your_file','./assets/your_file.js');

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
});

Javascript File not working in symfony twig

In my symfony project my css is well linked, but my javascript is not working and I can't find why. I guess something stupid I missed ...! I tried with the encore webpack at firt since I'm runninh Sass, but I gave up and switched to the classi src=""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{# Run `composer require symfony/webpack-encore-bundle`
and uncomment the following Encore helpers to start using Symfony UX #}
{% block stylesheets %}
{# {{ encore_entry_link_tags('app') }}#}
{# {{ encore_entry_script_tags('app') }}#}
<link rel="stylesheet" href="{{ asset('bundles/mercuryseriesflashy/css/flashy.css') }}">
<link rel="stylesheet" href="{{ asset('./build/app.css') }}">
{% endblock %}
{% block javascripts %}
{# {{ encore_entry_script_tags('app') }}#}
<!-- Flashy depends on jQuery -->
{% endblock %}
</head>
<body>
{% block navbar %} {% include 'inc/navbar.html.twig'%}{% endblock %}
{% block body %}{% endblock %}
{% block footer %}{% endblock %}
<script src="{{ asset('./build/app.js') }}"></script>
<script src="//code.jquery.com/jquery.js"></script>
</body>
</html>
/*
* Welcome to your app's main JavaScript file!
*
* We recommend including the built version of this JavaScript file
* (and its CSS file) in your base layout (base.html.twig).
*/
// any CSS you import will output into a single css file (app.scss in this case)
import './styles/app.scss';
import './bootstrap';
// start the Stimulus application
const logoMenu = document.querySelector('.logoMenuImg');
const contItems = document.querySelector('.contItems');
const arrItems = document.querySelectorAll('.items');
console.log(logoMenu);
console.log(contItems);
console.log(arrItems);
console.log('arrItems');
alert('hello world');
Thank you for your help
p.s: in the inspector it's blank no erros
If you are using encore_entry_script_tags i assume you have add your JS file in your webpack configuration ?
I see you're using assetic, if you are in Symfony 4.4 and more, remove it, it's deprecated. Use Webpack encore.
let Encore = require('#symfony/webpack-encore');
let CopyWebpackPlugin = require('copy-webpack-plugin');
//let TerserPlugin = require('terser-webpack-plugin');
Encore
// directory where compiled assets will be stored
.setOutputPath('public/build/')
// public path used by the web server to access the output path
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
.addEntry('app', './assets/js/app.js')
.enableSingleRuntimeChunk()
;
module.exports = Encore.getWebpackConfig();
And run with npm run build or yarn encore <env>
Look at documentation: https://symfony.com/doc/current/frontend.html

Why are my javascript files not being found in assetic?

I am using Twig as my template, utilizing a layout, in which I have a javascript block
...
{% block javascripts %}
{% javascripts
'#jquery'
'#bootstrap_js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
</body>
In a content template, I have
{% block javascripts %}
{{ parent() }}
{% javascripts '#AppBundle/Resources/public/index/*' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
This is my assetic section of the config.yml
assetic:
debug: true
use_controller: false
filters:
cssrewrite: ~
assets:
bootstrap_js:
inputs:
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/js/bootstrap.min.js
bootstrap_css:
inputs:
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/css/bootstrap.css
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/css/bootstrap-theme.css
filters:
[cssrewrite]
bootstrap_glyphicons_ttf:
inputs:
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
output:
"../fonts/glyphicons-halflings-regular.ttf"
bootstrap_glyphicons_eot:
inputs:
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
output:
"../fonts/glyphicons-halflings-regular.eot"
bootstrap_glyphicons_svg:
inputs:
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
output:
"../fonts/glyphicons-halflings-regular.svg"
bootstrap_glyphicons_woff:
inputs:
- %kernel.root_dir%/../vendor/twbs/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
output:
"../fonts/glyphicons-halflings-regular.woff"
jquery:
inputs:
- %kernel.root_dir%/../vendor/components/jquery/jquery.min.js
Everything else works fine, except for the files under #AppBundle/Resources/public/index/*. I run assetic:dump for my environment, everything goes smoothly, but I keep getting a 404 HTML dump underneath the script element for #AppBundle/Resources/public/index/*
<script src="/logbook/web/js/431d650.js">
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>An Error Occurred: Not Found</title>
</head>
<body>
<h1>Oops! An Error Occurred</h1>
<h2>The server returned a "404 Not Found".</h2>
<div>
Something is broken. Please let us know what you were doing when this error occurred.
We will fix it as soon as possible. Sorry for any inconvenience caused.
</div>
</body>
</html>
</script>
Something is broken, apparently, but I cannot figure out what???
I did also have some issues using assetic in the past and I wrote down a way how I got it work. Maybe it would help you if you'd try to execute the following command first before dumping the assetic stuff:
php app/console assets:install --symlink web

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)

Placeholders in Phalcon

I'm used to ASP.NET MVC, where I can define a section in a Razor view like this:
#Html.RenderSection( "scripts" )
I usually put this at the bottom of my layout view. That way, I can add scripts from my views like this and they will be included at the bottom of the body, where the scripts section is defined:
#section scripts {
<script>
(function () {
// do stuff...
}());
</script>
}
In Phalcon, I can put this at the bottom of my layout view:
$this->assets->outputJs();
Then I can add scripts from my views like this:
$this->assets->addJs('js/whatever.js');
The only downside to this method is the script for this view has to be in a separate file, which means a separate request. I'd like to be able to add the script directly to the view like I can do with Razor and still have it rendered at the bottom of the body. Does Phalcon allow you to do this?
Yes, you can use Partials:
<?php $this->partial("partials/js/whatever") ?>
Where js/whatever is a php template file in views/partials/js/whatever.phtml.
Also you can use Volt template engine and do the same:
{{ partial('partials/js/whatever') }}
or use include:
{% include "partials/js/whatever" %}
In Volt you can use also [Blocks][3] where you can define parts of main layout (such as footer) in main template file and in each view file you can define what should be placed there.
{# templates/base.volt #}
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}{% endblock %}
</div>
</body>
</html>
And in view:
{% extends "templates/base.volt" %}
{% block content %}
<h1>My page</h1>
{% endblock %}
{% block footer %}{{ partial('partials/js/whatever') }}{% endblock %}
Well as I wrote this is for Volt template engine for Phalcon, but if you're using plain PHP then I don't know similar solution. You could create simple service that gathers links to templates in controller and then output them as partials in main template.

Categories

Resources