UNPKG

simplyview

Version:

Library to rapidly build UI components, using declarative tools

240 lines (223 loc) 6.97 kB
export function routes(options, optionsCompat) { if (optionsCompat) { let app = options options = optionsCompat options.app = options } return new SimplyRoute(options) } class SimplyRoute { constructor(options={}) { this.root = options.root || '/' this.app = options.app this.addMissingSlash = !!options.addMissingSlash this.matchExact = !!options.matchExact this.clear() if (options.routes) { this.load(options.routes) } } load(routes) { parseRoutes(routes, this.routeInfo, this.matchExact) } clear() { this.routeInfo = [] this.listeners = { match: {}, call: {}, finish: {} } } match(path, options) { let args = { path, options } args = this.runListeners('match',args) path = args.path ? args.path : path; let matches; if (!path) { if (this.match(document.location.pathname+document.location.hash)) { return true; } else { return this.match(document.location.pathname); } } path = getPath(path); for ( let route of this.routeInfo) { matches = route.match.exec(path) if (this.addMissingSlash && !matches?.length) { if (path && path[path.length-1]!='/') { matches = route.match.exec(path+'/') if (matches) { path+='/' history.replaceState({}, '', getURL(path)) } } } if (matches && matches.length) { var params = {}; route.params.forEach((key, i) => { if (key=='*') { key = 'remainder' } params[key] = matches[i+1] }) Object.assign(params, options) args.route = route args.params = params args = this.runListeners('call', args) params = args.params ? args.params : params args.result = route.action.call(route, params) this.runListeners('finish', args) return args.result } } return false } runListeners(action, params) { if (!Object.keys(this.listeners[action])) { return } Object.keys(this.listeners[action]).forEach((route) => { var routeRe = getRegexpFromRoute(route); if (routeRe.exec(params.path)) { var result; for (let callback of this.listeners[action][route]) { result = callback.call(this.app, params) if (result) { params = result } } } }) return params } handleEvents() { globalThis.addEventListener('popstate', () => { if (this.match(getPath(document.location.pathname + document.location.hash, this.root)) === false) { this.match(getPath(document.location.pathname, this.root)) } }) this.app.container.addEventListener('click', (evt) => { if (evt.ctrlKey) { return; } if (evt.which != 1) { return; // not a 'left' mouse click } var link = evt.target; while (link && link.tagName!='A') { link = link.parentElement; } if (link && link.pathname && link.hostname==globalThis.location.hostname && !link.link && !link.dataset.simplyCommand ) { let path = getPath(link.pathname+link.hash, this.root); if ( !this.has(path) ) { path = getPath(link.pathname, this.root); } if ( this.has(path) ) { let params = this.runListeners('goto', { path: path}); if (params.path) { if (this.goto(params.path)) { // now cancel the browser navigation, since a route handler was found evt.preventDefault(); return false; } } } } }) } goto(path) { history.pushState({},'',getURL(path)) return this.match(path) } has(path) { path = getPath(path, this.root) for (let route of this.routeInfo) { var matches = route.match.exec(path) if (matches && matches.length) { return true } } return false } addListener(action, route, callback) { if (['goto','match','call','finish'].indexOf(action)==-1) { throw new Error('Unknown action '+action) } if (!this.listeners[action][route]) { this.listeners[action][route] = [] } this.listeners[action][route].push(callback) } removeListener(action, route, callback) { if (['match','call','finish'].indexOf(action)==-1) { throw new Error('Unknown action '+action) } if (!this.listeners[action][route]) { return } this.listeners[action][route] = this.listeners[action][route].filter((listener) => { return listener != callback }) } init(options) { if (options.root) { this.root = options.root } } } function getPath(path, root='/') { if (path.substring(0,root.length)==root || ( root[root.length-1]=='/' && path.length==(root.length-1) && path == root.substring(0,path.length) ) ) { path = path.substring(root.length) } if (path[0]!='/' && path[0]!='#') { path = '/'+path } return path } function getURL(path, root) { path = getPath(path, root) if (root[root.length-1]==='/' && path[0]==='/') { path = path.substring(1) } return root + path; } function getRegexpFromRoute(route, exact=false) { if (exact) { return new RegExp('^'+route.replace(/:\w+/g, '([^/]+)').replace(/:\*/, '(.*)')+'(\\?|$)') } return new RegExp('^'+route.replace(/:\w+/g, '([^/]+)').replace(/:\*/, '(.*)')) } function parseRoutes(routes, routeInfo, exact=false) { const paths = Object.keys(routes) const matchParams = /:(\w+|\*)/g for (let path of paths) { let matches = [] let params = [] do { matches = matchParams.exec(path) if (matches) { params.push(matches[1]) } } while(matches) routeInfo.push({ match: getRegexpFromRoute(path, exact), params: params, action: routes[path] }) } return routeInfo }