1 | ;
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | /**
|
7 | * Regular expression matching all control characters used in regular
|
8 | * expressions. The regular expression is used to match these characters in
|
9 | * path expressions and replace them appropriately so the path expression can
|
10 | * be compiled to a regular expression.
|
11 | *
|
12 | * @const
|
13 | * @type {RegExp}
|
14 | */
|
15 | const CONTROL_CHARACTERS_REGEXP = /[\\.+*?^$[\](){}/'#]/g;
|
16 |
|
17 | /**
|
18 | * Regular expression used to match and remove the starting and trailing
|
19 | * forward slashes from a path expression or a URL path.
|
20 | *
|
21 | * @const
|
22 | * @type {RegExp}
|
23 | */
|
24 | const LOOSE_SLASHES_REGEXP = /^\/|\/$/g;
|
25 |
|
26 | /**
|
27 | * Regular expression used to match the parameter names from a path expression.
|
28 | *
|
29 | * @const
|
30 | * @type {RegExp}
|
31 | */
|
32 | const PARAMS_REGEXP_UNIVERSAL = /:\??([\w-]+)/g;
|
33 |
|
34 | /**
|
35 | * Regular expression used to match the required parameter names from a path expression.
|
36 | *
|
37 | * @const
|
38 | * @type {RegExp}
|
39 | */
|
40 | const PARAMS_REGEXP_REQUIRED = /(?:^|\\\/):([a-z0-9]+)(?=\\\/|$)/gi;
|
41 |
|
42 | /**
|
43 | * Regular expression used to separate a camelCase parameter name
|
44 | *
|
45 | * @const
|
46 | * @type {RegExp}
|
47 | */
|
48 | const PARAMS_REGEXP_CORE_NAME = /[a-z0-9]+/i;
|
49 |
|
50 | /**
|
51 | * Regular expression used to match start of parameter names from a path expression.
|
52 | *
|
53 | * @const
|
54 | * @type {String}
|
55 | */
|
56 | const PARAMS_START_PATTERN = '(^|/|[_-])';
|
57 |
|
58 | /**
|
59 | * Regular expression used to match end of parameter names from a path expression.
|
60 | *
|
61 | * @const
|
62 | * @type {String}
|
63 | */
|
64 | const PARAMS_END_PATTERN = '[/?_-]|$';
|
65 |
|
66 | /**
|
67 | * Regular expression used to never match the parameter names from a path expression.
|
68 | * It's used for wrong parameters order (optional vs. required ones)
|
69 | *
|
70 | * @const
|
71 | * @type {RegExp}
|
72 | */
|
73 | const PARAMS_NEVER_MATCH_REGEXP = /$a/;
|
74 |
|
75 | /**
|
76 | * Regular expression used to match all main parameter names from a path expression.
|
77 | *
|
78 | * @const
|
79 | * @type {RegExp}
|
80 | */
|
81 | const PARAMS_MAIN_REGEXP = /(?:\\\/|^):\\\?([a-z0-9]+)(?=\\\/|$)|(?:^|\\\/):([a-z0-9]+)(?=\\\/|$)/gi;
|
82 |
|
83 | /**
|
84 | * Regular expression used to match the required subparameter names from a path expression.
|
85 | * (e.g. for path '/:paramA-:paramB/:nextParam' are subparametres 'paramA' and 'paramB')
|
86 | *
|
87 | * @const
|
88 | * @type {Object<String, RegExp>}
|
89 | */
|
90 | const SUBPARAMS_REQUIRED_REGEXP = {
|
91 | LAST: /([_-]{1})((\w-)?:[a-z0-9]+)(?=\\\/|$)/gi,
|
92 | OTHERS: /(:[a-z0-9]+)(?=[_-]{1})/gi
|
93 | };
|
94 |
|
95 | /**
|
96 | * Regular expression used to match the optional parameter names from a path expression.
|
97 | *
|
98 | * @const
|
99 | * @type {Object<String, RegExp>}
|
100 | */
|
101 | const SUBPARAMS_OPT_REGEXP = {
|
102 | LAST: /([_-]{1}(\w-)?:\\\?[a-z0-9]+)(?=\\\/|$)/gi,
|
103 | OTHERS: /(:\\\?[a-z0-9]+)(?=[_-]{1}(\w-)?)/gi
|
104 | };
|
105 |
|
106 | /**
|
107 | * Regular expression used to match the parameter names from a path expression.
|
108 | *
|
109 | * @const
|
110 | * @type {RegExp}
|
111 | */
|
112 | const PARAMS_REGEXP_OPT = /(?:^:\\\?([a-z0-9]+)(?=\\\/|$))|(?:(\\\/):\\\?([a-z0-9]+)(?=\\\/|$))/gi; // last part: |(?::\\\?([a-z0-9]+)(?=\\\/|$))
|
113 |
|
114 | /**
|
115 | * Utility for representing and manipulating a single route in the router's
|
116 | * configuration.
|
117 | */
|
118 | class Route {
|
119 | /**
|
120 | * Initializes the route.
|
121 | *
|
122 | * @param {string} name The unique name of this route, identifying it among
|
123 | * the rest of the routes in the application.
|
124 | * @param {string} pathExpression A path expression specifying the URL path
|
125 | * part matching this route (must not contain a query string),
|
126 | * optionally containing named parameter placeholders specified as
|
127 | * {@code :parameterName}.
|
128 | * @param {string} controller The full name of Object Container alias
|
129 | * identifying the controller associated with this route.
|
130 | * @param {string} view The full name or Object Container alias identifying
|
131 | * the view class associated with this route.
|
132 | * @param {{
|
133 | * onlyUpdate: (
|
134 | * boolean|
|
135 | * function(
|
136 | * (string|function(new: Controller, ...*)),
|
137 | * (string|function(
|
138 | * new: React.Component,
|
139 | * Object<string, *>,
|
140 | * ?Object<string, *>
|
141 | * ))
|
142 | * ): boolean
|
143 | * )=,
|
144 | * autoScroll: boolean=,
|
145 | * allowSPA: boolean=,
|
146 | * documentView: ?AbstractDocumentView=,
|
147 | * managedRootView: ?function(new: React.Component)=,
|
148 | * viewAdapter: ?function(new: React.Component)=
|
149 | * }} options The route additional options.
|
150 | */
|
151 | constructor(name, pathExpression, controller, view, options) {
|
152 | /**
|
153 | * The unique name of this route, identifying it among the rest of the
|
154 | * routes in the application.
|
155 | *
|
156 | * @type {string}
|
157 | */
|
158 | this._name = name;
|
159 |
|
160 | /**
|
161 | * The original URL path expression from which this route was created.
|
162 | *
|
163 | * @type {string}
|
164 | */
|
165 | this._pathExpression = pathExpression;
|
166 |
|
167 | /**
|
168 | * The full name of Object Container alias identifying the controller
|
169 | * associated with this route.
|
170 | *
|
171 | * @type {string}
|
172 | */
|
173 | this._controller = controller;
|
174 |
|
175 | /**
|
176 | * The full name or Object Container alias identifying the view class
|
177 | * associated with this route.
|
178 | *
|
179 | * @type {React.Component}
|
180 | */
|
181 | this._view = view;
|
182 |
|
183 | /**
|
184 | * The route additional options.
|
185 | *
|
186 | * @type {{
|
187 | * onlyUpdate: (
|
188 | * boolean|
|
189 | * function(
|
190 | * (string|function(new: Controller, ...*)),
|
191 | * (string|function(
|
192 | * new: React.Component,
|
193 | * Object<string, *>,
|
194 | * ?Object<string, *>
|
195 | * ))
|
196 | * ): boolean
|
197 | * ),
|
198 | * autoScroll: boolean,
|
199 | * allowSPA: boolean,
|
200 | * documentView: ?function(new: AbstractDocumentView),
|
201 | * managedRootView: ?function(new: React.Component),
|
202 | * viewAdapter: ?function(new: React.Component)
|
203 | * }}
|
204 | */
|
205 | this._options = Object.assign({
|
206 | onlyUpdate: false,
|
207 | autoScroll: true,
|
208 | allowSPA: true,
|
209 | documentView: null,
|
210 | managedRootView: null,
|
211 | viewAdapter: null
|
212 | }, options);
|
213 |
|
214 | /**
|
215 | * The path expression with the trailing slashes trimmed.
|
216 | *
|
217 | * @type {string}
|
218 | */
|
219 | this._trimmedPathExpression = this._getTrimmedPath(pathExpression);
|
220 |
|
221 | /**
|
222 | * The names of the parameters in this route.
|
223 | *
|
224 | * @type {string[]}
|
225 | */
|
226 | this._parameterNames = this._getParameterNames(pathExpression);
|
227 |
|
228 | /**
|
229 | * Set to {@code true} if this route contains parameters in its path.
|
230 | *
|
231 | * @type {boolean}
|
232 | */
|
233 | this._hasParameters = !!this._parameterNames.length;
|
234 |
|
235 | /**
|
236 | * A regexp used to match URL path against this route and extract the
|
237 | * parameter values from the matched URL paths.
|
238 | *
|
239 | * @type {RegExp}
|
240 | */
|
241 | this._matcher = this._compileToRegExp(this._trimmedPathExpression);
|
242 | }
|
243 |
|
244 | /**
|
245 | * Creates the URL and query parts of a URL by substituting the route's
|
246 | * parameter placeholders by the provided parameter value.
|
247 | *
|
248 | * The extraneous parameters that do not match any of the route's
|
249 | * placeholders will be appended as the query string.
|
250 | *
|
251 | * @param {Object<string, (number|string)>=} [params={}] The route
|
252 | * parameter values.
|
253 | * @return {string} Path and, if necessary, query parts of the URL
|
254 | * representing this route with its parameters replaced by the
|
255 | * provided parameter values.
|
256 | */
|
257 | toPath(params = {}) {
|
258 | let path = this._pathExpression;
|
259 | let query = [];
|
260 |
|
261 | for (let paramName of Object.keys(params)) {
|
262 | if (this._isRequiredParamInPath(path, paramName)) {
|
263 | path = this._substituteRequiredParamInPath(path, paramName, params[paramName]);
|
264 | } else if (this._isOptionalParamInPath(path, paramName)) {
|
265 | path = this._substituteOptionalParamInPath(path, paramName, params[paramName]);
|
266 | } else {
|
267 | const pair = [paramName, params[paramName]];
|
268 | query.push(pair.map(encodeURIComponent).join('='));
|
269 | }
|
270 | }
|
271 | path = this._cleanUnusedOptionalParams(path);
|
272 |
|
273 | path = query.length ? path + '?' + query.join('&') : path;
|
274 | path = this._getTrimmedPath(path);
|
275 | return path;
|
276 | }
|
277 |
|
278 | /**
|
279 | * Returns the unique identifying name of this route.
|
280 | *
|
281 | * @return {string} The name of the route, identifying it.
|
282 | */
|
283 | getName() {
|
284 | return this._name;
|
285 | }
|
286 |
|
287 | /**
|
288 | * Returns the full name of the controller to use when this route is
|
289 | * matched by the current URL, or an Object Container-registered alias of
|
290 | * the controller.
|
291 | *
|
292 | * @return {string} The name of alias of the controller.
|
293 | */
|
294 | getController() {
|
295 | return this._controller;
|
296 | }
|
297 |
|
298 | /**
|
299 | * Returns the full name of the view class or an Object
|
300 | * Container-registered alias for the view class, representing the view to
|
301 | * use when this route is matched by the current URL.
|
302 | *
|
303 | * @return {string} The name or alias of the view class.
|
304 | */
|
305 | getView() {
|
306 | return this._view;
|
307 | }
|
308 |
|
309 | /**
|
310 | * Return route additional options.
|
311 | *
|
312 | * @return {{
|
313 | * onlyUpdate: (
|
314 | * boolean|
|
315 | * function(
|
316 | * (string|function(new: Controller, ...*)),
|
317 | * (string|function(
|
318 | * new: React.Component,
|
319 | * Object<string, *>,
|
320 | * ?Object<string, *>
|
321 | * ))
|
322 | * ): boolean
|
323 | * ),
|
324 | * autoScroll: boolean,
|
325 | * allowSPA: boolean,
|
326 | * documentView: ?AbstractDocumentView,
|
327 | * managedRootView: ?function(new: React.Component),
|
328 | * viewAdapter: ?function(new: React.Component)
|
329 | * }}
|
330 | */
|
331 | getOptions() {
|
332 | return this._options;
|
333 | }
|
334 |
|
335 | /**
|
336 | * Returns the path expression, which is the parametrized pattern matching
|
337 | * the URL paths matched by this route.
|
338 | *
|
339 | * @return {string} The path expression.
|
340 | */
|
341 | getPathExpression() {
|
342 | return this._pathExpression;
|
343 | }
|
344 |
|
345 | /**
|
346 | * Tests whether the provided URL path matches this route. The provided
|
347 | * path may contain the query.
|
348 | *
|
349 | * @param {string} path The URL path.
|
350 | * @return {boolean} {@code true} if the provided path matches this route.
|
351 | */
|
352 | matches(path) {
|
353 | let trimmedPath = this._getTrimmedPath(path);
|
354 | return this._matcher.test(trimmedPath);
|
355 | }
|
356 |
|
357 | /**
|
358 | * Extracts the parameter values from the provided path. The method
|
359 | * extracts both the in-path parameters and parses the query, allowing the
|
360 | * query parameters to override the in-path parameters.
|
361 | *
|
362 | * The method returns an empty hash object if the path does not match this
|
363 | * route.
|
364 | *
|
365 | * @param {string} path
|
366 | * @return {Object<string, ?string>} Map of parameter names to parameter
|
367 | * values.
|
368 | */
|
369 | extractParameters(path) {
|
370 | let trimmedPath = this._getTrimmedPath(path);
|
371 | let parameters = this._getParameters(trimmedPath);
|
372 | let query = this._getQuery(trimmedPath);
|
373 |
|
374 | return Object.assign({}, parameters, query);
|
375 | }
|
376 |
|
377 | /**
|
378 | * Replace required parameter placeholder in path with parameter value.
|
379 | *
|
380 | * @param {string} path
|
381 | * @param {string} paramName
|
382 | * @param {string} paramValue
|
383 | * @return {string} New path.
|
384 | */
|
385 | _substituteRequiredParamInPath(path, paramName, paramValue) {
|
386 | return path.replace(new RegExp(`${PARAMS_START_PATTERN}:${paramName}(${PARAMS_END_PATTERN})`), paramValue ? '$1' + encodeURIComponent(paramValue) + '$2' : '');
|
387 | }
|
388 |
|
389 | /**
|
390 | * Replace optional param placeholder in path with parameter value.
|
391 | *
|
392 | * @param {string} path
|
393 | * @param {string} paramName
|
394 | * @param {string} paramValue
|
395 | * @return {string} New path.
|
396 | */
|
397 | _substituteOptionalParamInPath(path, paramName, paramValue) {
|
398 | const paramRegexp = `${PARAMS_START_PATTERN}:\\?${paramName}(${PARAMS_END_PATTERN})`;
|
399 | return path.replace(new RegExp(paramRegexp), paramValue ? '$1' + encodeURIComponent(paramValue) + '$2' : '/');
|
400 | }
|
401 |
|
402 | /**
|
403 | * Remove unused optional param placeholders in path.
|
404 | *
|
405 | * @param {string} path
|
406 | * @return {string} New path.
|
407 | */
|
408 | _cleanUnusedOptionalParams(path) {
|
409 | let replacedPath = path;
|
410 |
|
411 | // remove last subparameters
|
412 | replacedPath = replacedPath.replace(/([_-])(:\?([a-z0-9]+))(?=\/)/gi, '$1');
|
413 |
|
414 | // remove parameters
|
415 | replacedPath = replacedPath.replace(/(\/:\?([a-z0-9]+))|(:\?([a-z0-9]+)\/?)/gi, '');
|
416 |
|
417 | return replacedPath;
|
418 | }
|
419 |
|
420 | /**
|
421 | * Returns true, if paramName is placed in path.
|
422 | *
|
423 | * @param {string} path
|
424 | * @param {string} paramName
|
425 | * @return {boolean}
|
426 | */
|
427 | _isOptionalParamInPath(path, paramName) {
|
428 | const paramRegexp = `${PARAMS_START_PATTERN}:\\?${paramName}(?:${PARAMS_END_PATTERN})`;
|
429 | let regexp = new RegExp(paramRegexp);
|
430 | return regexp.test(path);
|
431 | }
|
432 |
|
433 | /**
|
434 | * Returns true, if paramName is placed in path and it's required.
|
435 | *
|
436 | * @param {string} path
|
437 | * @param {string} paramName
|
438 | * @return {boolean}
|
439 | */
|
440 | _isRequiredParamInPath(path, paramName) {
|
441 | let regexp = new RegExp(`:${paramName}`);
|
442 |
|
443 | return regexp.test(path);
|
444 | }
|
445 |
|
446 | /**
|
447 | * Extract clear parameter name, e.q. '?name' or 'name'
|
448 | *
|
449 | * @param {string} rawParam
|
450 | * @return {string}
|
451 | */
|
452 | _getClearParamName(rawParam) {
|
453 | const regExpr = /\??[a-z0-9]+/i;
|
454 | const paramMatches = rawParam.match(regExpr);
|
455 | const param = paramMatches ? paramMatches[0] : '';
|
456 |
|
457 | return param;
|
458 | }
|
459 |
|
460 | /**
|
461 | * Get pattern for subparameter.
|
462 | *
|
463 | * @param {string} delimeter Parameters delimeter
|
464 | * @return {string}
|
465 | */
|
466 | _getSubparamPattern(delimeter) {
|
467 | const pattern = `([^${delimeter}?/]+)`;
|
468 |
|
469 | return pattern;
|
470 | }
|
471 |
|
472 | /**
|
473 | * Check if all optional params are below required ones
|
474 | *
|
475 | * @param {array<string>} allMainParams
|
476 | * @return {boolean}
|
477 | */
|
478 | _checkOptionalParamsOrder(allMainParams) {
|
479 | let optionalLastId = -1;
|
480 |
|
481 | const count = allMainParams.length;
|
482 | for (let idx = 0; idx < count; idx++) {
|
483 | const item = allMainParams[idx];
|
484 |
|
485 | if (item.substr(0, 1) === '?') {
|
486 | optionalLastId = idx;
|
487 | } else {
|
488 | if (optionalLastId > -1 && idx > optionalLastId) {
|
489 | return false;
|
490 | }
|
491 | }
|
492 | }
|
493 |
|
494 | return true;
|
495 | }
|
496 |
|
497 | /**
|
498 | * Check if main parametres have correct order.
|
499 | * It means that required param cannot follow optional one.
|
500 | *
|
501 | * @param {string} clearedPathExpr The cleared URL path (removed first and last slash, ...).
|
502 | * @return {Bool} Returns TRUE if order is correct.
|
503 | */
|
504 | _checkParametersOrder(clearedPathExpr) {
|
505 | const mainParamsMatches = clearedPathExpr.match(PARAMS_MAIN_REGEXP) || [];
|
506 | const allMainParamsCleared = mainParamsMatches.map(paramExpr => this._getClearParamName(paramExpr));
|
507 |
|
508 | const isCorrectParamOrder = this._checkOptionalParamsOrder(allMainParamsCleared);
|
509 | return isCorrectParamOrder;
|
510 | }
|
511 |
|
512 | /**
|
513 | * Convert main optional parameters to capture sequences
|
514 | *
|
515 | * @param {string} path The URL path.
|
516 | * @param {array<string>} optionalParams List of main optimal parameter expressions
|
517 | * @return {string} RegExp pattern.
|
518 | */
|
519 | _replaceOptionalParametersInPath(path, optionalParams) {
|
520 | const pattern = optionalParams.reduce((path, paramExpr, idx, matches) => {
|
521 | const lastIdx = matches.length - 1;
|
522 | const hasSlash = paramExpr.substr(0, 2) === '\\/';
|
523 |
|
524 | let separator = '';
|
525 |
|
526 | if (idx === 0) {
|
527 | separator = '(?:' + (hasSlash ? '/' : '');
|
528 | } else {
|
529 | separator = hasSlash ? '/?' : '';
|
530 | }
|
531 |
|
532 | let regExpr = separator + `([^/?]+)?(?=/|$)?`;
|
533 |
|
534 | if (idx === lastIdx) {
|
535 | regExpr += ')?';
|
536 | }
|
537 |
|
538 | return path.replace(paramExpr, regExpr);
|
539 | }, path);
|
540 |
|
541 | return pattern;
|
542 | }
|
543 |
|
544 | /**
|
545 | * Convert required subparameters to capture sequences
|
546 | *
|
547 | * @param {string} path The URL path (route definition).
|
548 | * @param {string} clearedPathExpr The original cleared URL path.
|
549 | * @return {string} RegExp pattern.
|
550 | */
|
551 | _replaceRequiredSubParametersInPath(path, clearedPathExpr) {
|
552 | const requiredSubparamsOthers = clearedPathExpr.match(SUBPARAMS_REQUIRED_REGEXP.OTHERS) || [];
|
553 | const requiredSubparamsLast = clearedPathExpr.match(SUBPARAMS_REQUIRED_REGEXP.LAST) || [];
|
554 |
|
555 | path = requiredSubparamsOthers.reduce((pattern, paramExpr) => {
|
556 | const paramIdx = pattern.indexOf(paramExpr) + paramExpr.length;
|
557 | const delimeter = pattern.substr(paramIdx, 1);
|
558 |
|
559 | const regExpr = this._getSubparamPattern(delimeter);
|
560 |
|
561 | return pattern.replace(paramExpr, regExpr);
|
562 | }, path);
|
563 |
|
564 | path = requiredSubparamsLast.reduce((pattern, rawParamExpr) => {
|
565 | const paramExpr = rawParamExpr.substr(1);
|
566 | const regExpr = '([^/?]+)';
|
567 |
|
568 | return pattern.replace(paramExpr, regExpr);
|
569 | }, path);
|
570 |
|
571 | return path;
|
572 | }
|
573 |
|
574 | /**
|
575 | * Convert optional subparameters to capture sequences
|
576 | *
|
577 | * @param {string} path The URL path (route definition).
|
578 | * @param {array<string>} optionalSubparamsOthers List of all subparam. expressions but last ones
|
579 | * @param {array<string>} optionalSubparamsLast List of last subparam. expressions
|
580 | * @return {string} RegExp pattern.
|
581 | */
|
582 | _replaceOptionalSubParametersInPath(path, optionalSubparamsOthers, optionalSubparamsLast) {
|
583 | path = optionalSubparamsOthers.reduce((pattern, paramExpr) => {
|
584 | const paramIdx = pattern.indexOf(paramExpr) + paramExpr.length;
|
585 | const delimeter = pattern.substr(paramIdx, 1);
|
586 | const paramPattern = this._getSubparamPattern(delimeter);
|
587 | const regExpr = paramPattern + '?';
|
588 |
|
589 | return pattern.replace(paramExpr, regExpr);
|
590 | }, path);
|
591 |
|
592 | path = optionalSubparamsLast.reduce((pattern, rawParamExpr) => {
|
593 | const paramExpr = rawParamExpr.substr(1);
|
594 | const regExpr = '([^/?]+)?';
|
595 |
|
596 | return pattern.replace(paramExpr, regExpr);
|
597 | }, path);
|
598 |
|
599 | return path;
|
600 | }
|
601 |
|
602 | /**
|
603 | * Compiles the path expression to a regular expression that can be used
|
604 | * for easier matching of URL paths against this route, and extracting the
|
605 | * path parameter values from the URL path.
|
606 | *
|
607 | * @param {string} pathExpression The path expression to compile.
|
608 | * @return {RegExp} The compiled regular expression.
|
609 | */
|
610 | _compileToRegExp(pathExpression) {
|
611 | const clearedPathExpr = pathExpression.replace(LOOSE_SLASHES_REGEXP, '').replace(CONTROL_CHARACTERS_REGEXP, '\\$&');
|
612 |
|
613 | const requiredMatches = clearedPathExpr.match(PARAMS_REGEXP_REQUIRED) || [];
|
614 | const optionalMatches = clearedPathExpr.match(PARAMS_REGEXP_OPT) || [];
|
615 |
|
616 | const optionalSubparamsLast = clearedPathExpr.match(SUBPARAMS_OPT_REGEXP.LAST) || [];
|
617 | const optionalSubparamsOthers = clearedPathExpr.match(SUBPARAMS_OPT_REGEXP.OTHERS) || [];
|
618 | const optionalSubparams = [...optionalSubparamsOthers, ...optionalSubparamsLast];
|
619 |
|
620 | const optionalSubparamsCleanNames = optionalSubparams.map(paramExpr => {
|
621 | return this._getClearParamName(paramExpr);
|
622 | });
|
623 |
|
624 | const optionalParams = optionalMatches.filter(paramExpr => {
|
625 | const param = this._getClearParamName(paramExpr);
|
626 |
|
627 | return !optionalSubparamsCleanNames.includes(param);
|
628 | });
|
629 |
|
630 | if (!!requiredMatches.length && !!optionalParams.length) {
|
631 | const isCorrectParamOrder = this._checkParametersOrder(clearedPathExpr);
|
632 |
|
633 | if (!isCorrectParamOrder) {
|
634 | return PARAMS_NEVER_MATCH_REGEXP;
|
635 | }
|
636 | }
|
637 |
|
638 | // convert required parameters to capture sequences
|
639 | let pattern = requiredMatches.reduce((pattern, rawParamExpr) => {
|
640 | const paramExpr = ':' + this._getClearParamName(rawParamExpr);
|
641 | const regExpr = '([^/?]+)';
|
642 |
|
643 | return pattern.replace(paramExpr, regExpr);
|
644 | }, clearedPathExpr);
|
645 |
|
646 | pattern = this._replaceOptionalParametersInPath(pattern, optionalParams);
|
647 | pattern = this._replaceRequiredSubParametersInPath(pattern, clearedPathExpr);
|
648 | pattern = this._replaceOptionalSubParametersInPath(pattern, optionalSubparamsOthers, optionalSubparamsLast);
|
649 |
|
650 | // add path root
|
651 | pattern = '^\\/' + pattern;
|
652 |
|
653 | // add query parameters matcher
|
654 | let pairPattern = '[^=&;]*(?:=[^&;]*)?';
|
655 | pattern += `(?:\\?(?:${pairPattern})(?:[&;]${pairPattern})*)?$`;
|
656 |
|
657 | return new RegExp(pattern);
|
658 | }
|
659 |
|
660 | /**
|
661 | * Parses the provided path and extract the in-path parameters. The method
|
662 | * decodes the parameters and returns them in a hash object.
|
663 | *
|
664 | * @param {string} path The URL path.
|
665 | * @return {Object<string, string>} The parsed path parameters.
|
666 | */
|
667 | _getParameters(path) {
|
668 | if (!this._hasParameters) {
|
669 | return {};
|
670 | }
|
671 |
|
672 | let parameterValues = path.match(this._matcher);
|
673 | if (!parameterValues) {
|
674 | return {};
|
675 | }
|
676 |
|
677 | parameterValues.shift(); // remove the match on whole path, and other parts
|
678 |
|
679 | return this._extractParameters(parameterValues);
|
680 | }
|
681 |
|
682 | /**
|
683 | * Extract parameters from given path.
|
684 | *
|
685 | * @param {string[]} parameterValues
|
686 | * @return {Object<string, ?string>} Params object.
|
687 | */
|
688 | _extractParameters(parameterValues) {
|
689 | let parameters = {};
|
690 |
|
691 | const parametersCount = this._parameterNames.length;
|
692 |
|
693 | // Cycle for names and values from last to 0
|
694 | for (let i = parametersCount - 1; i >= 0; i--) {
|
695 | let [rawName, rawValue] = [this._parameterNames[i], parameterValues[i]];
|
696 | let cleanParamName = this._cleanOptParamName(rawName);
|
697 |
|
698 | const matchesName = cleanParamName.match(PARAMS_REGEXP_CORE_NAME);
|
699 | const currentCoreName = matchesName ? matchesName[0] : '';
|
700 |
|
701 | if (currentCoreName) {
|
702 | const value = this._decodeURIParameter(rawValue);
|
703 | parameters[currentCoreName] = value;
|
704 | }
|
705 | }
|
706 |
|
707 | return parameters;
|
708 | }
|
709 |
|
710 | /**
|
711 | * Decoding parameters.
|
712 | *
|
713 | * @param {string} parameterValue
|
714 | * @return {string} decodedValue
|
715 | */
|
716 | _decodeURIParameter(parameterValue) {
|
717 | let decodedValue;
|
718 | if (parameterValue) {
|
719 | decodedValue = decodeURIComponent(parameterValue);
|
720 | }
|
721 | return decodedValue;
|
722 | }
|
723 |
|
724 | /**
|
725 | * Returns optional param name without "?"
|
726 | *
|
727 | * @param {string} paramName Full param name with "?"
|
728 | * @return {string} Strict param name without "?"
|
729 | */
|
730 | _cleanOptParamName(paramName) {
|
731 | return paramName.replace('?', '');
|
732 | }
|
733 |
|
734 | /**
|
735 | * Checks if parameter is optional or not.
|
736 | *
|
737 | * @param {string} paramName
|
738 | * @return {boolean} return true if is optional, otherwise false
|
739 | */
|
740 | _isParamOptional(paramName) {
|
741 | return (/\?.+/.test(paramName)
|
742 | );
|
743 | }
|
744 |
|
745 | /**
|
746 | * Extracts and decodes the query parameters from the provided URL path and
|
747 | * query.
|
748 | *
|
749 | * @param {string} path The URL path, including the optional query string
|
750 | * (if any).
|
751 | * @return {Object<string, ?string>} Parsed query parameters.
|
752 | */
|
753 | _getQuery(path) {
|
754 | let query = {};
|
755 | let queryStart = path.indexOf('?');
|
756 | let hasQuery = queryStart > -1 && queryStart !== path.length - 1;
|
757 |
|
758 | if (hasQuery) {
|
759 | let pairs = path.substring(queryStart + 1).split(/[&;]/);
|
760 |
|
761 | for (let parameterPair of pairs) {
|
762 | let pair = parameterPair.split('=');
|
763 | query[decodeURIComponent(pair[0])] = pair.length > 1 ? decodeURIComponent(pair[1]) : true;
|
764 | }
|
765 | }
|
766 |
|
767 | return query;
|
768 | }
|
769 |
|
770 | /**
|
771 | * Trims the trailing forward slash from the provided URL path.
|
772 | *
|
773 | * @param {string} path The path to trim.
|
774 | * @return {string} Trimmed path.
|
775 | */
|
776 | _getTrimmedPath(path) {
|
777 | return `/${path.replace(LOOSE_SLASHES_REGEXP, '')}`;
|
778 | }
|
779 |
|
780 | /**
|
781 | * Extracts the parameter names from the provided path expression.
|
782 | *
|
783 | * @param {string} pathExpression The path expression.
|
784 | * @return {string[]} The names of the parameters defined in the provided
|
785 | * path expression.
|
786 | */
|
787 | _getParameterNames(pathExpression) {
|
788 | let rawNames = pathExpression.match(PARAMS_REGEXP_UNIVERSAL) || [];
|
789 |
|
790 | return rawNames.map(rawParameterName => {
|
791 | return rawParameterName.substring(1).replace('?', '');
|
792 | });
|
793 | }
|
794 | }
|
795 | exports.default = Route;
|
796 |
|
797 | typeof $IMA !== 'undefined' && $IMA !== null && $IMA.Loader && $IMA.Loader.register('ima/router/Route', [], function (_export, _context) {
|
798 | ;
|
799 | return {
|
800 | setters: [],
|
801 | execute: function () {
|
802 | _export('default', exports.default);
|
803 | }
|
804 | };
|
805 | });
|