Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Integrating FOSJsRoutingBundle

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.

Open Components/RepLogApp.js and search for Routing:

... 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:

... 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:

... 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'):

... 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!

Leave a comment!

8
Login or Register to join the conversation
Kiuega Avatar
Kiuega Avatar Kiuega | posted 3 years ago | edited

Hello, there is something I don't understand with FosJsRouting ...
I installed it, and I configured it like this:

<b>Routing.js</b>


// assets/Routing.js

const routes = require('../../public/js/fos_js_routes.json');
const Routing = require("../../public/bundles/fosjsrouting/js/router");

Routing.setRoutingData(routes);

module.exports = Routing;

<b>app.js</b>


$(document).ready(function(){

    var Routing = require('./Routing.js');
    console.log(Routing.generate("validation_index"));
})

And in my <b>base.html.twig</b>, before all my others javascript files :


<script src="{{ asset('bundles/fosjsrouting/js/router.min.js') }}"></script>
<script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>

If I launch the web page, I've :

<b>Uncaught Error: The route "validation_index" does not exist.
</b>
But if I do the same thing in a <b>twig template</b> in the <b>javascript block</b> :


$(document).ready(function(){

    var Routing = require('./Routing.js');
    console.log(Routing.generate("validation_index"));
})

it works...
So how can I make this work from my <b>app.js</b>?
Knowing that when I debug to see the list of available routes, I have the desired route

Reply

Hi Kiuega !

Hmm. So let me try something "simple" first: On the validation_index route, have you added "expose"=true? https://symfony.com/doc/master/bundles/FOSJsRoutingBundle/usage.html#generating-uris

If you haven't, that's your problem :). If you HAVE, well, then obviously it's something else. And, one thing stands out. In assets/Routing.js, you have const routes = require('../../public/js/fos_js_routes.json'); That means you are running fos:js-routing:dump to dump the JSON file. In that case, if this route is new, then you would need to run that command again to update the file. But on this topic, I also noticed that you have &lt;script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"&gt;. These should not both be needed. Sometimes I will have this last one, but only in dev mode - to basically make sure that if my dumped JSON files is missing any, they will be added here (in dev only, but it would still require you setting the Router as a global variable... and having your script tags in the right order). Oh, and the line above this (the script tag for router.min.js) isn't needed at all if you're properly importing the Router like you are. This is a long way of saying that you seem to be importing all the Routing stuff the "proper" way in assets/Routing.js... but also the "easier" way in base.html.twig. These two might be colliding in some way :).

Cheers!

Reply
Kiuega Avatar

Thanks for your help ! I had to make a dump !

Reply
Carlos Avatar

Does this really works with Symfony 4?? Because this:

php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json

is not working for me here. The file is empty.

Reply

Hey Carlos

Yea, that bundle works in Symfony4 since this version: https://github.com/FriendsO...

Probably you forgot to specify routes as public? https://symfony.com/doc/mas...

Cheers!

Reply
Carlos Avatar

It generates the file, but it results like this:

{"base_url":"","routes":[],"prefix":"","host":"localhost","scheme":"http"}

When I use the "php bin/console debug:router" it shows me all the routes that I've defined.

Maybe the documentation on https://symfony.com/doc/mas... is outdated? Look at the step 2, for example. I don´t think that now with Symfony4 I need to change this AppKernel.php file, even because it doesn´t exists. Maybe now it is the Kernel.php file:

public function registerBundles()
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if (isset($envs['all']) || isset($envs[$this->environment])) {
yield new $class();
}
}
}

From this method, I went to /config/bundles.php and the FOS\JsRoutingBundle\FOSJsRoutingBundle it´s already there:

[...]
FOS\JsRoutingBundle\FOSJsRoutingBundle::class => ['all' => true],

And app/config/routing.yml also doesn´t exists anymore. I think that it was replaced by config/routes/fos_js_routing.yaml, since it has the same content that was mentioned:

fos_js_routing:
resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"

There´s more people saying the same:

https://github.com/FriendsO...

Maybe anyone here knows the answer?? Thanks

Reply
Carlos Avatar

Ok, I think I found the problem. Here it is:

Checking how the FOSJsRoutingBundle I found the ExposedRoutesExtractor.php , that checks if "isRouteExposed" on "getRoutes()" method.

I found that I needed to expose my routes by setting the 'exposed' = true on the @Route, like this:

/**
*
* @Route("/bse/pessoa/findByNome/{str}", name="bse_pessoa_findByNome", methods={"GET"}, options = { "expose" = true })
*
*/
public function findByNome($str = null)
{
[...]

But, even after that, FOSJsRoutingBundle continues not showing anyone.

In this Issue (https://github.com/FriendsO..., they said that routes defined by annotations (FOSRest routes???) was not caught by this ExposedRoutesExtractor, only if they were exposed in yaml.

So I dit it. On config/routes/annotations.yaml I added:

controllers:
resource: ../../src/Controller/
type: annotation
options:
expose: true

Voila.

php bin/console fos:js-routing:debug now shows all my routes.

1 Reply

Hey Carlos

Nice findings! But you may not want to "expose" all your routes, so what you can do is to expose manually each of the routes that you want them as public, and then run this command (so your routes file gets updated)
bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json

Probably there is a way to not have to run that command every time you add a new route but at the moment I don't know how.

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.8.1
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
        "doctrine/orm": "^2.5", // v2.7.2
        "friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
        "friendsofsymfony/user-bundle": "dev-master", // dev-master
        "sensio/framework-extra-bundle": "^5.1", // v5.1.5
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.4
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.0.4
        "symfony/framework-bundle": "^4.0", // v4.0.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/monolog-bundle": "^3.1", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.7.0
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/swiftmailer-bundle": "^3.1", // v3.1.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/validator": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0", // v4.0.4
        "twig/twig": "2.10.*" // v2.10.0
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.4
        "symfony/dotenv": "^4.0", // v4.0.4
        "symfony/phpunit-bridge": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0" // v4.0.4
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.19.0", // 0.19.0
        "bootstrap": "3", // 3.3.7
        "copy-webpack-plugin": "^4.4.1", // 4.4.1
        "font-awesome": "4", // 4.7.0
        "jquery": "^3.3.1", // 3.3.1
        "node-sass": "^4.7.2", // 4.7.2
        "sass-loader": "^6.0.6", // 6.0.6
        "sweetalert2": "^7.11.0", // 7.11.0
        "webpack-notifier": "^1.5.1" // 1.5.1
    }
}
userVoice