UNPKG

21.1 kBJavaScriptView Raw
1import { OpaqueToken } from '@angular/core';
2import { isArray, isBlank, isPresent } from '../util/util';
3/**
4 * @hidden
5 */
6var UrlSerializer = (function () {
7 /**
8 * @param {?} _app
9 * @param {?} config
10 */
11 function UrlSerializer(_app, config) {
12 this._app = _app;
13 if (config && isArray(config.links)) {
14 this.links = normalizeLinks(config.links);
15 }
16 else {
17 this.links = [];
18 }
19 }
20 /**
21 * Parse the URL into a Path, which is made up of multiple NavSegments.
22 * Match which components belong to each segment.
23 * @param {?} browserUrl
24 * @return {?}
25 */
26 UrlSerializer.prototype.parse = function (browserUrl) {
27 if (browserUrl.charAt(0) === '/') {
28 browserUrl = browserUrl.substr(1);
29 }
30 // trim off data after ? and #
31 browserUrl = browserUrl.split('?')[0].split('#')[0];
32 return convertUrlToSegments(this._app, browserUrl, this.links);
33 };
34 /**
35 * @param {?} navContainer
36 * @param {?} nameOrComponent
37 * @return {?}
38 */
39 UrlSerializer.prototype.createSegmentFromName = function (navContainer, nameOrComponent) {
40 var /** @type {?} */ configLink = this.getLinkFromName(nameOrComponent);
41 if (configLink) {
42 return this._createSegment(this._app, navContainer, configLink, null);
43 }
44 return null;
45 };
46 /**
47 * @param {?} nameOrComponent
48 * @return {?}
49 */
50 UrlSerializer.prototype.getLinkFromName = function (nameOrComponent) {
51 return this.links.find(function (link) {
52 return (link.component === nameOrComponent) ||
53 (link.name === nameOrComponent);
54 });
55 };
56 /**
57 * Serialize a path, which is made up of multiple NavSegments,
58 * into a URL string. Turn each segment into a string and concat them to a URL.
59 * @param {?} segments
60 * @return {?}
61 */
62 UrlSerializer.prototype.serialize = function (segments) {
63 if (!segments || !segments.length) {
64 return '/';
65 }
66 var /** @type {?} */ sections = segments.map(function (segment) {
67 if (segment.type === 'tabs') {
68 if (segment.requiresExplicitNavPrefix) {
69 return "/" + segment.type + "/" + segment.navId + "/" + segment.secondaryId + "/" + segment.id;
70 }
71 return "/" + segment.secondaryId + "/" + segment.id;
72 }
73 // it's a nav
74 if (segment.requiresExplicitNavPrefix) {
75 return "/" + segment.type + "/" + segment.navId + "/" + segment.id;
76 }
77 return "/" + segment.id;
78 });
79 return sections.join('');
80 };
81 /**
82 * Serializes a component and its data into a NavSegment.
83 * @param {?} navContainer
84 * @param {?} component
85 * @param {?} data
86 * @return {?}
87 */
88 UrlSerializer.prototype.serializeComponent = function (navContainer, component, data) {
89 if (component) {
90 var /** @type {?} */ link = findLinkByComponentData(this.links, component, data);
91 if (link) {
92 return this._createSegment(this._app, navContainer, link, data);
93 }
94 }
95 return null;
96 };
97 /**
98 * \@internal
99 * @param {?} app
100 * @param {?} navContainer
101 * @param {?} configLink
102 * @param {?} data
103 * @return {?}
104 */
105 UrlSerializer.prototype._createSegment = function (app, navContainer, configLink, data) {
106 var /** @type {?} */ urlParts = configLink.segmentParts;
107 if (isPresent(data)) {
108 // create a copy of the original parts in the link config
109 urlParts = urlParts.slice();
110 // loop through all the data and convert it to a string
111 var /** @type {?} */ keys = Object.keys(data);
112 var /** @type {?} */ keysLength = keys.length;
113 if (keysLength) {
114 for (var /** @type {?} */ i = 0; i < urlParts.length; i++) {
115 if (urlParts[i].charAt(0) === ':') {
116 for (var /** @type {?} */ j = 0; j < keysLength; j++) {
117 if (urlParts[i] === ":" + keys[j]) {
118 // this data goes into the URL part (between slashes)
119 urlParts[i] = encodeURIComponent(data[keys[j]]);
120 break;
121 }
122 }
123 }
124 }
125 }
126 }
127 var /** @type {?} */ requiresExplicitPrefix = true;
128 if (navContainer.parent) {
129 requiresExplicitPrefix = navContainer.parent && navContainer.parent.getAllChildNavs().length > 1;
130 }
131 else {
132 // if it's a root nav, and there are multiple root navs, we need an explicit prefix
133 requiresExplicitPrefix = app.getRootNavById(navContainer.id) && app.getRootNavs().length > 1;
134 }
135 return {
136 id: urlParts.join('/'),
137 name: configLink.name,
138 component: configLink.component,
139 loadChildren: configLink.loadChildren,
140 data: data,
141 defaultHistory: configLink.defaultHistory,
142 navId: navContainer.name || navContainer.id,
143 type: navContainer.getType(),
144 secondaryId: navContainer.getSecondaryIdentifier(),
145 requiresExplicitNavPrefix: requiresExplicitPrefix
146 };
147 };
148 return UrlSerializer;
149}());
150export { UrlSerializer };
151function UrlSerializer_tsickle_Closure_declarations() {
152 /** @type {?} */
153 UrlSerializer.prototype.links;
154 /** @type {?} */
155 UrlSerializer.prototype._app;
156}
157/**
158 * @param {?} name
159 * @return {?}
160 */
161export function formatUrlPart(name) {
162 name = name.replace(URL_REPLACE_REG, '-');
163 name = name.charAt(0).toLowerCase() + name.substring(1).replace(/[A-Z]/g, function (match) {
164 return '-' + match.toLowerCase();
165 });
166 while (name.indexOf('--') > -1) {
167 name = name.replace('--', '-');
168 }
169 if (name.charAt(0) === '-') {
170 name = name.substring(1);
171 }
172 if (name.substring(name.length - 1) === '-') {
173 name = name.substring(0, name.length - 1);
174 }
175 return encodeURIComponent(name);
176}
177export var /** @type {?} */ isPartMatch = function (urlPart, configLinkPart) {
178 if (isPresent(urlPart) && isPresent(configLinkPart)) {
179 if (configLinkPart.charAt(0) === ':') {
180 return true;
181 }
182 return (urlPart === configLinkPart);
183 }
184 return false;
185};
186export var /** @type {?} */ createMatchedData = function (matchedUrlParts, link) {
187 var /** @type {?} */ data = null;
188 for (var /** @type {?} */ i = 0; i < link.segmentPartsLen; i++) {
189 if (link.segmentParts[i].charAt(0) === ':') {
190 data = data || {};
191 data[link.segmentParts[i].substring(1)] = decodeURIComponent(matchedUrlParts[i]);
192 }
193 }
194 return data;
195};
196export var /** @type {?} */ findLinkByComponentData = function (links, component, instanceData) {
197 var /** @type {?} */ foundLink = null;
198 var /** @type {?} */ foundLinkDataMatches = -1;
199 for (var /** @type {?} */ i = 0; i < links.length; i++) {
200 var /** @type {?} */ link = links[i];
201 if (link.component === component) {
202 // ok, so the component matched, but multiple links can point
203 // to the same component, so let's make sure this is the right link
204 var /** @type {?} */ dataMatches = 0;
205 if (instanceData) {
206 var /** @type {?} */ instanceDataKeys = Object.keys(instanceData);
207 // this link has data
208 for (var /** @type {?} */ j = 0; j < instanceDataKeys.length; j++) {
209 if (isPresent(link.dataKeys[instanceDataKeys[j]])) {
210 dataMatches++;
211 }
212 }
213 }
214 else if (link.dataLen) {
215 // this component does not have data but the link does
216 continue;
217 }
218 if (dataMatches >= foundLinkDataMatches) {
219 foundLink = link;
220 foundLinkDataMatches = dataMatches;
221 }
222 }
223 }
224 return foundLink;
225};
226export var /** @type {?} */ normalizeLinks = function (links) {
227 for (var /** @type {?} */ i = 0, /** @type {?} */ ilen = links.length; i < ilen; i++) {
228 var /** @type {?} */ link = links[i];
229 if (isBlank(link.segment)) {
230 link.segment = link.name;
231 }
232 link.dataKeys = {};
233 link.segmentParts = link.segment.split('/');
234 link.segmentPartsLen = link.segmentParts.length;
235 // used for sorting
236 link.staticLen = link.dataLen = 0;
237 var /** @type {?} */ stillCountingStatic = true;
238 for (var /** @type {?} */ j = 0; j < link.segmentPartsLen; j++) {
239 if (link.segmentParts[j].charAt(0) === ':') {
240 link.dataLen++;
241 stillCountingStatic = false;
242 link.dataKeys[link.segmentParts[j].substring(1)] = true;
243 }
244 else if (stillCountingStatic) {
245 link.staticLen++;
246 }
247 }
248 }
249 // sort by the number of parts, with the links
250 // with the most parts first
251 return links.sort(sortConfigLinks);
252};
253/**
254 * @param {?} a
255 * @param {?} b
256 * @return {?}
257 */
258function sortConfigLinks(a, b) {
259 // sort by the number of parts
260 if (a.segmentPartsLen > b.segmentPartsLen) {
261 return -1;
262 }
263 if (a.segmentPartsLen < b.segmentPartsLen) {
264 return 1;
265 }
266 // sort by the number of static parts in a row
267 if (a.staticLen > b.staticLen) {
268 return -1;
269 }
270 if (a.staticLen < b.staticLen) {
271 return 1;
272 }
273 // sort by the number of total data parts
274 if (a.dataLen < b.dataLen) {
275 return -1;
276 }
277 if (a.dataLen > b.dataLen) {
278 return 1;
279 }
280 return 0;
281}
282var /** @type {?} */ URL_REPLACE_REG = /\s+|\?|\!|\$|\,|\.|\+|\"|\'|\*|\^|\||\/|\\|\[|\]|#|%|`|>|<|;|:|@|&|=/g;
283/**
284 * @hidden
285 */
286export var DeepLinkConfigToken = new OpaqueToken('USERLINKS');
287/**
288 * @param {?} app
289 * @param {?} userDeepLinkConfig
290 * @return {?}
291 */
292export function setupUrlSerializer(app, userDeepLinkConfig) {
293 return new UrlSerializer(app, userDeepLinkConfig);
294}
295/**
296 * @param {?} navGroupStrings
297 * @return {?}
298 */
299export function navGroupStringtoObjects(navGroupStrings) {
300 // each string has a known format-ish, convert it to it
301 return navGroupStrings.map(function (navGroupString) {
302 var /** @type {?} */ sections = navGroupString.split('/');
303 if (sections[0] === 'nav') {
304 return {
305 type: 'nav',
306 navId: sections[1],
307 niceId: sections[1],
308 secondaryId: null,
309 segmentPieces: sections.splice(2)
310 };
311 }
312 else if (sections[0] === 'tabs') {
313 return {
314 type: 'tabs',
315 navId: sections[1],
316 niceId: sections[1],
317 secondaryId: sections[2],
318 segmentPieces: sections.splice(3)
319 };
320 }
321 return {
322 type: null,
323 navId: null,
324 niceId: null,
325 secondaryId: null,
326 segmentPieces: sections
327 };
328 });
329}
330/**
331 * @param {?} url
332 * @return {?}
333 */
334export function urlToNavGroupStrings(url) {
335 var /** @type {?} */ tokens = url.split('/');
336 var /** @type {?} */ keywordIndexes = [];
337 for (var /** @type {?} */ i = 0; i < tokens.length; i++) {
338 if (i !== 0 && (tokens[i] === 'nav' || tokens[i] === 'tabs')) {
339 keywordIndexes.push(i);
340 }
341 }
342 // append the last index + 1 to the list no matter what
343 keywordIndexes.push(tokens.length);
344 var /** @type {?} */ groupings = [];
345 var /** @type {?} */ activeKeywordIndex = 0;
346 var /** @type {?} */ tmpArray = [];
347 for (var /** @type {?} */ i = 0; i < tokens.length; i++) {
348 if (i >= keywordIndexes[activeKeywordIndex]) {
349 groupings.push(tmpArray.join('/'));
350 tmpArray = [];
351 activeKeywordIndex++;
352 }
353 tmpArray.push(tokens[i]);
354 }
355 // okay, after the loop we've gotta push one more time just to be safe
356 groupings.push(tmpArray.join('/'));
357 return groupings;
358}
359/**
360 * @param {?} app
361 * @param {?} url
362 * @param {?} navLinks
363 * @return {?}
364 */
365export function convertUrlToSegments(app, url, navLinks) {
366 var /** @type {?} */ pairs = convertUrlToDehydratedSegments(url, navLinks);
367 return hydrateSegmentsWithNav(app, pairs);
368}
369/**
370 * @param {?} url
371 * @param {?} navLinks
372 * @return {?}
373 */
374export function convertUrlToDehydratedSegments(url, navLinks) {
375 var /** @type {?} */ navGroupStrings = urlToNavGroupStrings(url);
376 var /** @type {?} */ navGroups = navGroupStringtoObjects(navGroupStrings);
377 return getSegmentsFromNavGroups(navGroups, navLinks);
378}
379/**
380 * @param {?} app
381 * @param {?} dehydratedSegmentPairs
382 * @return {?}
383 */
384export function hydrateSegmentsWithNav(app, dehydratedSegmentPairs) {
385 var /** @type {?} */ segments = [];
386 for (var /** @type {?} */ i = 0; i < dehydratedSegmentPairs.length; i++) {
387 var /** @type {?} */ navs = getNavFromNavGroup(dehydratedSegmentPairs[i].navGroup, app);
388 // okay, cool, let's walk through the segments and hydrate them
389 for (var _i = 0, _a = dehydratedSegmentPairs[i].segments; _i < _a.length; _i++) {
390 var dehydratedSegment = _a[_i];
391 if (navs.length === 1) {
392 segments.push(hydrateSegment(dehydratedSegment, navs[0]));
393 navs = navs[0].getActiveChildNavs();
394 }
395 else if (navs.length > 1) {
396 // this is almost certainly an async race condition bug in userland
397 // if you're in this state, it would be nice to just bail here
398 // but alas we must perservere and handle the issue
399 // the simple solution is to just use the last child
400 // because that is probably what the user wants anyway
401 // remember, do not harm, even if it makes our shizzle ugly
402 segments.push(hydrateSegment(dehydratedSegment, navs[navs.length - 1]));
403 navs = navs[navs.length - 1].getActiveChildNavs();
404 }
405 else {
406 break;
407 }
408 }
409 }
410 return segments;
411}
412/**
413 * @param {?} navGroup
414 * @param {?} app
415 * @return {?}
416 */
417export function getNavFromNavGroup(navGroup, app) {
418 if (navGroup.navId) {
419 var /** @type {?} */ rootNav = app.getNavByIdOrName(navGroup.navId);
420 if (rootNav) {
421 return [rootNav];
422 }
423 return [];
424 }
425 // we don't know what nav to use, so just use the root nav.
426 // if there is more than one root nav, throw an error
427 return app.getRootNavs();
428}
429/**
430 * @param {?} navGroups
431 * @param {?} navLinks
432 * @return {?}
433 */
434export function getSegmentsFromNavGroups(navGroups, navLinks) {
435 var /** @type {?} */ pairs = [];
436 var /** @type {?} */ usedNavLinks = new Set();
437 for (var _i = 0, navGroups_1 = navGroups; _i < navGroups_1.length; _i++) {
438 var navGroup = navGroups_1[_i];
439 var /** @type {?} */ segments = [];
440 var /** @type {?} */ segmentPieces = navGroup.segmentPieces.concat([]);
441 for (var /** @type {?} */ i = segmentPieces.length; i >= 0; i--) {
442 var /** @type {?} */ created = false;
443 for (var /** @type {?} */ j = 0; j < i; j++) {
444 var /** @type {?} */ startIndex = i - j - 1;
445 var /** @type {?} */ endIndex = i;
446 var /** @type {?} */ subsetOfUrl = segmentPieces.slice(startIndex, endIndex);
447 for (var _a = 0, navLinks_1 = navLinks; _a < navLinks_1.length; _a++) {
448 var navLink = navLinks_1[_a];
449 if (!usedNavLinks.has(navLink.name)) {
450 var /** @type {?} */ segment = getSegmentsFromUrlPieces(subsetOfUrl, navLink);
451 if (segment) {
452 i = startIndex + 1;
453 usedNavLinks.add(navLink.name);
454 created = true;
455 // sweet, we found a segment
456 segments.push(segment);
457 // now we want to null out the url subsection in the segmentPieces
458 for (var /** @type {?} */ k = startIndex; k < endIndex; k++) {
459 segmentPieces[k] = null;
460 }
461 break;
462 }
463 }
464 }
465 if (created) {
466 break;
467 }
468 }
469 if (!created && segmentPieces[i - 1]) {
470 // this is very likely a tab's secondary identifier
471 segments.push({
472 id: null,
473 name: null,
474 secondaryId: segmentPieces[i - 1],
475 component: null,
476 loadChildren: null,
477 data: null,
478 defaultHistory: null
479 });
480 }
481 }
482 // since we're getting segments in from right-to-left in the url, reverse them
483 // so they're in the correct order. Also filter out and bogus segments
484 var /** @type {?} */ orderedSegments = segments.reverse();
485 // okay, this is the lazy persons approach here.
486 // so here's the deal! Right now if section of the url is not a part of a segment
487 // it is almost certainly the secondaryId for a tabs component
488 // basically, knowing the segment for the `tab` itself is good, but we also need to know
489 // which tab is selected, so we have an identifer in the url that is associated with the tabs component
490 // telling us which tab is selected. With that in mind, we are going to go through and find the segments with only secondary identifiers,
491 // and simply add the secondaryId to the next segment, and then remove the empty segment from the list
492 for (var /** @type {?} */ i = 0; i < orderedSegments.length; i++) {
493 if (orderedSegments[i].secondaryId && !orderedSegments[i].id && ((i + 1) <= orderedSegments.length - 1)) {
494 orderedSegments[i + 1].secondaryId = orderedSegments[i].secondaryId;
495 orderedSegments[i] = null;
496 }
497 }
498 var /** @type {?} */ cleanedSegments = segments.filter(function (segment) { return !!segment; });
499 // if the nav group has a secondary id, make sure the first segment also has it set
500 if (navGroup.secondaryId && segments.length) {
501 cleanedSegments[0].secondaryId = navGroup.secondaryId;
502 }
503 pairs.push({
504 navGroup: navGroup,
505 segments: cleanedSegments
506 });
507 }
508 return pairs;
509}
510/**
511 * @param {?} urlSections
512 * @param {?} navLink
513 * @return {?}
514 */
515export function getSegmentsFromUrlPieces(urlSections, navLink) {
516 if (navLink.segmentPartsLen !== urlSections.length) {
517 return null;
518 }
519 for (var /** @type {?} */ i = 0; i < urlSections.length; i++) {
520 if (!isPartMatch(urlSections[i], navLink.segmentParts[i])) {
521 // just return an empty array if the part doesn't match
522 return null;
523 }
524 }
525 return {
526 id: urlSections.join('/'),
527 name: navLink.name,
528 component: navLink.component,
529 loadChildren: navLink.loadChildren,
530 data: createMatchedData(urlSections, navLink),
531 defaultHistory: navLink.defaultHistory
532 };
533}
534/**
535 * @param {?} segment
536 * @param {?} nav
537 * @return {?}
538 */
539export function hydrateSegment(segment, nav) {
540 var /** @type {?} */ hydratedSegment = (Object.assign({}, segment));
541 hydratedSegment.type = nav.getType();
542 hydratedSegment.navId = nav.name || nav.id;
543 // secondaryId is set on an empty dehydrated segment in the case of tabs to identify which tab is selected
544 hydratedSegment.secondaryId = segment.secondaryId;
545 return hydratedSegment;
546}
547/**
548 * @param {?} urlChunks
549 * @param {?} navLink
550 * @return {?}
551 */
552export function getNonHydratedSegmentIfLinkAndUrlMatch(urlChunks, navLink) {
553 var /** @type {?} */ allSegmentsMatch = true;
554 for (var /** @type {?} */ i = 0; i < urlChunks.length; i++) {
555 if (!isPartMatch(urlChunks[i], navLink.segmentParts[i])) {
556 allSegmentsMatch = false;
557 break;
558 }
559 }
560 if (allSegmentsMatch) {
561 return {
562 id: navLink.segmentParts.join('/'),
563 name: navLink.name,
564 component: navLink.component,
565 loadChildren: navLink.loadChildren,
566 data: createMatchedData(urlChunks, navLink),
567 defaultHistory: navLink.defaultHistory
568 };
569 }
570 return null;
571}
572//# sourceMappingURL=url-serializer.js.map
\No newline at end of file