gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
We still have work to do to get the new.html.twig
template working:
... lines 1 - 2 | |
{% block javascripts %} | |
... lines 4 - 5 | |
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.jquery.min.js"></script> | |
<script src="{{ asset('js/algolia-autocomplete.js') }}"></script> | |
... line 8 | |
{% endblock %} | |
... lines 10 - 25 |
we have a script tag for this external autocomplete library and one for our own public/js/algolia-autocomplete.js
file... which is our last JavaScript file in the public/
directory! Woo!
$(document).ready(function() { | |
$('.js-user-autocomplete').each(function() { | |
var autocompleteUrl = $(this).data('autocomplete-url'); | |
$(this).autocomplete({hint: false}, [ | |
{ | |
source: function(query, cb) { | |
$.ajax({ | |
url: autocompleteUrl+'?query='+query | |
}).then(function(data) { | |
cb(data.users); | |
}); | |
}, | |
displayKey: 'email', | |
debounce: 500 // only request every 1/2 second | |
} | |
]) | |
}); | |
}); |
This holds code that adds auto-completion... on this author box... which, yes, is totally broken.
To start, remove the CDN link to this autocomplete library:
... lines 1 - 2 | |
{% block javascripts %} | |
... lines 4 - 5 | |
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.jquery.min.js"></script> | |
... lines 7 - 8 | |
{% endblock %} | |
... lines 10 - 25 |
And, at your terminal, install it properly!
yarn add autocomplete.js --dev
Next, you know the drill, take the algolia-autocomplete.js
file and move it into the assets/js/
directory. But I'm not going to make this a new entry point. We could do that, but really, we already have an entry file that's included on this page: admin_article_form
:
... lines 1 - 2 | |
{% block javascripts %} | |
... lines 4 - 7 | |
{{ encore_entry_script_tags('admin_article_form') }} | |
{% endblock %} | |
... lines 10 - 25 |
So really, admin_article_form.js
should probably just use the code from algolia-autocomplete.js
.
So, move that file into the components/ directory... which is kind of meant for reusable modules. And... well, this isn't really written like a re-usable module yet because it just executes code instead or returning something, like a function. But, we'll work on that later.
Let's also take the algolia-autocomplete.css
file and move that all the way up here into assets/css/
. And just because we can, I'll make it an SCSS file!
Okay! Back in admin_article_form.js
, let's bring in this code: import './components/algolia-autocomplete'
:
... lines 1 - 4 | |
import './components/algolia-autocomplete'; | |
... lines 6 - 157 |
We don't need an import from
yet... because that file doesn't actually export anything. For the CSS: import '../css/algolia-autocomplete.scss'
:
... lines 1 - 4 | |
import './components/algolia-autocomplete'; | |
import '../css/algolia-autocomplete.scss'; | |
... lines 7 - 157 |
Back in new.html.twig
, the great thing is, we don't need to import this CSS file anymore or any of these script files. This is really how we want our templates to look: a single a call to {{ encore_entry_script_tags() }}
and a single call to {{ encore_entry_link_tags() }}
:
... lines 1 - 2 | |
{% block javascripts %} | |
{{ parent() }} | |
{{ encore_entry_script_tags('admin_article_form') }} | |
{% endblock %} | |
{% block stylesheets %} | |
{{ parent() }} | |
{{ encore_entry_link_tags('admin_article_form') }} | |
{% endblock %} | |
... lines 14 - 22 |
So if we refresh right now, not surprisingly, it still won't work! And it's our favorite error!
$ is undefined
from algolia-autocomplete.js
. Yes, this is the error I see when I close my eyes at night.
Let's get to work. Of course, we are referencing $
. So, import $ from 'jquery'
:
import $ from 'jquery'; | |
... lines 2 - 23 |
We're also using the autocomplete library in here. No problem: import autocomplete from 'autocomplete.js'
:
import $ from 'jquery'; | |
import autocomplete from 'autocomplete.js'; | |
... lines 3 - 23 |
Wait... that's not quite right. This autocomplete.js
library is a standalone JavaScript library that can be used with anything - jQuery, React, whatever. But... our existing code isn't using the "standalone" version of the library. It's using a jQuery plugin - this .autocomplete()
function - that comes with that package:
... lines 1 - 3 | |
$(document).ready(function() { | |
$('.js-user-autocomplete').each(function() { | |
... lines 6 - 7 | |
$(this).autocomplete({hint: false}, [ | |
... lines 9 - 19 | |
]) | |
}); | |
}); |
So, we could refactor our code down here to use the, kind of, official way of using this library - independent of jQuery. But... that's the easy way out! Let's see if we can get this to work as a jQuery plugin.
I'll hold Command
or Control
and click into autocomplete.js
. Then double-click the directory to zoom us there. The "main" file is this index.js
at the root of the directory. But if you look in dist/
, hey! autocomplete.jquery.js
! That's what we were including before via the <script>
tag!
So instead of importing the main file, let's import autocomplete.js/dist/autocomplete.jquery
:
import $ from 'jquery'; | |
import 'autocomplete.js/dist/autocomplete.jquery'; | |
... lines 3 - 23 |
And remember, we don't use import from with jQuery plugins... because they don't return anything: they modify the jQuery object.
Ok, I think we're great and I think we're ready. Move over, refresh and... huh:
jQuery is not defined
Notice it doesn't say "$ is not defined": it says "jQuery is not defined"... and it's coming from autocomplete.jquery.js
! It's coming from the third party package!
This... is tricky. Plain and simple, that file is written incorrectly. Yea, it only works if jQuery is a global variable! And in Webpack... it's not! Let's talk more about this and fix it with some black magic, next.
Hey Anton Bagdatyev
That's a good question :)
I would recommend you to check how other Jquery plugins do it. What I've seen is that they check for a couple of things, like for example this
if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory(require('jquery'));
}
That's checking if the code is being executed in a Node.js environment. I hope this helps a bit
Cheers!
Thank you for you reply, Diego!
Yes, what you say is correct, but your example is the final dist/bundle code generated by Webpack.
Whereas, I was thinking about the way plugin authors import jQuery in their sources before they modify <strong>$</strong> so that it works in all environments (UMD, i.e. CommonJS, AMD as well as global/window object)...
I think you agree with me that library authors won't write code like this anymore nowadays:
<br />(function (global, factory) {<br /> typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery'), require('popper.js')) :<br /> typeof define === 'function' && define.amd ? define(['exports', 'jquery', 'popper.js'], factory) :<br /> (global = global || self, factory(global.bootstrap = {}, global.jQuery, global.Popper));<br />}(this, (function (exports, $, Popper) { 'use strict';<br />...<br />
That's the result of Webpack/Rollup's transpiling process.
I just checked the source code of Bootstrap again and I found out that I was looking at the wrong version. I was looking at the master branch, whereas this Webpack Encore project uses version 4.4.1.
Indeed, if you look at the sources of that tag, you will see code like this (e.g. https://github.com/twbs/bootstrap/blob/v4.4.1/js/src/alert.js):
`
...
import $ from 'jquery'
...
$.fn[NAME] = Alert._jQueryInterface
$.fn[NAME].Constructor = Alert
$.fn[NAME].noConflict = () => {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Alert._jQueryInterface
}
export default Alert
`
They import jQuery and just use it, so I guess that's enough when using peer dependencies as Webpack (in their case Rollup) takes care of the rest (but have to play a bit with my jQuery plugin in order to be sure).
Otherwise I don't know which kind of black magic they are using yet.
Thanks!
> so I guess that's enough when using peer dependencies as Webpack
As far as I know, yes, Webpack takes care of the rest. You just have to import the dependencies you want to work with and that would be it
// 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
}
}
I have a question regarding JS/jQuery plugins written correctly (like bootstrap) and "badly" (like autocomplete.js): I checked out the bootstrap source code and found out (as I understood) that bootstrap uses a <strong>getjQuery()</strong> util function to get jQuery and attach plugin functionality to it:
But here you clearly see that <strong>getjQuery</strong> only checks for jQuery to be available on window...
How does then the final transpiled/built bootstrap (the one we import in our code using <strong>import 'bootstrap'</strong>, i.e. "dist/js/bootstrap.js")
know that it has to attach the jQuery plugin to the "local" jQuery peer dependency we installed with <strong>yarn add jquery --dev</strong> if the bootstrap's source only uses jQuery through the getjQuery() util function and therefore through window? Hope I correctly formulated my question.
I mean, I have written a jQuery plugin called jquery-multiselect-checkbox (you can check it out here: https://github.com/tonix-tuft/jquery-multiselect-checkbox/), and there in my index.js source file I explicitly do <strong>import $ from 'jquery'</strong> (which I marked as a peer dep in package.json) and I also check for window.jQuery and attach my plugin to both if available (if the imported $ is different from window.$, i.e. <strong>$ !== window.jQuery</strong>). But maybe this is not the way to do it when authoring libraries. I don't know...