Last active
September 18, 2018 09:58
-
-
Save Radiergummi/08152f9e6e1f87b57d7acc017a33fdd9 to your computer and use it in GitHub Desktop.
This is a simple Vue plugin implementation of Laroute
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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