Skip to content

Instantly share code, notes, and snippets.

@mcamiano
Last active January 24, 2018 14:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mcamiano/1e2aba568c1ac0591dcc0bcf289342ed to your computer and use it in GitHub Desktop.
Save mcamiano/1e2aba568c1ac0591dcc0bcf289342ed to your computer and use it in GitHub Desktop.
Laravel Javascript Route Helper

This gist adds a route:json' command to generate a Javascript module RouteHelper.js capable of resolving urls for the application's routes. The module encapsulates a json version of the application routes. It provides methods to resolve URLs with or without arguments, and relative or absolute paths. The purpose is to provide client-side Javascript route helpers comparable to those available to Laravel code within PHP.

Configuring

Copy the JsRouteHelper.php class into your app/Console/Commands folder, and add the classname to the app/Console/Kernel.php#commands array:

    protected $commands = [
        Commands\JsRouteHelper::class,
    ]; 

Decide what directory you want the helper script to be dropped into. This gist puts the file into resources/assets/js/api . To use this as the default you might need to mkdir -p resources/assets/js/api. Othwise edit the JsRouteHelper.php to put it in a different location. Whereever it is, needs to be available to load either through your HTML headers or a Javascript module packer.

Obviously, you'll need named routes in order for them to be dumped symbolically.

Run php artisan route:json, import the RouteHelper file, and new up an instance:

import RouteHelper from './api/RouteHelper'
Window.urlTo = new RouteHelper(document.head.querySelector('base').href));

axios.get(urlTo.route('widgets', widgetId)).then((results) => { 
    // do something with widget
}).catch((failure) { 
    // do something with failure
});

The helper can be chained as a property off of Axios' object as well. Note: the term route has multiple meanings in the context of a Laravel-backed SPA, and polluting another object with names could lead to shadowed properties.

import RouteHelper from './api/RouteHelper'

let urlTo = new RouteHelper(document.head.querySelector('base').href));
let axios = require('axios').create({ baseURL: urlTo.getBaseUrl(), timeout: 2500 });
axios.urlTo = urlTo;
...
let linkToHome = axios.urlTo.route('home'); 
...

Making an absolute route

You can pass positional arguments to the route helper, or pass only a second argument that is an object with named keys for the parameters. If the profile route named a "{username}" path segment, the following two forms would be equivalent:

let linkToMyProfile = axios.urlTo.route('profile', { username:  this.username })
let linkToMyProfile = axios.urlTo.route('profile', this.username)

Making a relative route

By default, the route helper returns a URL rooted in the base url passed to its constructor. If you want to grab a relative path instead, use the relativeTo(path) method:

let srcUrl = axios.urlTo.relativeTo('https://imgur.com/').route('file-browser')

You can also call urlTo.setBaseUrl(path) on the routeHelper object, or save the result of calling relativeTo which is a copy of the routeHelper object.

The method relative() is shorthand for relativeTo('.') Both of the methods mentioned here are chainable.

Getting and Setting the APP_URL

Calling the method getBaseUrl() on a newly instantiated routeHelper object returns the default root url with which it was constructed. The converse method, setBaseUrl(base) has the obvious functionality.

The setBaseUrl method is chainable.

Getting and Setting the route map

Calling the method setUrlMap(arrtype) on a newly instantiated routeHelper object overwrites the route mapping as imported from Laravel's routes. Again, the converse method, getUrlMap() exists.

The setUrlMap method is chainable.

// add "php artisan route:json" to run before your Javascript asset building pipeline
// and hook the generated class into your Javascript.
// This example assumes Vue with Axios; you may need to tweak the file paths.
// Example integration
import RouteHelper from 'api/RouteHelper'
let urlTo = new RouteHelper(window.location.origin+window.location.pathname; // untested in webpack env; may need to use global
let axios = require('axios').create({
baseURL: urlTo.getBaseUrl(),
timeout: 2500,
});
axios.urlTo = urlTo;
// then for instance,
let linkToMyProfile = axios.urlTo.route('profile', { username: this.username })
// or
let linkToMyProfile = axios.urlTo.route('profile', this.username)
<?php
// app/Console/Commands/JsRouteHelper.php
namespace App\Console\Commands;
use Illuminate\Support\Facades\File;
use Illuminate\Routing\Router;
use Illuminate\Console\Command;
/**
* Class JsRouteHelper
* Derived from examples from Andrei Canta at https://ideas.hexbridge.com/how-to-use-laravel-routes-in-javascript-4d9c484a0d97
*
* @package App\Console\Commands
*/
class JsRouteHelper extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'route:json';
protected $router;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate Javascript client-side route helper';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Router $router)
{
parent::__construct();
$this->router = $router;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$routes = [];
foreach ($this->router->getRoutes() as $route) {
$routes[$route->getName()] = $route->uri();
}
File::put('resources/assets/js/api/RouteHelper.js',
$this->jsContent(
json_encode($routes, JSON_PRETTY_PRINT)
)
);
}
private function jsContent($routes) {
return <<<EOJS
// This is an auto-generated file created using php artisan route:json
// The definition is in App/Console/Commands/JsRouteHelper.php#jsContent
export default class {
constructor(baseUrl) {
this.setUrlMap($routes);
this.setBaseUrl(baseUrl);
}
setUrlMap(urlMap) {
this.urlMap = urlMap;
return this;
}
getUrlMap() {
return this.urlMap;
}
setBaseUrl(newBase) {
this.baseUrl = newBase;
return this;
}
getBaseUrl() {
return this.baseUrl;
}
relativeTo(newBase) {
let wrappedCopy = Object.create(this);
wrappedCopy.setBaseUrl(newBase);
return wrappedCopy;
}
relative() {
return this.relativeTo('.');
}
route() {
var args = Array.prototype.slice.call(arguments);
var name = args.shift();
if (this.urlMap[name] === undefined) {
throw 'Oops. The application is pointing at an unknown location "' + name + '".';
}
// Object passed with keys for arguments
if (args.length == 1 && args[0] !== null && typeof args[0] === 'object') {
return this.baseUrl + this.urlMap[name]
.split('/')
.map((chunk) => {
if (chunk[0] == '{') {
let word = chunk.match(/{ *([^ }]+) *}/)[1];
if ((!word) || (!args[0][word])) {
return chunk;
}
return args[0][word];
} else {
return chunk;
}
}).join('/');
}
// Positional parameters
return this.baseUrl + '/' + this.urlMap[name]
.split('/')
.map(s => s[0] == '{' ? args.shift() : s)
.join('/');
}
}
EOJS;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment