Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Auto-Provide jQuery for Mischievous Packages

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.

Everything should be working... but nope! We've got this

jQuery is not defined

error... but it's not from our code! It's coming from inside of autocomplete.jquery.js - that third party package we installed!

Poorly-Behaved jQuery Packages

This is the second jQuery plugin that we've used. The first was bootstrap... and that worked brilliantly! Look inside app.js:

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

We imported bootstrap and, yea... that was it. Bootstrap is a well-written jQuery plugin, which means that inside, it imports jquery - just like we do - and then modifies it.

But this Algolia autocomplete.js plugin? Yea, it's not so well-written. Instead of detecting that we're inside Webpack and importing jQuery, it just says... jQuery! And expects it to be available as a global variable. This is why jQuery plugins are a special monster: they've been around for so long, that they don't always play nicely in the modern way of doing things.

So... are we stuck? I mean, this 3rd-party package is literally written incorrectly! What can we do?

autoProvidejQuery()

Well... it's Webpack to the rescue! Open up webpack.config.js and find some commented-out code: autoProvidejQuery(). Uncomment that:

... lines 1 - 2
Encore
... lines 4 - 67
// uncomment if you're having problems with a jQuery plugin
.autoProvidejQuery()
... lines 70 - 73
;
... lines 75 - 76

Then, go restart Encore:

yarn watch

When it finishes, move back over and... refresh! No errors! And if I start typing in the autocomplete box... it works! What black magic is this?!

The .autoProvidejQuery() method... yea... it sorta is black magic. Webpack is already scanning all of our code. When you enable this feature, each time it finds a jQuery or $ variable- anywhere in any of the code that we use - that is uninitialized, it replaces it with require('jquery'). It basically rewrites the broken code to be correct.

Including CSS from the Algolia JS

While we're here, there's an organizational improvement I want to make. Look inside admin_article_form.js. Hmm, we include both the JavaScript file and the CSS file for Algolia autocomplete:

... lines 1 - 4
import './components/algolia-autocomplete';
import '../css/algolia-autocomplete.scss';
... lines 7 - 157

But if you think about it, this CSS file is meant to support the algolia-autocomplete.js file. To say it differently: the CSS file is a dependency of algolia-autocomplete.js: if that file was ever used without this CSS file, things wouldn't look right.

Take out the import and move it into algolia-autocomplete.js. Make sure to update the path:

import $ from 'jquery';
import 'autocomplete.js/dist/autocomplete.jquery';
import '../../css/algolia-autocomplete.scss';
... lines 4 - 24

That's nice! If we want to use this autocomplete logic somewhere else, we only need to import the JavaScript file: it takes care of importing everything else. The result is the same, but cleaner.

Making algolia-autocomplete.js a Proper Module

Well, this file still isn't as clean as I want it. We're importing the algolia-autocomplete.js file... but it's not really a "module". It doesn't export some reusable function or class: it just runs code. I really want to start thinking of all of our JavaScript files - except for the entry files themselves - as reusable components.

Check it out: instead of just "doing" stuff, let's export a new function that can initialize the autocomplete logic. Replace $(document).ready() with export default function() with three arguments: the jQuery $elements that we want to attach the autocomplete behavior to, the dataKey, which will be used down here as a way of a defining where to get the data from on the Ajax response, and displayKey - another config option used at the bottom, which is the key on each result that should be displayed in the box:

... lines 1 - 4
export default function($elements, dataKey, displayKey) {
... lines 6 - 25
};

Basically, we're taking out all the specific parts and replacing them with generic variables.

Now we can say $elements.each():

... lines 1 - 4
export default function($elements, dataKey, displayKey) {
$elements.each(function() {
... lines 7 - 24
});
};

And for dataKey, we can put a bit of logic: if (dataKey), then data = data[dataKey], and finally just cb(data):

... lines 1 - 4
export default function($elements, dataKey, displayKey) {
$elements.each(function() {
var autocompleteUrl = $(this).data('autocomplete-url');
$(this).autocomplete({hint: false}, [
{
source: function(query, cb) {
$.ajax({
url: autocompleteUrl+'?query='+query
}).then(function(data) {
if (dataKey) {
data = data[dataKey];
}
cb(data);
});
},
... lines 21 - 22
}
])
});
};

Some of this is specific to exactly how the Autocomplete library itself works - we set that up in an earlier tutorial. Down at the bottom, set displayKey to displayKey:

... lines 1 - 4
export default function($elements, dataKey, displayKey) {
$elements.each(function() {
var autocompleteUrl = $(this).data('autocomplete-url');
$(this).autocomplete({hint: false}, [
{
source: function(query, cb) {
$.ajax({
url: autocompleteUrl+'?query='+query
}).then(function(data) {
if (dataKey) {
data = data[dataKey];
}
cb(data);
});
},
displayKey: displayKey,
debounce: 500 // only request every 1/2 second
}
])
});
};

Beautiful! Instead of doing something, this file returns a reusable function. That should feel familiar if you come from the Symfony world: we organize code by creating files that contain reusable classes, instead of files that contain procedural code that instantly does something.

Ok! Back in admin_article_form.js, let's import autocomplete from './components/algolia-autocomplete':

... lines 1 - 4
import autocomplete from './components/algolia-autocomplete';
... lines 6 - 161

Oooo. And then, const $autoComplete = $('.js-user-autocomplete') - to find the same element we were using before:

... lines 1 - 8
$(document).ready(function() {
const $autoComplete = $('.js-user-autocomplete');
... lines 11 - 44
});
... lines 46 - 161

Then, if not $autoComplete.is(':disabled'), call autocomplete() - because that's the variable we imported - and pass it $autoComplete, users for dataKey and email for displayKey:

... lines 1 - 8
$(document).ready(function() {
const $autoComplete = $('.js-user-autocomplete');
if (!$autoComplete.is(':disabled')) {
autocomplete($autoComplete, 'users', 'email');
}
... lines 14 - 44
});
... lines 46 - 161

I love it! By the way, the reason I'm added this :disabled logic is that we originally set up our forms so that the author field that we're adding this autocomplete to is disabled on the edit form. So, there's no reason to try to add the autocomplete stuff in that case.

Ok, refresh... then type admi... it works! Double-check that we didn't break the edit page: go back to /admin/article, edit any article and, yea! Looks good! The field is disabled, but nothing is breaking.

Hey! We have no more JavaScript files in our public/ directory. Woo! But, we do still have 2 CSS files. Let's handle those next.

Leave a comment!

15
Login or Register to join the conversation

Hey Piotr

Could you tell me more about your problem? I don't understand what went wrong.
In my opinion, Webpack Encore is a great Symfony component, it simplifies a lot working with Webpack

Cheers!

Reply
Piotr P. Avatar

just want to achieve jquery / java script - available on webpage. Not somewhere in mystery Encore cloud.

`webpack.config.js`

1) not working:

new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
});

2) not working

// uncomment if you're having problems with a jQuery plugin
.autoProvidejQuery()

app.js
NOT working

import './styles/app.css';
import 'bootstrap';

console.log('whatewer);

import '@popperjs/core';

======================
NOT working

import $ from 'jquery';

========================
NOT working

const $ = require('jquery') ;

let $ = require('jquery');

import 'webpack-jquery-ui';

global.$ = global.jquery = $;

....

each time I render the `build` dir, clean cache
twig has propper import `

{{ encore_entry_link_tags('app') }}` `
{{ encore_entry_script_tags('app') }}

Encore just dont want to be friend. I dont understand the idea of this tool. Everything it does can be achieved SIMPLER.

Reply

I got your point, jQuery and Webpack may have problems but it's because there are a few jQuery plugins badly written. You can get more info about it here https://github.com/symfony/...
A quick workaround would be to add these lines to your webpack.config.js file


const config = Encore.getWebpackConfig();
config.resolve.alias.jquery = path.join(__dirname, 'node_modules/jquery/dist/jquery');


Anyway, Symfony has a component's architecture, it does not force about anything, you can add or remove any Symfony component as you please. If Webpack Encore does not fit your needs, you can remove it and use something else instead

Cheers!

Reply
Piotr P. Avatar

Hi diego, firstly, thank You for your time,

we have progress but not quite.

I have removed nodeJs.14, installed nodeJs 18. - that was main reason for problems.

your workaround is great. Just need to defeat one more yelling information from parser about the proper path

found at webpack path managing






module.exports = {
//...
output: {
path: path.resolve(__dirname, 'public/assets'),
publicPath: 'https://localhost:8000/build/assets/',
},
};


const config = Encore.getWebpackConfig();
config.resolve.alias.jquery = path.join(__dirname, 'node_modules/jquery/dist/jquery');


thx, Cheers!

Reply
Leszek C. Avatar
Leszek C. Avatar Leszek C. | posted 9 months ago

Hello. I have a problem.
In algoria-autocomplete.js file i was trying to import like you:
import 'autocomplete.js/dist/autocomplete.jquery';

But in new version algoria autocomplete there is no such directory there :/
I tried several options but failed. Could you help me? :)

Reply
Leszek C. Avatar

problem solved, I installed another command "yarn add @algolia/autocomplete-js " instead of "Yarn add autocomplete.js –dev ".
Thanks for your greate courses :)

Reply

Hey Lechu85,

Thank you for sharing your solution with others!

P.S. though, you may still want to add "--dev" option to that command as well to install it as a dev dependency :)

Cheers!

1 Reply
Leszek C. Avatar

Can you tell why --dev must be? After all, autocomplete is also used in production mode :)

Reply

Hey Lechu85,

I bet it's explained somewhere in this course ;) But in short, it's not about "must be", but it's just a good practice. That's because all your NodeJS dependencies you install with Yarn are dev-type and nothing is available to the user. Then, the intermediate layer that is Webpack Encore in this case is handle this and compile the final version of those dependencies into the public/build/ folder which in turn is available for users. So, technically, all your JS deps are dev deps and Webpack Encore processes them and compile for users.

I hope it's clearer for you now :)

Cheers!

Reply
Leszek C. Avatar

Thanks for your answer. I understand well.

Reply
Default user avatar
Default user avatar Anton Bagdatyev | posted 2 years ago

Thank you! Does .autoProvidejQuery() use Webpack's ProvidePlugin under the hood?

Reply

Hey Anton,

Yes, it should use Webpack's ProvidePlugin behind the scene: https://webpack.js.org/plug... - it's just Encore shortcut for jQuery.

Cheers!

Reply
Default user avatar
Default user avatar Anton Bagdatyev | victor | posted 2 years ago

Thanks Victor!

Reply

Your explanation to fix the agolia-autocomplete problem saves my week. Thank you!

Reply

Hey Holger,

Thank you for your feedback! We are really happy to hear this from our users :) And that's why wer're doing screencasts! We want to help our users to understand things better, faster, and save them some time! :)

Cheers!

1 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
    }
}