Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Bootstrap & the Curious Case of jQuery Plugins

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.

The inline code in base.html.twig isn't working anymore because we've eliminated the $ global variable:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
... lines 92 - 95
<script>
$('.dropdown-toggle').dropdown();
$('.custom-file-input').on('change', function(event) {
var inputFile = event.currentTarget;
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name);
});
</script>
{% endblock %}
</body>
</html>

Woo! To make it work, let's move all this code into app.js:

25 lines assets/js/app.js
... lines 1 - 15
console.log(getNiceMessage(5));
$('.dropdown-toggle').dropdown();
$('.custom-file-input').on('change', function(event) {
var inputFile = event.currentTarget;
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name);
});

Instead of global variables, we're importing $ and that's why it's called $ down here:

25 lines assets/js/app.js
... lines 1 - 10
import $ from 'jquery';
... lines 12 - 17
$('.dropdown-toggle').dropdown();
$('.custom-file-input').on('change', function(event) {
var inputFile = event.currentTarget;
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name);
});

It's all just local variables.

Try it now. Ok, it sorta works. It logs... then explodes. The error has some Webpack stuff on it, but it ultimately says:

dropdown is not a function

Click the app.js link. Ah, it's having trouble with the dropdown() function. That is one of the functions that Bootstrap adds to jQuery. And... it makes sense why it's missing: we're running all of our code here, and then including Bootstrap:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
... lines 93 - 94
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

It's simply not adding the function in time! Well actually, it's a bit more than that. Even if we moved this script tag up, it still wouldn't work. Why? Because when you include Bootstrap via a script tag, it expects jQuery to be a global variable... and that - wonderfully - doesn't exist anymore.

Let's do this properly.

Installing Bootstrap

Oh, by the way, this popper.js thing is here because it's needed by Bootstrap:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>

You'll see how this works in Webpack in a moment. Delete both of the script tags:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

Then, find your terminal and run:

yarn add "bootstrap@^4" --dev

Oh, and how did I know that the package name was bootstrap? Just because I cheated and searched for it before recording. Go to https://yarnpkg.com/ and search for "Bootstrap". 9.7 million downloads... in the last 30 days... that's probably the right one.

And... it's done! Oh, and there's a little notice:

bootstrap has an unmet peer dependency popper.js

We'll come back to that in a minute.

Importing jQuery Plugins

Back in app.js installing Bootstrap isn't enough. On top, add import 'bootstrap':

26 lines assets/js/app.js
... lines 1 - 10
import $ from 'jquery';
import 'bootstrap'; // adds functions to jQuery
... lines 13 - 26

Nope, we don't need to say import $ from or anything like that. Bootstrap is a jQuery plugin and jQuery plugins are... super weird. They do not return a value. Instead, they modify jQuery and add functions to it. I'll add a note here because... it just looks strange: it's weird that adding this allows me to use the tooltip() function, for example.

How Bootstrap Finds jQuery

But wait a second. If Bootstrap modifies jQuery... internally, how does it get the jQuery object in order to do that? I mean, jQuery is no longer global: if we need it, we need to import it. Well... because Bootstrap is a well-written library, it does the exact same thing. It detects that it's in a Webpack environment and, instead of expecting there to be a global jQuery variable, it imports jquery, just like we are.

And, fun fact, when two different files import the same module, they get back the same, one instance of it - a lot like Symfony's container. We import jQuery and assign it to $. Then, a microsecond later, Bootstrap imports that same object and modifies it:

26 lines assets/js/app.js
... lines 1 - 10
import $ from 'jquery';
import 'bootstrap'; // adds functions to jQuery
... lines 13 - 26

By the time we get past line 12, the $ variable has the new tooltip() function.

Installing popper.js

But... you may have noticed that, while I was talking about how awesome this is all going to work... my build was failing!

This dependency was not found: popper.js in bootstrap.js

This is awesome! Bootstrap has two dependencies: jQuery but also another library called popper.js. Internally, it tries to import both of them. But, because this is not installed in our project, it fails. By the way, if you're wondering:

Why doesn't Bootstrap just list this as a dependency in its package.json so that it's automatically downloaded for us?

Excellent question! And that's exactly how we would do it in the PHP world. Short answer: Node dependencies are complicated, and so sometimes it will work like this, but sometimes it's a better idea for a library to force us to install its dependency manually. That's called a "peer" dependency.

Anyways, this is a great error, and it even suggests how to fix it: npm install --save popper.js. Because we're using Yarn, we'll do our version of that command. Back in your open terminal tab, run:

yarn add popper.js --dev

When that finishes... ah. Because we haven't modified any files, Webpack doesn't know it should re-build. Let's go over here and just add a space. That triggers a rebuild which is... successful!

Try it out - refresh! No errors.

Next! I have a surprise! Webpack has already started to silently optimize our build through a process called code splitting. Let's see what that means and learn how it works.

Leave a comment!

23
Login or Register to join the conversation
skocdopolet Avatar
skocdopolet Avatar skocdopolet | posted 21 days ago | edited

Hey Ryan,

Please how are the things about actual situation with using bootstrap and webpack encore? I have simillar problems with JavaScript as Tim K. and I am not sure if my solution is good and acceptable. I am using Symfony 6.1.7 with webpack encore. I have installed bootstrap version 5.2.2 (via yarn add bootstrap --dev) and @popperjs/core version 2.11.6.

I have followed the official documentation page Using Bootstrap CSS & JS

There is this advice to include bootstrap JavaScript files this way:

app.js

...
const $ = require('jquery');
// this "modifies" the jquery module: adding behavior to it
// the bootstrap module doesn't export/return anything
require('bootstrap');
...

I found a information on Bootstrap website - version 5 does not require jQuery library. So I have added only require('bootstrap');

I try to use Tooltips from Bootstrap, so my app.js file looks like this:

/*
 * 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.css in this case)
import './styles/app.scss';

// start the Stimulus application
import './bootstrap';

// Loads Bootstrap JavaScript
require('bootstrap');

// Enable Tooltips
// see: https://getbootstrap.com/docs/5.2/components/tooltips/
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));

And part of Twig template like this

<button type="button" class="btn btn-secondary"
        data-bs-toggle="tooltip" data-bs-placement="top"
        data-bs-custom-class="custom-tooltip"
        data-bs-title="This top tooltip is themed via CSS variables.">
  Custom tooltip
</button>

But Tooltips are not working and I got a warning from my webbrowser
Uncaught ReferenceError: bootstrap is not defined

So I found a solution in this discussion
My app.js file

...
// Loads Bootstrap JavaScript
import * as bootstrap from 'bootstrap';
...

So I have three questions
1) What I do wrong in steps according to official documentation (Using Bootstrap CSS & JS)
2) What diferrence is between require('bootstrap') and import * as bootstrap from 'bootstrap';
3) Is using import * as bootstrap from 'bootstrap'; correct?

Thank you!
Regards Tomas

Reply
Jesse-Rushlow Avatar
Jesse-Rushlow Avatar Jesse-Rushlow | SFCASTS | skocdopolet | posted 15 days ago

Howdy Tomas!

I feel like every time I use Bootstrap in a project, I always run into issue's getting it to work correctly. But, I created a "template" so to speak that I use in just about all of my projects now when I reach for Bootstrap.

// package.json
{
    "devDependencies": {
        "@popperjs/core": "^2.11.5",
        "bootstrap": "^5.1.3",
        ...
// app.js

// any CSS you import will output into a single css file (app.css in this case)
import './scss/global.scss';       // I use SASS so this for the most part instead of vanilla CSS

// start the Stimulus application
import './bootstrap';

import { Popover } from 'bootstrap'; // eslint-disable-line no-unused-vars
// global.scss
// Configuration
@import "bootstrap/scss/functions";

@import "variable-overrides";    // This is just another scss file I overwrite bootstrap vars in
@import "bootstrap/scss/variables";

@import "map-overrides";        // Same for any Bootstrap Map overrides
@import "bootstrap/scss/maps";

@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";

// Layout & components
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
//@import "bootstrap/scss/images";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/tables";
@import "bootstrap/scss/popover";
.......


// Helpers
@import "bootstrap/scss/helpers";

// Utilities
@import "bootstrap/scss/utilities/api";

I hope that helps, to answer your questions directly:

2) What diferrence is between require('bootstrap') and import * as bootstrap from 'bootstrap';

require('.....') was how we use to include additional JS into a file before ES6 which introduced the concept of "modules". With ES6, we can use import() other JS modules. They are sort of like using standard PHP functions vs Object Oriented PHP if that makes sense.

3) Is using import * as bootstrap from 'bootstrap'; correct?

You can do that, but I believe that will import all of the JS that is bundled with Bootstrap - which will result in larger file sizes for the end users. Personally, I prefer to only import exactly what I need from third-party libraries vs importing everything that library may provide.

Reply
Tim-K Avatar

Hey Ryan,

I installed Bootstrap v5.1 (which comes with the yarn add bootstrap --dev).
But it seems not to be imported, but something called Stimulus???

I tried this in `app.js`, but no Bootstrap Styling is applied.


// any CSS you import will output into a single css file (app.css in this case)
import './styles/app.css';

// start the Stimulus application
// import './bootstrap';

// load bootstrap (install with 'yarn add bootstrap --dev')
import 'bootstrap';

Any idea?

Reply

Hey Tim K.!

Ah yes, there's some confusing naming happening! So, a few things:

1) That './bootstrap' import is referring to a new "file that bootstraps some functionality" that Symfony provides in newer projects. So, it has nothing to do with "Bootstrap" the framework (that's the confusing naming part).

2) When you import 'bootstrap', that imports the Bootstrap *JavaScript*, not the Bootstrap CSS. To get the CSS, you'll want to do this same thing from inside a CSS file - https://symfonycasts.com/sc...

Let me know if that helps! But the Bootstrap 5 JavaScript does, I believe, work a bit differently than in Bootstrap 4. So if you import it like you are doing here, it will not automatically the functions to jQuery (e.g. $('.some-element').dropdown()) like we show in this tutorial. That doesn't change anything about how Encore works - but I wanted you to be aware. That's the reason we explicitly mention to install version 4 (you can TOTALLY install version 5 if you want - but there may be a few differences).

Cheers!

Reply
Tim-K Avatar

thanks! It's working well now.

In case you or other might be interessted: As I try to avoid JQuery in my fresh project, I adapted the code a little bit to enable e.g. the usage of ToolTips

app.js

// any CSS you import will output into a single css file (app.css in this case)
import './styles/app.css';

// start the Stimulus application
import './bootstrap'; // Remark: unfortunately called 'bootstrap', but it is not BOOTSTRAP (getbootstrap.com)


// load BOOTSTRAP's JavaScript (install with 'yarn add bootstrap --dev')
import * as bootstrap from 'bootstrap'; // Remark: "import 'bootstrap'" will not work e.g for ToolTips

// activate BOOTSTRAP-Tooltip everywhere
// see: https://getbootstrap.com/docs/5.1/components/tooltips/#example-enable-tooltips-everywhere
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})

Reply
Juan-Etxenike Avatar
Juan-Etxenike Avatar Juan-Etxenike | Tim-K | posted 11 months ago

Thank you so, so much, this answer litterally got me out of desperation.

Reply
Ruslan Avatar

Hi,
I'm trying to do the same things with bootstrap@5.1.0 and jquery@3.6.0.
For open a modal window ($('#confirmationModal').modal('show');) . I get error : .modal is not a function
I see in Bootstrap docs :
Bootstrap 5 is designed to be used without jQuery, but it’s still possible to use our components with jQuery. If Bootstrap detects jQuery in the window object it’ll add all of our components in jQuery’s plugin system;
Is it possible to help Bootstrap to find jQuery?
Thank you.

Reply

Hey Ruslan!

I haven't tried this yet, but I just checked the code and it looks like it's a *requirement* that jQuery be defined on the window variable at the time that bootstrap is still imported. So, you'll need something like this before you import bootstrap:


const $ = import('jquery');
global.jQuery = $;
// global. should work - but you can try window. if it doesn't for some reason

import { Modal } from 'bootstrap';

Just be careful not to start relying on the global jQuery variable (i.e. import it whenever you need it). If you want to play it safe, you could probably unset the jQuery variable after importing from bootstrap - e.g. "delete global.jQuery".

Cheers!

Reply
Ruslan Avatar

Hi.
Why we don't need to import "popper.js" ?

Reply

Hey Ruslan!

Excellent question! Because if you looked at the bootstrap.js source code, you would find code that looks like this:


// the core bootstrap.js module
const Popper = import('popper.js');

// ...
Popper.createPopper(...);

This is a simplified version of what you would *actually* find there, but conceptually, it's correct. The point is: bootstrap.js itself imports and uses popper.js. All WE need to do is make sure that popper.js is downloaded into our project so that the import('popper.js') line in the bootstrap code doesn't fail.

Let me know if that helps :).

Cheers!

Reply

Hello, im kind'a new to webpack encore. Having an issue with some bootstrap/jQuery. Seems like only half of it is working - like carousel , I don't get any errors in console, webpack builds successfully - but my dropdowns don't work - using bootstrap 4.4.1 and jQuery 3.4.1. When i use cdn of bs/jq - all is working. Would appreciate any help

Reply

Hey Andynec

Have you watched this chapter? https://symfonycasts.com/sc...
I think you hit that bug with jQuery plugins. If it's not the case this thread may help you out https://github.com/froala/K...
Cheers!

Reply

Hey. Did watch that chapter now - but most of it i have done already, and the jquery was also set globally. Funny part is - that this line helped me out in app.js require('bootstrap/dist/js/bootstrap.bundle'); although i have also import './bootstrap'; Anyways - thanks for the response!

Reply

Interesting! I'm not sure why importing that file fixed your problem. I'll grant it to jQuery weirdness. Anyway, I'm glad to know you could fix your problem.
Cheers!

1 Reply
Default user avatar
Default user avatar Javier Quintana | posted 1 year ago

Heads up, Bootstrap 5 was released on May this year and it removed jQuery. If you yarn add bootstrap --dev, it will install that version and you will still get that dropdown is not a function error.

To follow this tutorial along, the easiest thing to do is to use the Bootstrap version used in it, typing yarn add bootstrap@^4.3.1 in your terminal instead.

Reply

Hey Javier Quintana

Thanks for ping, Fix is on the way!

Cheers!

1 Reply
Default user avatar
Default user avatar Florent Hazard | posted 1 year ago

So, in my project, I imported jQuery, bootstrap, font awesome, the bought template SCSS, other dependencies and now it's taking 35 seconds to compile...
How to do to not compile dependencies for each SASS change ?

Reply

Hey Florent Hazard

Sometimes the building process may take a while, Webpack is getting smarter at compiling faster but it's still a heavy process. I'd recommend you to just run yarn dev (without the watcher) so the assets don't get compiler on every change, but the changes won't be applied until you manually compile again

Cheers!

Reply
Default user avatar

I've always encapsulated my JQuery code in a DocumentReady function. Isn't that necessary?

Reply

Hey @Karl!

That's an excellent question! We talk about it in this other tutorial - https://symfonycasts.com/sc...

The short answer is that I *also* used to do this. And there is nothing wrong with it at all. But depending on how your JavaScript is organized, it may be completely redundant :).

Cheers!

Reply
Ad F. Avatar

can you add a possible solution for fontawesome-svg - importing only used icons ? it will drastically improve speed of the required files for a website :)

Reply

Hey @cybernet2u!

This isn't my exact department here at SymfonyCasts (I don't deal too much with icons, CSS, etc), but I believe we handle FontAwesome by building our own custom pack on their site that only has what we need. I'm not sure if that's the only way or best way :p. If you're importing a package directly and only want to import the icons you want, then I think it would be up to you to manually import the files you need (if that's possible, given how the library is organized).

Cheers!

Reply
MolloKhan Avatar MolloKhan | SFCASTS | posted 2 years ago | edited

Hey there

I'm sorry but I don't follow you. Could you elaborate a bit more?

Cheers!

Reply
Cat in space

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

This tutorial works great with Symfony5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.91.4
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.3.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.5
        "symfony/console": "^4.0", // v4.2.5
        "symfony/flex": "^1.9", // v1.17.6
        "symfony/form": "^4.0", // v4.2.5
        "symfony/framework-bundle": "^4.0", // v4.2.5
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.5
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.5
        "symfony/validator": "^4.0", // v4.2.5
        "symfony/web-server-bundle": "^4.0", // v4.2.5
        "symfony/webpack-encore-bundle": "^1.4", // v1.5.0
        "symfony/yaml": "^4.0", // v4.2.5
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.5
        "symfony/dotenv": "^4.0", // v4.2.5
        "symfony/maker-bundle": "^1.0", // v1.11.5
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.5
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.27.0", // 0.27.0
        "autocomplete.js": "^0.36.0",
        "autoprefixer": "^9.5.1", // 9.5.1
        "bootstrap": "^4.3.1", // 4.3.1
        "core-js": "^3.0.0", // 3.0.1
        "dropzone": "^5.5.1", // 5.5.1
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.4.0", // 3.4.0
        "popper.js": "^1.15.0",
        "postcss-loader": "^3.0.0", // 3.0.0
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^7.0.1", // 7.3.1
        "sortablejs": "^1.8.4", // 1.8.4
        "webpack-notifier": "^1.6.0" // 1.7.0
    }
}