Categories
Software Development Web Development

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

The bottom of this article also explains how to update the routes. I’ll update this article as I find issues or things change. There are 85+ revisions so far. LOL

Do not miss the last step.

Because it prints the damn routes out after a user registers, no other time only after registration. I used my psychic abilities to solve this magical issue. LOL

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.

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

The docs say something about app/appKernel.php that file no longer exists.  It has been replaced with just kernel.php and you do pretty much nothing to that file. What you do need to do is make sure that you add this line or it exists, the bundle system should add it.

This goes inside (should be inside) app\config\bundles.php file, you will see a list of the bundles your app is using in this file too.

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

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.

You don’t have to do step 3 of the old docs either as the config system has changed. There is no file anymore. When you install the bundle the new system creates a new file named app/config/routes/fos_js_routing.yaml for you and the required code is inside. NICE!!!

borat nice meme
Very nice!!!

For step 4 the required code is the same for version 5 as for version 4. I don’t know what this actually 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)

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.

If you are using Webpack with Symfony these lines are easier. 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 '../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js';

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

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.

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.

 

Leave a Reply

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