Skip to content

Instantly share code, notes, and snippets.

@Radiergummi
Last active September 18, 2018 09:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Radiergummi/08152f9e6e1f87b57d7acc017a33fdd9 to your computer and use it in GitHub Desktop.
Save Radiergummi/08152f9e6e1f87b57d7acc017a33fdd9 to your computer and use it in GitHub Desktop.
This is a simple Vue plugin implementation of Laroute
'use strict';
//noinspection JSFileReferences
/**
* This is a generic HTTP module such as axios, which needs to be defined by the user.
* Maybe as a config setting?
*/
import http from './modules/http';
/**
* Router-Link component definition
*/
const RouterLink = {
name: 'router-link',
props: {
tag: {
type: String,
default: 'a'
},
to: {
type: String,
default: ''
},
with: {
'type': Object
},
data: {
type: Object,
default: () => ({})
,
method: {
type: String,
default: 'get'
}
}
},
computed: {
/**
* The target URL. Uses all given parameters to build the URL.
*/
url() {
return this.$router.match(this.to, this.with);
}
},
methods: {
// navigate to the link target
navigate(event) {
if (this.method.toLowerCase() === 'get') {
// this is a default GET link, let the browser do it's job
if (this.tag === 'a') {
return true;
}
window.location = this.url;
return true;
}
// otherwise, we got more work to do, so discard whatever the default action would be
event.preventDefault();
// TODO: This is a generic HTTP method call and should be swapped out against
// a user provided library. Maybe define it in the config file?
http({
method: this.method.toLowerCase(),
url: this.url,
data: this.data,
// pass withCredentials to axios to retain the user session
withCredentials: true
});
return true;
}
},
/**
* renders the router-link
*
* @param {function(tagName:string, data:object, slots:object)} createElement
* @returns {VNode}
*/
render(createElement) {
const attributes = {};
if (this.tag === 'a') {
attributes.href = this.url;
}
return createElement(this.tag, {
attrs: attributes,
on: {
click: this.navigate
}
}, this.$slots.default);
}
};
/**
* Client router class
*/
class Router {
/**
* creates a new router
*
* @param {RouterBackend} backend
*/
constructor(backend) {
this._backend = backend;
}
/**
* retrieves all routes
*
* @returns {object}
*/
get routes() {
return this._backend._routes;
}
/**
* matches a route. accepts URLs, actions and routes.
*
* @param {string} target
* @param {object} [parameters]
* @returns {*}
*/
match(target, parameters) {
if (target.indexOf('/') > -1) {
return this.url(target, parameters);
}
if (/^.+@.+$/.test(target)) {
return this.action(target, parameters);
}
return this.route(target, parameters);
}
/**
* retrieves a route by route
*
* @param {string} route
* @param {object} [parameters]
* @returns {string|undefined}
*/
route(route, parameters) {
return this._backend.route(route, parameters);
}
/**
* retrieves a route by action
* @param {string} name
* @param {object} [parameters]
* @returns {string|undefined}
*/
action(name, parameters) {
return this._backend.route(name, parameters, this._backend.getByAction(name));
}
/**
* builds a URL
*
* @param {string} route
* @param {object} [parameters]
* @returns {string}
*/
url(route, parameters) {
return this._backend.url(route, parameters);
};
/**
* generates an html link to a given URL
* @param {string} url
* @param {string} [title]
* @param {object} [attributes]
* @returns {string}
*/
link_to(url, title, attributes) {
return Router.getHtmlLink(this._backend.url(url), title, attributes);
}
/**
* generate a html link to the given route
*
* @param {string} route
* @param {string} [title]
* @param {object} [parameters]
* @param {object} [attributes]
* @returns {string}
*/
link_to_route(route, title, parameters, attributes) {
const url = this._backend.route(route, parameters);
return Router.getHtmlLink(url, title, attributes);
}
/**
* generate a html link to the given controller action
*
* @param {string} action
* @param {string} [title]
* @param {object} [parameters]
* @param {object} [attributes]
* @returns {string}
*/
link_to_action(action, title, parameters, attributes) {
const url = this._backend.action(action, parameters);
return Router.getHtmlLink(url, title, attributes);
}
/**
* retrieves link attributes
*
* @param {object} [attributes]
* @returns {string}
*/
static getLinkAttributes(attributes = {}) {
const mappedAttributes = [];
for (let key in attributes) {
if (attributes.hasOwnProperty(key)) {
mappedAttributes.push(`${key}="${attributes[key]}"`);
}
}
return mappedAttributes.join(' ');
}
/**
* generates a valid HTML link
*
* @param {string} url
* @param {string} [title]
* @param {object} [attributes]
* @returns {string}
*/
static getHtmlLink(url, title = url, attributes) {
attributes = Router.getLinkAttributes(attributes);
return `<a href="${url}" ${attributes}>${title}</a>`;
}
}
/**
* Laravel router backend
*/
class RouterBackend {
/**
* initializes a new backend
*
* @param {object} [config]
* @param {bool} config.absolute
* @param {string} config.rootUrl
* @param {object} config.routes
* @param {string} config.prefix
*/
constructor(config = {}) {
this._absolute = config.absolute;
this._rootUrl = config.rootUrl;
this._routes = config.routes;
this._prefix = config.prefix;
}
/**
* whether the router is in absolute mode
*
* @returns {bool}
*/
isAbsolute() {
return this._absolute;
}
/**
* creates a route
*
* @param {string} name
* @param {object} [parameters]
* @param {object} [route]
* @returns {string|undefined}
*/
route(name, parameters, route = this.getByName(name)) {
if (!route) {
return undefined;
}
return this.toRoute(route, parameters);
}
/**
* retrieves the URL
*
* @param {string} url
* @param {object} [parameters]
* @returns {string}
*/
url(url, parameters = []) {
return this.getCorrectUrl(`${url}/${parameters.join('/')}`);
}
/**
* retrieves the URL to a route
*
* @param {object} route
* @param {object} [parameters]
* @returns {*}
*/
toRoute(route, parameters) {
const uri = this.replaceNamedParameters(route.uri, parameters),
queryString = this.getRouteQueryString(parameters);
if (this.isAbsolute() && this.isOtherHost(route)) {
return `//${route.host}/${uri}${queryString}`;
}
return this.getCorrectUrl(uri + queryString);
}
//noinspection JSMethodCanBeStatic
/**
* checks whether a route points to a foreign host
*
* @param {object} route
* @returns {boolean}
*/
isOtherHost(route) {
return route.host && route.host !== window.location.hostname;
}
/**
* replaces URI parameter placeholders
*
* @param {string} uri
* @param {object} parameters
* @returns {XML|string}
*/
replaceNamedParameters(uri, parameters) {
uri = uri.replace(/{(.*?)\??}/g, (match, key) => {
if (parameters.hasOwnProperty(key)) {
const value = parameters[key];
delete parameters[key];
return value;
}
return match;
});
// Strip out any optional parameters that were not given
return uri.replace(/\/{.*?\?}/g, '');
}
//noinspection JSMethodCanBeStatic
/**
* retrieves the query string for a route
*
* @param {object} [parameters]
* @returns {*}
*/
getRouteQueryString(parameters = {}) {
const queryString = [];
for (let key in parameters) {
if (parameters.hasOwnProperty(key)) {
queryString.push(key + '=' + parameters[key]);
}
}
if (queryString.length < 1) {
return '';
}
return '?' + queryString.join('&');
}
/**
* retrieves a route by name
*
* @param {string} name
* @returns {object}
*/
getByName(name) {
for (let key in this._routes) {
if (this._routes.hasOwnProperty(key) && this._routes[key].name === name) {
return this._routes[key];
}
}
}
/**
* retrieves a route by action
*
* @param {string} action
* @returns {object}
*/
getByAction(action) {
for (let key in this._routes) {
if (this._routes.hasOwnProperty(key) && this._routes[key].action === action) {
return this._routes[key];
}
}
}
/**
* retrieves the correct URL to a location
*
* @param {string} uri
* @returns {string}
*/
getCorrectUrl(uri) {
const url = this._prefix + '/' + uri.replace(/^\/?/, '');
if (!this.isAbsolute()) {
return url;
}
return this._rootUrl.replace('/\/?$/', '') + url;
}
/**
* installs the plugin to the Vue prototype
*
* @param {Vue$3} Vue
*/
install(Vue) {
/**
* attach a shortcut to the current location
*
* @type {string}
*/
Vue.prototype.$route = window.location.href;
//noinspection JSUnusedGlobalSymbols
/**
* creates the router on our Vue prototype
*
* @type {Router}
*/
Vue.prototype.$router = new Router(this);
/**
* adds the router-link component
*/
Vue.component('router-link', RouterLink);
}
}
//noinspection JSUnresolvedVariable,SpellCheckingInspection
/**
* retrieve the configuration from Laravel
*
* @type {{absolute, rootUrl: string, routes, prefix: string}}
*/
const config = {
absolute: $ABSOLUTE$,
rootUrl: '$ROOTURL$',
routes: $ROUTES$,
prefix: '$PREFIX$'
};
//noinspection JSUnusedGlobalSymbols
/**
* Export the routing frontend
*
* @type {RouterBackend}
*/
export default new RouterBackend(config);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment