Categories
Software Development Web Development

How to get URL Routes in your Javascript in Symfony 5+

This article has been recently updated to fix errors.

Do not miss the last step.

If you are using an EventSubscriber to store the last page the user visited or else Symfony redirects your users to the routes after they register.

Isn’t this exactly what you would expect???? Yeah me too.

In the Symfony docs it shows you how to get a URL to your javascript in your template. Basically what this is good for is it gives your Javascript code access to a url that you store in a javascript variable. You would place this at the bottom of your Twig template page, then you can access it with other Javascript code within that script tag area.

Another way to do it is to hide an element with an id in the template then use Javascript to fetch it.

This is all good if you only have a few routes.

But yet another way to get the routes is also mentioned in the Symfony Documentation, FosJsRoutingBundle. This allows you to skip the other ways and be able to just get the routes in your Javascript, when and where you need them. For example if your frontend is React, Vue etc. or you have a Javascript component that needs to use AJAX to interact with the backend.

Step 1.

Install FosJsRoutingBundle with this command

composer require friendsofsymfony/jsrouting-bundle

That command in version 5.1+ of Symfony installs and does pretty much everything you need. It even registers the bundle for you. Your config/bundles.yaml file should look something like this now. See FosJsRoutingBundle listed last.

<?php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
    DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
    Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
    FOS\JsRoutingBundle\FOSJsRoutingBundle::class => ['all' => true],
];

Step 2.

I am not sure what version Symfony started this but I know in 5+ you don’t have to register the bundle like step 2 of the old docs shows. This is done automatically for you in a new file named config/bundles.php which is where all bundles are automatically registered when you install them.

So all you do is this command now in the terminal

composer require friendsofsymfony/jsrouting-bundle

inside your apps root level. It is auto registered for you and you will see it listed inside composer.json file too.

Step 3.

You may or may not have to create this file. It may be created for you it might not.

So if the following file

When you install the bundle the new system, it might create a new file named app/config/routes/fos_js_routing.yaml for you and the required code is inside.
If not you must create this file and add the following.

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

 

For step 4 the required code is the same for version 5 as for version 4. I don’t know exactly what this does.

$ php bin/console assets:install --symlink public

But I know, you will also need to use this command or nothing will work. This is also how you update( see below)

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

That actually puts the file with the routes in the proper location so you can use it

Basically to install the bundle you just use the composer command and most of the stuff is now done for you.

Don’t forget to expose your routes

expose routes meme
Don’t forget to expose your routes.

I told you there are many baby steps.

In order for the routes to work you must add something new to every single route definition that you want to be able to generate a route for in your Javascript.

You must add this to the annotation or where ever you define your routes. I use annotations so I can just look to the controllers.

options={"expose"=true}

So for my menu route the definition in the controller looks like this.

@Route("/menu", name="menu", options={"expose"=true}, methods={"GET"})
</code.

Now how to use it?

yoda use the routes meme
Use the routes Luke.

So now that it is installed how do you use it? One more step. Now you must include the needed Javascript in your page with these tags. If you are not using Webpack with Symfony you can use these lines.

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

Place those anywhere you want. What I did is put them in my base twig template in a section where I include my Javascript this way it is available on every page because I will probably be needing access to routes as I build the app.

If you are using Webpack with Symfony, place these at the top of the file where you need to access routes.

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

If you don’t know, if you are using webpack then there will be an app.js file in your app\assets\js\app.js

Learn how to install webpack here in the Symfony Docs. Learn more about how to use Webpack with Symfony here.

You include this in your base template. Then anything you add to it will be included for other Javascript code you use with import statements. You must be using Webpack for this to work though. This is where I have included JQuery and Bootstrap and now FosJsRouting for example.

Once you have all of the above done you can use it like this


//set the routes for the Routing object
Routing.setRoutingData(routes);
//create a url
let testUrl = Routing.generate( 'menu');
//check to see it is working
console.log(testUrl); //outputs /menu

Read a lot more about generating URLs in the FosJsRouting documentation.

Route not found errors?

more errors meme
And.. Errors

Yes I got these too. You must call this setRoutingData() method first with the routes constant created above. I was like WTF too.

Routing.setRoutingData(routes);

The last step

What ever you do, do not forget this crucial step if you are using an EventSubscriber to save the users last page visited. Otherwise your users are shown your fos_js_routes.json file after Registering ONLY, NO OTHER TIME.

homer simpson meme
Not the action we expected now is it.

With the standard Symfony Authentication system created with the Maker bundles, your users will be returned to the page they last viewed when they login/register.
To have more control over this you can create an EventSubscriber like this.

There are more ways to do this than the one I suggest. But I suggest using a Subscriber class and extending

EventSubscriberInterface

The reason is, this works for both the Login and Registration forms and you don’t have to repeat code anywhere. Below is what my class looks like.


namespace App\EventSubscriber;


use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class TargetPathSubscriber implements EventSubscriberInterface
{

    use TargetPathTrait;

    private SessionInterface $session;

    public function __construct(SessionInterface $session)
    {
        $this->session = $session;
    }

    public function onKernelRequest(RequestEvent $event): void
    {

        $request = $event->getRequest();
        //don't save the path if the user has come to the login page first making a
        //never ending loop. Dont save AJAX requests either. Make sure this is a master
        //not a sub request, subs happen in forms
        $route = $request->attributes->get('_route');
        dump('Route is ' . $route);

        if (
            !$event->isMasterRequest()
            || $request->isXmlHttpRequest()
            || 'app_login' === $route
            || 'app_register' === $route
            || 'js/routing?callback=fos.Router.setData' === $route
            || 'menu'
            || 'alias_exists'
            || 'email_exists'
        ) {
            return;
        }

        $this->saveTargetPath($this->session, 'main', $request->getUri());

    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => ['onKernelRequest']
        ];
    }
}

You can read more about how this works in the Symfony documentation here. What the code inside onKernelRequest() does is it saves the targetPath aka the last page the user was viewing/tried to access. The problem is it saves things you don’t want like the login or registration routes.
What these lines of code are doing is excluding routes. What the Authentication system does is it looks for the _targetpath. You will need to make changes to your firewall too in order for this to work. More on that here.

Basically the code above only sets a targetpath if it is not one of the routes in the list. I had to add '/js/routing...' === $route so that the user would not be redirected to that, which is a script which outputs all of your routes you have exposed to FosJsRoutingBundle.

I set my firewall so that if nothing exists for targetpath, which will be the case for the routes in the list or if a user goes directly to yoursite.com What we need in that instance is a default page to redirect the user to.

This is what my firewall code looks like (inside config/packages/security.yaml


firewalls:
    dev:
      pattern: ^/(_(profiler|wdt)|css|images|js)/
      security: false
    main:
      lazy: true
      entry_point: form_login
      provider: app_user_provider
      form_login:
        login_path: app_login
        check_path: app_login
        #csrf_token_generator: security.csrf.token_manager
        enable_csrf: true
        default_target_path: list_articles
        username_parameter : 'email'
        password_parameter: 'password'

See this one magic line ( default_target_path: list_articles ) if nothing is set in my TargetPathSubscriber this is the route that is used.

The interesting and most confusing part of all of this was the fact that Symfony only displayed the FosJsRoutingBundle routes when a user registered not when they logged in. That was insanely confusing.

so much work meme
I told you getting routes in your symfony js is a lot of work

Updating your routes

Every time you add a new routes or change a routes name you must update the routes.json file or else you will get route not found errors. To update your routes use this command.

php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json
And now you know how to get routes in your Javascript in Symfony

View all the routes

You may also need or want to view which routes are now exposed after updating or out of curiosity. The following command will output all of your exposed routes.

 php bin/console fos:js-routing:debug

Links

More on stackoverflow about redirecting after registration.

An older SymfonyCast about integrating FosJsBundle

Here is the link to the FosJsRoutingBundle installation documentation on github which this blog basically explains.

Events and EventListeners in the Symfony Documentation.

More about using targetpath in this SymfonyCast

Here is a link to my favorite HTMLEntities converter if you write articles about programming you will need this.

 

By Robert jackson

This is my website. I write articles about programming and more. I've been programming 16 years now.

One reply on “How to get URL Routes in your Javascript in Symfony 5+”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: