ima
Version:
IMA.js framework for isomorphic javascript application
460 lines (367 loc) • 11.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _ActionTypes = require("./ActionTypes");
var _ActionTypes2 = _interopRequireDefault(_ActionTypes);
var _Events = require("./Events");
var _Events2 = _interopRequireDefault(_Events);
var _Router = require("./Router");
var _Router2 = _interopRequireDefault(_Router);
var _RouteNames = require("./RouteNames");
var _RouteNames2 = _interopRequireDefault(_RouteNames);
var _GenericError = require("../error/GenericError");
var _GenericError2 = _interopRequireDefault(_GenericError);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The basic implementation of the {@codelink Router} interface, providing the
* common or default functionality for parts of the API.
*
* @abstract
*/
class AbstractRouter extends _Router2.default {
/**
* Initializes the router.
*
* @param {PageManager} pageManager The page manager handling UI rendering,
* and transitions between pages if at the client side.
* @param {RouteFactory} factory Factory for routes.
* @param {Dispatcher} dispatcher Dispatcher fires events to app.
* @example
* router.link('article', {articleId: 1});
* @example
* router.redirect('http://www.example.com/web');
* @example
* router.add(
* 'home',
* '/',
* ns.app.page.home.Controller,
* ns.app.page.home.View,
* {
* onlyUpdate: false,
* autoScroll: true,
* allowSPA: true,
* documentView: null,
* managedRootView: null,
* viewAdapter: null
* }
* );
*/
constructor(pageManager, factory, dispatcher) {
super();
/**
* The page manager handling UI rendering, and transitions between
* pages if at the client side.
*
* @type {PageManager}
*/
this._pageManager = pageManager;
/**
* Factory for routes.
*
* @type {RouteFactory}
*/
this._factory = factory;
/**
* Dispatcher fires events to app.
*
* @type {Dispatcher}
*/
this._dispatcher = dispatcher;
/**
* The current protocol used to access the application, terminated by a
* colon (for example {@code https:}).
*
* @type {string}
*/
this._protocol = '';
/**
* The application's host.
*
* @type {string}
*/
this._host = '';
/**
* The URL path pointing to the application's root.
*
* @type {string}
*/
this._root = '';
/**
* The URL path fragment used as a suffix to the {@code _root} field
* that specifies the current language.
*
* @type {string}
*/
this._languagePartPath = '';
/**
* Storage of all known routes. The key are the route names.
*
* @type {Map<string, Route>}
*/
this._routes = new Map();
}
/**
* @inheritdoc
*/
init(config) {
this._protocol = config.$Protocol || '';
this._root = config.$Root || '';
this._languagePartPath = config.$LanguagePartPath || '';
this._host = config.$Host;
this._currentlyRoutedPath = this.getPath();
}
/**
* @inheritdoc
*/
add(name, pathExpression, controller, view, options = undefined) {
if (this._routes.has(name)) {
throw new _GenericError2.default(`ima.router.AbstractRouter.add: The route with name ${name} ` + `is already defined`);
}
let factory = this._factory;
let route = factory.createRoute(name, pathExpression, controller, view, options);
this._routes.set(name, route);
return this;
}
/**
* @inheritdoc
*/
remove(name) {
this._routes.delete(name);
return this;
}
/**
* @inheritdoc
*/
getPath() {
throw new _GenericError2.default('The getPath() method is abstract and must be overridden.');
}
/**
* @inheritdoc
*/
getUrl() {
return this.getBaseUrl() + this.getPath();
}
/**
* @inheritdoc
*/
getBaseUrl() {
return this.getDomain() + this._root + this._languagePartPath;
}
/**
* @inheritdoc
*/
getDomain() {
return this._protocol + '//' + this._host;
}
/**
* @inheritdoc
*/
getHost() {
return this._host;
}
/**
* @inheritdoc
*/
getProtocol() {
return this._protocol;
}
/**
* @inheritdoc
*/
getCurrentRouteInfo() {
let path = this.getPath();
let route = this._getRouteByPath(path);
if (!route) {
throw new _GenericError2.default(`ima.router.AbstractRouter.getCurrentRouteInfo: The route ` + `for path ${path} is not defined.`);
}
let params = route.extractParameters(path);
return {
route,
params,
path
};
}
/**
* @inheritdoc
* @abstract
*/
listen() {
throw new _GenericError2.default('The listen() method is abstract and must be overridden.');
}
/**
* @inheritdoc
* @abstract
*/
redirect() {
throw new _GenericError2.default('The redirect() method is abstract and must be overridden.');
}
/**
* @inheritdoc
*/
link(routeName, params) {
let route = this._routes.get(routeName);
if (!route) {
throw new _GenericError2.default(`ima.router.AbstractRouter:link has undefined route with ` + `name ${routeName}. Add new route with that name.`);
}
return this.getBaseUrl() + route.toPath(params);
}
/**
* @inheritdoc
*/
route(path, options = {}, action) {
this._currentlyRoutedPath = path;
let routeForPath = this._getRouteByPath(path);
let params = {};
if (!routeForPath) {
params.error = new _GenericError2.default(`Route for path '${path}' is not configured.`, {
status: 404
});
return this.handleNotFound(params);
}
params = routeForPath.extractParameters(path);
return this._handle(routeForPath, params, options, action);
}
/**
* @inheritdoc
*/
handleError(params, options = {}) {
let routeError = this._routes.get(_RouteNames2.default.ERROR);
if (!routeError) {
let error = new _GenericError2.default(`ima.router.AbstractRouter:handleError cannot process the ` + `error because no error page route has been configured. Add ` + `a new route named '${_RouteNames2.default.ERROR}'.`, params);
return Promise.reject(error);
}
return this._handle(routeError, params, options, {
url: this.getUrl(),
type: _ActionTypes2.default.ERROR
});
}
/**
* @inheritdoc
*/
handleNotFound(params, options = {}) {
let routeNotFound = this._routes.get(_RouteNames2.default.NOT_FOUND);
if (!routeNotFound) {
let error = new _GenericError2.default(`ima.router.AbstractRouter:handleNotFound cannot processes ` + `a non-matching route because no not found page route has ` + `been configured. Add new route named ` + `'${_RouteNames2.default.NOT_FOUND}'.`, params);
return Promise.reject(error);
}
return this._handle(routeNotFound, params, options, {
url: this.getUrl(),
type: _ActionTypes2.default.ERROR
});
}
/**
* @inheritdoc
*/
isClientError(reason) {
return reason instanceof _GenericError2.default && reason.getHttpStatus() >= 400 && reason.getHttpStatus() < 500;
}
/**
* @inheritdoc
*/
isRedirection(reason) {
return reason instanceof _GenericError2.default && reason.getHttpStatus() >= 300 && reason.getHttpStatus() < 400;
}
/**
* Strips the URL path part that points to the application's root (base
* URL) from the provided path.
*
* @protected
* @param {string} path Relative or absolute URL path.
* @return {string} URL path relative to the application's base URL.
*/
_extractRoutePath(path) {
return path.replace(this._root + this._languagePartPath, '');
}
/**
* Handles the provided route and parameters by initializing the route's
* controller and rendering its state via the route's view.
*
* The result is then sent to the client if used at the server side, or
* displayed if used as the client side.
*
* @param {Route} route The route that should have its
* associated controller rendered via the associated view.
* @param {Object<string, (Error|string)>} params Parameters extracted from
* the URL path and query.
* @param {{
* onlyUpdate: (
* boolean|
* function(
* (string|function(new: Controller, ...*)),
* (string|function(
* new: React.Component,
* Object<string, *>,
* ?Object<string, *>
* ))
* ): boolean
* )=,
* autoScroll: boolean=,
* allowSPA: boolean=,
* documentView: ?AbstractDocumentView=,
* managedRootView: ?function(new: React.Component)=,
* viewAdapter: ?function(new: React.Component)=
* }} options The options overrides route options defined in the
* {@code routes.js} configuration file.
* @param {{ type: string, event: Event, url: string }} [action] An action
* object describing what triggered this routing.
* @return {Promise<Object<string, *>>} A promise that resolves when the
* page is rendered and the result is sent to the client, or
* displayed if used at the client side.
*/
_handle(route, params, options, action = {}) {
options = Object.assign({}, route.getOptions(), options);
const eventData = {
route,
params,
path: this._getCurrentlyRoutedPath(),
options,
action
};
this._dispatcher.fire(_Events2.default.BEFORE_HANDLE_ROUTE, eventData, true);
return this._pageManager.manage(route, options, params, action).then(response => {
response = response || {};
if (params.error && params.error instanceof Error) {
response.error = params.error;
}
eventData.response = response;
this._dispatcher.fire(_Events2.default.AFTER_HANDLE_ROUTE, eventData, true);
return response;
});
}
/**
* Returns the route matching the provided URL path part. The path may
* contain a query.
*
* @param {string} path The URL path.
* @return {?Route} The route matching the path, or {@code null} if no such
* route exists.
*/
_getRouteByPath(path) {
for (let route of this._routes.values()) {
if (route.matches(path)) {
return route;
}
}
return null;
}
/**
* Returns path that is stored in private property when a {@code route}
* method is called.
*
* @returns {string}
*/
_getCurrentlyRoutedPath() {
return this._currentlyRoutedPath;
}
}
exports.default = AbstractRouter;
typeof $IMA !== 'undefined' && $IMA !== null && $IMA.Loader && $IMA.Loader.register('ima/router/AbstractRouter', [], function (_export, _context) {
'use strict';
return {
setters: [],
execute: function () {
_export('default', exports.default);
}
};
});