UNPKG

10.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _Events = require('./Events');
8
9var _Events2 = _interopRequireDefault(_Events);
10
11var _Router = require('./Router');
12
13var _Router2 = _interopRequireDefault(_Router);
14
15var _RouteNames = require('./RouteNames');
16
17var _RouteNames2 = _interopRequireDefault(_RouteNames);
18
19var _GenericError = require('../error/GenericError');
20
21var _GenericError2 = _interopRequireDefault(_GenericError);
22
23function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
25/**
26 * The basic implementation of the {@codelink Router} interface, providing the
27 * common or default functionality for parts of the API.
28 *
29 * @abstract
30 */
31class AbstractRouter extends _Router2.default {
32 /**
33 * Initializes the router.
34 *
35 * @param {PageManager} pageManager The page manager handling UI rendering,
36 * and transitions between pages if at the client side.
37 * @param {RouteFactory} factory Factory for routes.
38 * @param {Dispatcher} dispatcher Dispatcher fires events to app.
39 * @example
40 * router.link('article', {articleId: 1});
41 * @example
42 * router.redirect('http://www.example.com/web');
43 * @example
44 * router.add(
45 * 'home',
46 * '/',
47 * ns.app.page.home.Controller,
48 * ns.app.page.home.View,
49 * {
50 * onlyUpdate: false,
51 * autoScroll: true,
52 * allowSPA: true,
53 * documentView: null,
54 * managedRootView: null,
55 * viewAdapter: null
56 * }
57 * );
58 */
59 constructor(pageManager, factory, dispatcher) {
60 super();
61
62 /**
63 * The page manager handling UI rendering, and transitions between
64 * pages if at the client side.
65 *
66 * @type {PageManager}
67 */
68 this._pageManager = pageManager;
69
70 /**
71 * Factory for routes.
72 *
73 * @type {RouteFactory}
74 */
75 this._factory = factory;
76
77 /**
78 * Dispatcher fires events to app.
79 *
80 * @type {Dispatcher}
81 */
82 this._dispatcher = dispatcher;
83
84 /**
85 * The current protocol used to access the application, terminated by a
86 * colon (for example {@code https:}).
87 *
88 * @type {string}
89 */
90 this._protocol = '';
91
92 /**
93 * The application's host.
94 *
95 * @type {string}
96 */
97 this._host = '';
98
99 /**
100 * The URL path pointing to the application's root.
101 *
102 * @type {string}
103 */
104 this._root = '';
105
106 /**
107 * The URL path fragment used as a suffix to the {@code _root} field
108 * that specifies the current language.
109 *
110 * @type {string}
111 */
112 this._languagePartPath = '';
113
114 /**
115 * Storage of all known routes. The key are the route names.
116 *
117 * @type {Map<string, Route>}
118 */
119 this._routes = new Map();
120 }
121
122 /**
123 * @inheritdoc
124 */
125 init(config) {
126 this._protocol = config.$Protocol || '';
127 this._root = config.$Root || '';
128 this._languagePartPath = config.$LanguagePartPath || '';
129 this._host = config.$Host;
130 }
131
132 /**
133 * @inheritdoc
134 */
135 add(name, pathExpression, controller, view, options = undefined) {
136 if (this._routes.has(name)) {
137 throw new _GenericError2.default(`ima.router.AbstractRouter.add: The route with name ${name} ` + `is already defined`);
138 }
139
140 let factory = this._factory;
141 let route = factory.createRoute(name, pathExpression, controller, view, options);
142 this._routes.set(name, route);
143
144 return this;
145 }
146
147 /**
148 * @inheritdoc
149 */
150 remove(name) {
151 this._routes.delete(name);
152
153 return this;
154 }
155
156 /**
157 * @inheritdoc
158 */
159 getPath() {
160 throw new _GenericError2.default('The getPath() method is abstract and must be overridden.');
161 }
162
163 /**
164 * @inheritdoc
165 */
166 getUrl() {
167 return this.getBaseUrl() + this.getPath();
168 }
169
170 /**
171 * @inheritdoc
172 */
173 getBaseUrl() {
174 return this.getDomain() + this._root + this._languagePartPath;
175 }
176
177 /**
178 * @inheritdoc
179 */
180 getDomain() {
181 return this._protocol + '//' + this._host;
182 }
183
184 /**
185 * @inheritdoc
186 */
187 getHost() {
188 return this._host;
189 }
190
191 /**
192 * @inheritdoc
193 */
194 getProtocol() {
195 return this._protocol;
196 }
197
198 /**
199 * @inheritdoc
200 */
201 getCurrentRouteInfo() {
202 let path = this.getPath();
203 let route = this._getRouteByPath(path);
204
205 if (!route) {
206 throw new _GenericError2.default(`ima.router.AbstractRouter.getCurrentRouteInfo: The route ` + `for path ${path} is not defined.`);
207 }
208
209 let params = route.extractParameters(path);
210
211 return { route, params, path };
212 }
213
214 /**
215 * @inheritdoc
216 * @abstract
217 */
218 listen() {
219 throw new _GenericError2.default('The listen() method is abstract and must be overridden.');
220 }
221
222 /**
223 * @inheritdoc
224 * @abstract
225 */
226 redirect() {
227 throw new _GenericError2.default('The redirect() method is abstract and must be overridden.');
228 }
229
230 /**
231 * @inheritdoc
232 */
233 link(routeName, params) {
234 let route = this._routes.get(routeName);
235
236 if (!route) {
237 throw new _GenericError2.default(`ima.router.AbstractRouter:link has undefined route with ` + `name ${routeName}. Add new route with that name.`);
238 }
239
240 return this.getBaseUrl() + route.toPath(params);
241 }
242
243 /**
244 * @inheritdoc
245 */
246 route(path, options = {}) {
247 let routeForPath = this._getRouteByPath(path);
248 let params = {};
249
250 if (!routeForPath) {
251 params.error = new _GenericError2.default(`Route for path '${path}' is not configured.`, { status: 404 });
252
253 return this.handleNotFound(params);
254 }
255
256 params = routeForPath.extractParameters(path);
257
258 return this._handle(routeForPath, params, options);
259 }
260
261 /**
262 * @inheritdoc
263 */
264 handleError(params, options = {}) {
265 let routeError = this._routes.get(_RouteNames2.default.ERROR);
266
267 if (!routeError) {
268 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);
269
270 return Promise.reject(error);
271 }
272
273 return this._handle(routeError, params, options);
274 }
275
276 /**
277 * @inheritdoc
278 */
279 handleNotFound(params, options = {}) {
280 let routeNotFound = this._routes.get(_RouteNames2.default.NOT_FOUND);
281
282 if (!routeNotFound) {
283 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);
284
285 return Promise.reject(error);
286 }
287
288 return this._handle(routeNotFound, params, options);
289 }
290
291 /**
292 * @inheritdoc
293 */
294 isClientError(reason) {
295 return reason instanceof _GenericError2.default && reason.getHttpStatus() >= 400 && reason.getHttpStatus() < 500;
296 }
297
298 /**
299 * @inheritdoc
300 */
301 isRedirection(reason) {
302 return reason instanceof _GenericError2.default && reason.getHttpStatus() >= 300 && reason.getHttpStatus() < 400;
303 }
304
305 /**
306 * Strips the URL path part that points to the application's root (base
307 * URL) from the provided path.
308 *
309 * @protected
310 * @param {string} path Relative or absolute URL path.
311 * @return {string} URL path relative to the application's base URL.
312 */
313 _extractRoutePath(path) {
314 return path.replace(this._root + this._languagePartPath, '');
315 }
316
317 /**
318 * Handles the provided route and parameters by initializing the route's
319 * controller and rendering its state via the route's view.
320 *
321 * The result is then sent to the client if used at the server side, or
322 * displayed if used as the client side.
323 *
324 * @param {Route} route The route that should have its
325 * associated controller rendered via the associated view.
326 * @param {Object<string, (Error|string)>} params Parameters extracted from
327 * the URL path and query.
328 * @param {{
329 * onlyUpdate: (
330 * boolean|
331 * function(
332 * (string|function(new: Controller, ...*)),
333 * (string|function(
334 * new: React.Component,
335 * Object<string, *>,
336 * ?Object<string, *>
337 * ))
338 * ): boolean
339 * )=,
340 * autoScroll: boolean=,
341 * allowSPA: boolean=,
342 * documentView: ?AbstractDocumentView=,
343 * managedRootView: ?function(new: React.Component)=,
344 * viewAdapter: ?function(new: React.Component)=
345 * }} options The options overrides route options defined in the
346 * {@code routes.js} configuration file.
347 * @return {Promise<Object<string, *>>} A promise that resolves when the
348 * page is rendered and the result is sent to the client, or
349 * displayed if used at the client side.
350 */
351 _handle(route, params, options) {
352 let controller = route.getController();
353 let view = route.getView();
354 options = Object.assign({}, route.getOptions(), options);
355 let data = { route, params, path: this.getPath(), options };
356
357 this._dispatcher.fire(_Events2.default.BEFORE_HANDLE_ROUTE, data, true);
358
359 return this._pageManager.manage(controller, view, options, params).then(response => {
360 response = response || {};
361
362 if (params.error && params.error instanceof Error) {
363 response.error = params.error;
364 }
365
366 data.response = response;
367
368 this._dispatcher.fire(_Events2.default.AFTER_HANDLE_ROUTE, data, true);
369
370 return response;
371 });
372 }
373
374 /**
375 * Returns the route matching the provided URL path part. The path may
376 * contain a query.
377 *
378 * @param {string} path The URL path.
379 * @return {?Route} The route matching the path, or {@code null} if no such
380 * route exists.
381 */
382 _getRouteByPath(path) {
383 for (let route of this._routes.values()) {
384 if (route.matches(path)) {
385 return route;
386 }
387 }
388
389 return null;
390 }
391}
392exports.default = AbstractRouter;
393
394typeof $IMA !== 'undefined' && $IMA !== null && $IMA.Loader && $IMA.Loader.register('ima/router/AbstractRouter', [], function (_export, _context) {
395 'use strict';
396 return {
397 setters: [],
398 execute: function () {
399 _export('default', exports.default);
400 }
401 };
402});