10.

Integrating FOSJsRoutingBundle

Share this awesome video!

|

Open Components/RepLogApp.js and search for Routing:

214 lines | assets/js/Components/RepLogApp.js
// ... lines 1 - 8
class RepLogApp {
// ... lines 10 - 43
loadRepLogs() {
$.ajax({
url: Routing.generate('rep_log_list'),
// ... lines 47 - 50
})
}
// ... lines 53 - 194
}
// ... lines 196 - 214

Guess what? This Routing variable is a global variable. Boo! It's our last one. In templates/, open the base layout:

106 lines | templates/base.html.twig
// ... lines 1 - 96
{% block javascripts %}
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>
// ... lines 100 - 101
{% endblock %}
// ... lines 103 - 106

Other than a polyfill - which we won't talk about - there are only two script tags left. These give us the Router variable and they come from FOSJsRoutingBundle: a really cool bundle that allows you to generate URLs from Symfony routes in JavaScript.

Our goal is clear: refactor our code so that we can require the Router instead of relying on the global variable.

Requiring the router.js File

The first interesting thing is that this is not a Node package. Nope, it's a normal PHP package that happens to have a JavaScript file inside. But, that doesn't really make any difference... except that the path for it is ugly: it lives in vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.js. Wow! Ok then: const Routing = require(), then go up a few directories and follow the path: vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.js:

215 lines | assets/js/Components/RepLogApp.js
// ... lines 1 - 5
const Routing = require('../../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js');
// ... lines 7 - 215

Simple enough! Let's try it! In your browser, refresh! Bah! Error!

The route rep_log_list does not exist

Booo! This error comes from inside the Router. Here's what's going on: this JavaScript library is more complex than most. The first script tag gives us the Router variable. But the second executes a dynamic endpoint that fetches a JSON list of the route information and then sets that on the router.

When we simply require the router... we do get the Router object... but it has no routes! So the question is: how can we get the dynamic route info so that it can be set into the router?

Actually, this is possible! If you look at the Usage section of the bundle's docs, it talks about how to integrate with Webpack Encore. Basically, by running a bin/console command, you can dump your route information to a static JSON file. Then, you can require that JSON from Webpack and set it on the Router. Oh, and don't worry about this import syntax - it's basically the same as require(), and we'll talk about it next.

So this is really cool! It shows how you can even require JSON files from JavaScript! But... it has a downside: each time you add a new route, you need to re-run the command. That can be a pain during development. It's still a great option - and is a bit faster on production - but it does have that weakness.

Creating the Fake Router Module

And there is another option. It's not quite as fancy or awesome... but it's easier. Inside assets/js/Components, create a new file called Routing.js. Inside, um, just say, module.exports = window.Routing:

/**
* For now, we rely on the router.js script tag to be included
* in the layout. This is just a helper module to get that object.
*/
module.exports = window.Routing;

Yep! We are going to continue using the global variable. But now, we can at least require this file from everywhere else so that our code looks more responsible: const Routing = require('./Routing'):

215 lines | assets/js/Components/RepLogApp.js
// ... lines 1 - 5
const Routing = require('./Routing');
// ... lines 7 - 215

And now, when we refresh, it works. The cool thing about this hacky solution is that if you want to change to the better solution later, it's easy! Just put the correct code in Router.js, and everything will already be using it. Nice!