123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- import { InjectionToken } from '@angular/core';
- import { isArray, isBlank, isPresent } from '../util/util';
- /**
- * @hidden
- */
- var UrlSerializer = (function () {
- function UrlSerializer(_app, config) {
- this._app = _app;
- if (config && isArray(config.links)) {
- this.links = normalizeLinks(config.links);
- }
- else {
- this.links = [];
- }
- }
- /**
- * Parse the URL into a Path, which is made up of multiple NavSegments.
- * Match which components belong to each segment.
- */
- UrlSerializer.prototype.parse = function (browserUrl) {
- if (browserUrl.charAt(0) === '/') {
- browserUrl = browserUrl.substr(1);
- }
- // trim off data after ? and #
- browserUrl = browserUrl.split('?')[0].split('#')[0];
- return convertUrlToSegments(this._app, browserUrl, this.links);
- };
- UrlSerializer.prototype.createSegmentFromName = function (navContainer, nameOrComponent) {
- var configLink = this.getLinkFromName(nameOrComponent);
- if (configLink) {
- return this._createSegment(this._app, navContainer, configLink, null);
- }
- return null;
- };
- UrlSerializer.prototype.getLinkFromName = function (nameOrComponent) {
- return this.links.find(function (link) {
- return (link.component === nameOrComponent) ||
- (link.name === nameOrComponent);
- });
- };
- /**
- * Serialize a path, which is made up of multiple NavSegments,
- * into a URL string. Turn each segment into a string and concat them to a URL.
- */
- UrlSerializer.prototype.serialize = function (segments) {
- if (!segments || !segments.length) {
- return '/';
- }
- var sections = segments.map(function (segment) {
- if (segment.type === 'tabs') {
- if (segment.requiresExplicitNavPrefix) {
- return "/" + segment.type + "/" + segment.navId + "/" + segment.secondaryId + "/" + segment.id;
- }
- return "/" + segment.secondaryId + "/" + segment.id;
- }
- // it's a nav
- if (segment.requiresExplicitNavPrefix) {
- return "/" + segment.type + "/" + segment.navId + "/" + segment.id;
- }
- return "/" + segment.id;
- });
- return sections.join('');
- };
- /**
- * Serializes a component and its data into a NavSegment.
- */
- UrlSerializer.prototype.serializeComponent = function (navContainer, component, data) {
- if (component) {
- var link = findLinkByComponentData(this.links, component, data);
- if (link) {
- return this._createSegment(this._app, navContainer, link, data);
- }
- }
- return null;
- };
- /**
- * @internal
- */
- UrlSerializer.prototype._createSegment = function (app, navContainer, configLink, data) {
- var urlParts = configLink.segmentParts;
- if (isPresent(data)) {
- // create a copy of the original parts in the link config
- urlParts = urlParts.slice();
- // loop through all the data and convert it to a string
- var keys = Object.keys(data);
- var keysLength = keys.length;
- if (keysLength) {
- for (var i = 0; i < urlParts.length; i++) {
- if (urlParts[i].charAt(0) === ':') {
- for (var j = 0; j < keysLength; j++) {
- if (urlParts[i] === ":" + keys[j]) {
- // this data goes into the URL part (between slashes)
- urlParts[i] = encodeURIComponent(data[keys[j]]);
- break;
- }
- }
- }
- }
- }
- }
- var requiresExplicitPrefix = true;
- if (navContainer.parent) {
- requiresExplicitPrefix = navContainer.parent && navContainer.parent.getAllChildNavs().length > 1;
- }
- else {
- // if it's a root nav, and there are multiple root navs, we need an explicit prefix
- requiresExplicitPrefix = app.getRootNavById(navContainer.id) && app.getRootNavs().length > 1;
- }
- return {
- id: urlParts.join('/'),
- name: configLink.name,
- component: configLink.component,
- loadChildren: configLink.loadChildren,
- data: data,
- defaultHistory: configLink.defaultHistory,
- navId: navContainer.name || navContainer.id,
- type: navContainer.getType(),
- secondaryId: navContainer.getSecondaryIdentifier(),
- requiresExplicitNavPrefix: requiresExplicitPrefix
- };
- };
- return UrlSerializer;
- }());
- export { UrlSerializer };
- export function formatUrlPart(name) {
- name = name.replace(URL_REPLACE_REG, '-');
- name = name.charAt(0).toLowerCase() + name.substring(1).replace(/[A-Z]/g, function (match) {
- return '-' + match.toLowerCase();
- });
- while (name.indexOf('--') > -1) {
- name = name.replace('--', '-');
- }
- if (name.charAt(0) === '-') {
- name = name.substring(1);
- }
- if (name.substring(name.length - 1) === '-') {
- name = name.substring(0, name.length - 1);
- }
- return encodeURIComponent(name);
- }
- export var isPartMatch = function (urlPart, configLinkPart) {
- if (isPresent(urlPart) && isPresent(configLinkPart)) {
- if (configLinkPart.charAt(0) === ':') {
- return true;
- }
- return (urlPart === configLinkPart);
- }
- return false;
- };
- export var createMatchedData = function (matchedUrlParts, link) {
- var data = null;
- for (var i = 0; i < link.segmentPartsLen; i++) {
- if (link.segmentParts[i].charAt(0) === ':') {
- data = data || {};
- data[link.segmentParts[i].substring(1)] = decodeURIComponent(matchedUrlParts[i]);
- }
- }
- return data;
- };
- export var findLinkByComponentData = function (links, component, instanceData) {
- var foundLink = null;
- var foundLinkDataMatches = -1;
- for (var i = 0; i < links.length; i++) {
- var link = links[i];
- if (link.component === component) {
- // ok, so the component matched, but multiple links can point
- // to the same component, so let's make sure this is the right link
- var dataMatches = 0;
- if (instanceData) {
- var instanceDataKeys = Object.keys(instanceData);
- // this link has data
- for (var j = 0; j < instanceDataKeys.length; j++) {
- if (isPresent(link.dataKeys[instanceDataKeys[j]])) {
- dataMatches++;
- }
- }
- }
- else if (link.dataLen) {
- // this component does not have data but the link does
- continue;
- }
- if (dataMatches >= foundLinkDataMatches) {
- foundLink = link;
- foundLinkDataMatches = dataMatches;
- }
- }
- }
- return foundLink;
- };
- export var normalizeLinks = function (links) {
- for (var i = 0, ilen = links.length; i < ilen; i++) {
- var link = links[i];
- if (isBlank(link.segment)) {
- link.segment = link.name;
- }
- link.dataKeys = {};
- link.segmentParts = link.segment.split('/');
- link.segmentPartsLen = link.segmentParts.length;
- // used for sorting
- link.staticLen = link.dataLen = 0;
- var stillCountingStatic = true;
- for (var j = 0; j < link.segmentPartsLen; j++) {
- if (link.segmentParts[j].charAt(0) === ':') {
- link.dataLen++;
- stillCountingStatic = false;
- link.dataKeys[link.segmentParts[j].substring(1)] = true;
- }
- else if (stillCountingStatic) {
- link.staticLen++;
- }
- }
- }
- // sort by the number of parts, with the links
- // with the most parts first
- return links.sort(sortConfigLinks);
- };
- function sortConfigLinks(a, b) {
- // sort by the number of parts
- if (a.segmentPartsLen > b.segmentPartsLen) {
- return -1;
- }
- if (a.segmentPartsLen < b.segmentPartsLen) {
- return 1;
- }
- // sort by the number of static parts in a row
- if (a.staticLen > b.staticLen) {
- return -1;
- }
- if (a.staticLen < b.staticLen) {
- return 1;
- }
- // sort by the number of total data parts
- if (a.dataLen < b.dataLen) {
- return -1;
- }
- if (a.dataLen > b.dataLen) {
- return 1;
- }
- return 0;
- }
- var URL_REPLACE_REG = /\s+|\?|\!|\$|\,|\.|\+|\"|\'|\*|\^|\||\/|\\|\[|\]|#|%|`|>|<|;|:|@|&|=/g;
- /**
- * @hidden
- */
- export var DeepLinkConfigToken = new InjectionToken('USERLINKS');
- export function setupUrlSerializer(app, userDeepLinkConfig) {
- return new UrlSerializer(app, userDeepLinkConfig);
- }
- export function navGroupStringtoObjects(navGroupStrings) {
- // each string has a known format-ish, convert it to it
- return navGroupStrings.map(function (navGroupString) {
- var sections = navGroupString.split('/');
- if (sections[0] === 'nav') {
- return {
- type: 'nav',
- navId: sections[1],
- niceId: sections[1],
- secondaryId: null,
- segmentPieces: sections.splice(2)
- };
- }
- else if (sections[0] === 'tabs') {
- return {
- type: 'tabs',
- navId: sections[1],
- niceId: sections[1],
- secondaryId: sections[2],
- segmentPieces: sections.splice(3)
- };
- }
- return {
- type: null,
- navId: null,
- niceId: null,
- secondaryId: null,
- segmentPieces: sections
- };
- });
- }
- export function urlToNavGroupStrings(url) {
- var tokens = url.split('/');
- var keywordIndexes = [];
- for (var i = 0; i < tokens.length; i++) {
- if (i !== 0 && (tokens[i] === 'nav' || tokens[i] === 'tabs')) {
- keywordIndexes.push(i);
- }
- }
- // append the last index + 1 to the list no matter what
- keywordIndexes.push(tokens.length);
- var groupings = [];
- var activeKeywordIndex = 0;
- var tmpArray = [];
- for (var i = 0; i < tokens.length; i++) {
- if (i >= keywordIndexes[activeKeywordIndex]) {
- groupings.push(tmpArray.join('/'));
- tmpArray = [];
- activeKeywordIndex++;
- }
- tmpArray.push(tokens[i]);
- }
- // okay, after the loop we've gotta push one more time just to be safe
- groupings.push(tmpArray.join('/'));
- return groupings;
- }
- export function convertUrlToSegments(app, url, navLinks) {
- var pairs = convertUrlToDehydratedSegments(url, navLinks);
- return hydrateSegmentsWithNav(app, pairs);
- }
- export function convertUrlToDehydratedSegments(url, navLinks) {
- var navGroupStrings = urlToNavGroupStrings(url);
- var navGroups = navGroupStringtoObjects(navGroupStrings);
- return getSegmentsFromNavGroups(navGroups, navLinks);
- }
- export function hydrateSegmentsWithNav(app, dehydratedSegmentPairs) {
- var segments = [];
- for (var i = 0; i < dehydratedSegmentPairs.length; i++) {
- var navs = getNavFromNavGroup(dehydratedSegmentPairs[i].navGroup, app);
- // okay, cool, let's walk through the segments and hydrate them
- for (var _i = 0, _a = dehydratedSegmentPairs[i].segments; _i < _a.length; _i++) {
- var dehydratedSegment = _a[_i];
- if (navs.length === 1) {
- segments.push(hydrateSegment(dehydratedSegment, navs[0]));
- navs = navs[0].getActiveChildNavs();
- }
- else if (navs.length > 1) {
- // this is almost certainly an async race condition bug in userland
- // if you're in this state, it would be nice to just bail here
- // but alas we must perservere and handle the issue
- // the simple solution is to just use the last child
- // because that is probably what the user wants anyway
- // remember, do not harm, even if it makes our shizzle ugly
- segments.push(hydrateSegment(dehydratedSegment, navs[navs.length - 1]));
- navs = navs[navs.length - 1].getActiveChildNavs();
- }
- else {
- break;
- }
- }
- }
- return segments;
- }
- export function getNavFromNavGroup(navGroup, app) {
- if (navGroup.navId) {
- var rootNav = app.getNavByIdOrName(navGroup.navId);
- if (rootNav) {
- return [rootNav];
- }
- return [];
- }
- // we don't know what nav to use, so just use the root nav.
- // if there is more than one root nav, throw an error
- return app.getRootNavs();
- }
- /*
- * Let's face the facts: Getting a dehydrated segment from the url is really hard
- * because we need to do a ton of crazy looping
- * the are chunks of a url that are totally irrelevant at this stage, such as the secondary identifier
- * stating which tab is selected, etc.
- * but is necessary.
- * We look at segment pieces in reverse order to try to build segments
- * as in, if you had an array like this
- * ['my', 'super', 'cool', 'url']
- * we want to look at the pieces in reverse order:
- * url
- * cool url
- * super cool url
- * my super cool url
- * cool
- * super cool
- * my super cool
- * super
- * my super
- * my
- **/
- export function getSegmentsFromNavGroups(navGroups, navLinks) {
- var pairs = [];
- var usedNavLinks = new Set();
- for (var _i = 0, navGroups_1 = navGroups; _i < navGroups_1.length; _i++) {
- var navGroup = navGroups_1[_i];
- var segments = [];
- var segmentPieces = navGroup.segmentPieces.concat([]);
- for (var i = segmentPieces.length; i >= 0; i--) {
- var created = false;
- for (var j = 0; j < i; j++) {
- var startIndex = i - j - 1;
- var endIndex = i;
- var subsetOfUrl = segmentPieces.slice(startIndex, endIndex);
- for (var _a = 0, navLinks_1 = navLinks; _a < navLinks_1.length; _a++) {
- var navLink = navLinks_1[_a];
- if (!usedNavLinks.has(navLink.name)) {
- var segment = getSegmentsFromUrlPieces(subsetOfUrl, navLink);
- if (segment) {
- i = startIndex + 1;
- usedNavLinks.add(navLink.name);
- created = true;
- // sweet, we found a segment
- segments.push(segment);
- // now we want to null out the url subsection in the segmentPieces
- for (var k = startIndex; k < endIndex; k++) {
- segmentPieces[k] = null;
- }
- break;
- }
- }
- }
- if (created) {
- break;
- }
- }
- if (!created && segmentPieces[i - 1]) {
- // this is very likely a tab's secondary identifier
- segments.push({
- id: null,
- name: null,
- secondaryId: segmentPieces[i - 1],
- component: null,
- loadChildren: null,
- data: null,
- defaultHistory: null
- });
- }
- }
- // since we're getting segments in from right-to-left in the url, reverse them
- // so they're in the correct order. Also filter out and bogus segments
- var orderedSegments = segments.reverse();
- // okay, this is the lazy persons approach here.
- // so here's the deal! Right now if section of the url is not a part of a segment
- // it is almost certainly the secondaryId for a tabs component
- // basically, knowing the segment for the `tab` itself is good, but we also need to know
- // which tab is selected, so we have an identifer in the url that is associated with the tabs component
- // telling us which tab is selected. With that in mind, we are going to go through and find the segments with only secondary identifiers,
- // and simply add the secondaryId to the next segment, and then remove the empty segment from the list
- for (var i = 0; i < orderedSegments.length; i++) {
- if (orderedSegments[i].secondaryId && !orderedSegments[i].id && ((i + 1) <= orderedSegments.length - 1)) {
- orderedSegments[i + 1].secondaryId = orderedSegments[i].secondaryId;
- orderedSegments[i] = null;
- }
- }
- var cleanedSegments = segments.filter(function (segment) { return !!segment; });
- // if the nav group has a secondary id, make sure the first segment also has it set
- if (navGroup.secondaryId && segments.length) {
- cleanedSegments[0].secondaryId = navGroup.secondaryId;
- }
- pairs.push({
- navGroup: navGroup,
- segments: cleanedSegments
- });
- }
- return pairs;
- }
- export function getSegmentsFromUrlPieces(urlSections, navLink) {
- if (navLink.segmentPartsLen !== urlSections.length) {
- return null;
- }
- for (var i = 0; i < urlSections.length; i++) {
- if (!isPartMatch(urlSections[i], navLink.segmentParts[i])) {
- // just return an empty array if the part doesn't match
- return null;
- }
- }
- return {
- id: urlSections.join('/'),
- name: navLink.name,
- component: navLink.component,
- loadChildren: navLink.loadChildren,
- data: createMatchedData(urlSections, navLink),
- defaultHistory: navLink.defaultHistory
- };
- }
- export function hydrateSegment(segment, nav) {
- var hydratedSegment = Object.assign({}, segment);
- hydratedSegment.type = nav.getType();
- hydratedSegment.navId = nav.name || nav.id;
- // secondaryId is set on an empty dehydrated segment in the case of tabs to identify which tab is selected
- hydratedSegment.secondaryId = segment.secondaryId;
- return hydratedSegment;
- }
- export function getNonHydratedSegmentIfLinkAndUrlMatch(urlChunks, navLink) {
- var allSegmentsMatch = true;
- for (var i = 0; i < urlChunks.length; i++) {
- if (!isPartMatch(urlChunks[i], navLink.segmentParts[i])) {
- allSegmentsMatch = false;
- break;
- }
- }
- if (allSegmentsMatch) {
- return {
- id: navLink.segmentParts.join('/'),
- name: navLink.name,
- component: navLink.component,
- loadChildren: navLink.loadChildren,
- data: createMatchedData(urlChunks, navLink),
- defaultHistory: navLink.defaultHistory
- };
- }
- return null;
- }
- //# sourceMappingURL=url-serializer.js.map
|