panel
Version:
Web Components with Virtual DOM: lightweight composable web apps
144 lines (109 loc) • 4.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function stripHash(fragment) {
return fragment.replace(/^#*/, ``);
}
function decodedFragmentsEqual(currFragment, newFragment) {
// decodeURIComponent since hash fragments are encoded while being
// written to url, making `#bar baz` and `#bar%20baz` effectively the same.
// This can result in hash update loops if the client passes in decoded hash.
return decodeURIComponent(currFragment) === decodeURIComponent(newFragment);
} // just the necessary bits of Backbone router+history
class Router {
constructor(app) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// allow injecting window dep
this.window = options.window || window;
this.app = app;
const routeDefs = this.app.getConfig(`routes`); // https://github.com/jashkenas/backbone/blob/d682061a/backbone.js#L1476-L1479
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
const optionalParam = /\((.*?)\)/g;
const namedParam = /(\(\?)?:\w+/g;
const splatParam = /\*\w+/g;
const escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // eslint-disable-line no-useless-escape
this.compiledRoutes = Object.keys(routeDefs).map(routeExpr => {
// https://github.com/jashkenas/backbone/blob/d682061a/backbone.js#L1537-L1547
let expr = routeExpr.replace(escapeRegExp, `\\$&`).replace(optionalParam, `(?:$1)?`).replace(namedParam, (match, optional) => optional ? match : `([^/?]+)`).replace(splatParam, `([^?]*?)`);
expr = new RegExp(`^` + expr + `(?:\\?([\\s\\S]*))?$`); // hook up route handler function
let handler = routeDefs[routeExpr];
if (typeof handler === `string`) {
// reference to another handler rather than its own function
handler = routeDefs[handler];
}
return {
expr,
handler
};
});
this.registerListeners(options.historyMethod || `pushState`);
}
registerListeners(historyMethod) {
var _this = this;
this.navigateToHash = () => this.navigate(this.window.location.hash);
this.window.addEventListener(`popstate`, this.navigateToHash);
this.historyMethod = historyMethod;
this.origChangeStateMethod = this.window.history[this.historyMethod];
this.window.history[this.historyMethod] = function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this.origChangeStateMethod.apply(_this.window.history, args);
_this.navigateToHash(); // fire "pushstate" or "replacestate" event so external action can be taken on url change
// these events are meant to be congruent with native "popstate" event
_this.app.dispatchEvent(new CustomEvent(_this.historyMethod.toLowerCase()));
};
}
unregisterListeners() {
this.window.removeEventListener(`popstate`, this.navigateToHash);
this.window.history[this.historyMethod] = this.origChangeStateMethod;
}
navigate(fragment) {
let stateUpdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
fragment = stripHash(fragment);
if (decodedFragmentsEqual(this.app.state.$fragment, fragment) && !Object.keys(stateUpdate).length) {
return;
}
stateUpdate.$fragment = fragment;
for (const route of this.compiledRoutes) {
const matches = route.expr.exec(fragment);
if (matches) {
// extract params
// https://github.com/jashkenas/backbone/blob/d682061a/backbone.js#L1553-L1558
let params = matches.slice(1);
params = params.map((param, i) => {
// Don't decode the search params.
if (i === params.length - 1) {
return param || null;
}
return param ? decodeURIComponent(param) : null;
});
const routeHandler = route.handler;
if (!routeHandler) {
throw `No route handler defined for #${fragment}`;
}
const routeStateUpdate = routeHandler.call(this.app, stateUpdate, ...params);
if (routeStateUpdate) {
// don't update if route handler returned a falsey result
this.app.update(Object.assign({}, stateUpdate, routeStateUpdate));
}
return;
}
} // no route matched
console.error(`No route found matching #${fragment}`);
}
replaceHash(fragment) {
let {
historyMethod = null
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
historyMethod = historyMethod || this.historyMethod;
fragment = stripHash(fragment);
if (!decodedFragmentsEqual(stripHash(this.window.location.hash), fragment)) {
this.window.history[historyMethod](null, null, `#${fragment}`);
}
}
}
exports.default = Router;