gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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!
This is the second jQuery plugin that we've used. The first was bootstrap... and that worked brilliantly! Look inside 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?
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.
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.
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.
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({<br />$: 'jquery',<br />jQuery: 'jquery',<br />});
2) not working
// uncomment if you're having problems with a jQuery plugin<br />.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.
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/webpack-encore/issues/244
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!
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 <a href="https://webpack.js.org/configuration/output/#outputpath">webpack path managing</a>
`
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!
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? :)
problem solved, I installed another command "yarn add @algolia/autocomplete-js " instead of "Yarn add autocomplete.js –dev ".
Thanks for your greate courses :)
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!
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!
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!
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!
// 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
}
}
// 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
}
}
Hey there
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!