UNPKG

20 kBJavaScriptView Raw
1import { DIRECTION_BACK, convertToViews, isNav, isTab, isTabs } from './nav-util';
2import { isArray, isPresent } from '../util/util';
3import { formatUrlPart } from './url-serializer';
4import { ViewController } from './view-controller';
5/**
6 * @hidden
7 */
8var DeepLinker = (function () {
9 /**
10 * @param {?} _app
11 * @param {?} _serializer
12 * @param {?} _location
13 * @param {?} _moduleLoader
14 * @param {?} _baseCfr
15 */
16 function DeepLinker(_app, _serializer, _location, _moduleLoader, _baseCfr) {
17 this._app = _app;
18 this._serializer = _serializer;
19 this._location = _location;
20 this._moduleLoader = _moduleLoader;
21 this._baseCfr = _baseCfr;
22 /**
23 * \@internal
24 */
25 this._history = [];
26 }
27 /**
28 * \@internal
29 * @return {?}
30 */
31 DeepLinker.prototype.init = function () {
32 var _this = this;
33 // scenario 1: Initial load of all navs from the initial browser URL
34 var /** @type {?} */ browserUrl = normalizeUrl(this._location.path());
35 (void 0) /* console.debug */;
36 // remember this URL in our internal history stack
37 this._historyPush(browserUrl);
38 // listen for browser URL changes
39 this._location.subscribe(function (locationChg) {
40 _this._urlChange(normalizeUrl(locationChg.url));
41 });
42 };
43 /**
44 * The browser's location has been updated somehow.
45 * \@internal
46 * @param {?} browserUrl
47 * @return {?}
48 */
49 DeepLinker.prototype._urlChange = function (browserUrl) {
50 var _this = this;
51 // do nothing if this url is the same as the current one
52 if (!this._isCurrentUrl(browserUrl)) {
53 var /** @type {?} */ isGoingBack = true;
54 if (this._isBackUrl(browserUrl)) {
55 // scenario 2: user clicked the browser back button
56 // scenario 4: user changed the browser URL to what was the back url was
57 // scenario 5: user clicked a link href that was the back url
58 (void 0) /* console.debug */;
59 this._historyPop();
60 }
61 else {
62 // scenario 3: user click forward button
63 // scenario 4: user changed browser URL that wasn't the back url
64 // scenario 5: user clicked a link href that wasn't the back url
65 isGoingBack = false;
66 (void 0) /* console.debug */;
67 this._historyPush(browserUrl);
68 }
69 // get the app's root nav container
70 var /** @type {?} */ activeNavContainers_1 = this._app.getActiveNavContainers();
71 if (activeNavContainers_1 && activeNavContainers_1.length) {
72 if (browserUrl === '/') {
73 // a url change to the index url
74 if (isPresent(this._indexAliasUrl)) {
75 // we already know the indexAliasUrl
76 // update the url to use the know alias
77 browserUrl = this._indexAliasUrl;
78 }
79 else {
80 // the url change is to the root but we don't
81 // already know the url used. So let's just
82 // reset the root nav to its root page
83 activeNavContainers_1.forEach(function (navContainer) {
84 navContainer.goToRoot({
85 updateUrl: false,
86 isNavRoot: true
87 });
88 });
89 return;
90 }
91 }
92 // normal url
93 var /** @type {?} */ segments = this.getCurrentSegments(browserUrl);
94 segments
95 .map(function (segment) {
96 // find the matching nav container
97 for (var _i = 0, activeNavContainers_2 = activeNavContainers_1; _i < activeNavContainers_2.length; _i++) {
98 var navContainer = activeNavContainers_2[_i];
99 var /** @type {?} */ nav = getNavFromTree(navContainer, segment.navId);
100 if (nav) {
101 return {
102 segment: segment,
103 navContainer: nav
104 };
105 }
106 }
107 })
108 .filter(function (pair) { return !!pair; })
109 .forEach(function (pair) {
110 _this._loadViewForSegment(pair.navContainer, pair.segment, function () { });
111 });
112 }
113 }
114 };
115 /**
116 * @param {?=} browserUrl
117 * @return {?}
118 */
119 DeepLinker.prototype.getCurrentSegments = function (browserUrl) {
120 if (!browserUrl) {
121 browserUrl = normalizeUrl(this._location.path());
122 }
123 return this._serializer.parse(browserUrl);
124 };
125 /**
126 * Update the deep linker using the NavController's current active view.
127 * \@internal
128 * @param {?} direction
129 * @return {?}
130 */
131 DeepLinker.prototype.navChange = function (direction) {
132 if (direction) {
133 var /** @type {?} */ activeNavContainers = this._app.getActiveNavContainers();
134 // the only time you'll ever get a TABS here is when loading directly from a URL
135 // this method will be called again when the TAB is loaded
136 // so just don't worry about the TABS for now
137 // if you encounter a TABS, just return
138 for (var _i = 0, activeNavContainers_3 = activeNavContainers; _i < activeNavContainers_3.length; _i++) {
139 var activeNavContainer = activeNavContainers_3[_i];
140 if (isTabs(activeNavContainer) || ((activeNavContainer)).isTransitioning()) {
141 return;
142 }
143 }
144 // okay, get the root navs and build the segments up
145 var /** @type {?} */ segments = [];
146 var /** @type {?} */ navContainers = this._app.getRootNavs();
147 for (var _a = 0, navContainers_1 = navContainers; _a < navContainers_1.length; _a++) {
148 var navContainer = navContainers_1[_a];
149 var /** @type {?} */ segmentsForNav = this.getSegmentsFromNav(navContainer);
150 segments = segments.concat(segmentsForNav);
151 }
152 segments = segments.filter(function (segment) { return !!segment; });
153 if (segments.length) {
154 var /** @type {?} */ browserUrl = this._serializer.serialize(segments);
155 this._updateLocation(browserUrl, direction);
156 }
157 }
158 };
159 /**
160 * @param {?} nav
161 * @return {?}
162 */
163 DeepLinker.prototype.getSegmentsFromNav = function (nav) {
164 var _this = this;
165 var /** @type {?} */ segments = [];
166 if (isNav(nav)) {
167 segments.push(this.getSegmentFromNav(/** @type {?} */ (nav)));
168 }
169 else if (isTab(nav)) {
170 segments.push(this.getSegmentFromTab(nav));
171 }
172 nav.getActiveChildNavs().forEach(function (child) {
173 segments = segments.concat(_this.getSegmentsFromNav(child));
174 });
175 return segments;
176 };
177 /**
178 * @param {?} nav
179 * @param {?=} component
180 * @param {?=} data
181 * @return {?}
182 */
183 DeepLinker.prototype.getSegmentFromNav = function (nav, component, data) {
184 if (!component) {
185 var /** @type {?} */ viewController = nav.getActive(true);
186 if (viewController) {
187 component = viewController.component;
188 data = viewController.data;
189 }
190 }
191 return this._serializer.serializeComponent(nav, component, data);
192 };
193 /**
194 * @param {?} navContainer
195 * @param {?=} component
196 * @param {?=} data
197 * @return {?}
198 */
199 DeepLinker.prototype.getSegmentFromTab = function (navContainer, component, data) {
200 if (navContainer && navContainer.parent) {
201 var /** @type {?} */ tabsNavContainer = (navContainer.parent);
202 var /** @type {?} */ activeChildNavs = tabsNavContainer.getActiveChildNavs();
203 if (activeChildNavs && activeChildNavs.length) {
204 var /** @type {?} */ activeChildNav = activeChildNavs[0];
205 var /** @type {?} */ viewController = ((activeChildNav)).getActive(true);
206 if (viewController) {
207 component = viewController.component;
208 data = viewController.data;
209 }
210 return this._serializer.serializeComponent(tabsNavContainer, component, data);
211 }
212 }
213 };
214 /**
215 * \@internal
216 * @param {?} browserUrl
217 * @param {?} direction
218 * @return {?}
219 */
220 DeepLinker.prototype._updateLocation = function (browserUrl, direction) {
221 if (this._indexAliasUrl === browserUrl) {
222 browserUrl = '/';
223 }
224 if (direction === DIRECTION_BACK && this._isBackUrl(browserUrl)) {
225 // this URL is exactly the same as the back URL
226 // it's safe to use the browser's location.back()
227 (void 0) /* console.debug */;
228 this._historyPop();
229 this._location.back();
230 }
231 else if (!this._isCurrentUrl(browserUrl)) {
232 // probably navigating forward
233 (void 0) /* console.debug */;
234 this._historyPush(browserUrl);
235 this._location.go(browserUrl);
236 }
237 };
238 /**
239 * @param {?} componentName
240 * @return {?}
241 */
242 DeepLinker.prototype.getComponentFromName = function (componentName) {
243 var /** @type {?} */ link = this._serializer.getLinkFromName(componentName);
244 if (link) {
245 // cool, we found the right link for this component name
246 return this.getNavLinkComponent(link);
247 }
248 // umm, idk
249 return Promise.reject("invalid link: " + componentName);
250 };
251 /**
252 * @param {?} link
253 * @return {?}
254 */
255 DeepLinker.prototype.getNavLinkComponent = function (link) {
256 if (link.component) {
257 // sweet, we're already got a component loaded for this link
258 return Promise.resolve(link.component);
259 }
260 if (link.loadChildren) {
261 // awesome, looks like we'll lazy load this component
262 // using loadChildren as the URL to request
263 return this._moduleLoader.load(link.loadChildren).then(function (response) {
264 link.component = response.component;
265 return response.component;
266 });
267 }
268 return Promise.reject("invalid link component: " + link.name);
269 };
270 /**
271 * \@internal
272 * @param {?} component
273 * @return {?}
274 */
275 DeepLinker.prototype.resolveComponent = function (component) {
276 var /** @type {?} */ cfr = this._moduleLoader.getComponentFactoryResolver(component);
277 if (!cfr) {
278 cfr = this._baseCfr;
279 }
280 return cfr.resolveComponentFactory(component);
281 };
282 /**
283 * \@internal
284 * @param {?} navContainer
285 * @param {?} nameOrComponent
286 * @param {?} _data
287 * @param {?=} prepareExternalUrl
288 * @return {?}
289 */
290 DeepLinker.prototype.createUrl = function (navContainer, nameOrComponent, _data, prepareExternalUrl) {
291 if (prepareExternalUrl === void 0) { prepareExternalUrl = true; }
292 // create a segment out of just the passed in name
293 var /** @type {?} */ segment = this._serializer.createSegmentFromName(navContainer, nameOrComponent);
294 var /** @type {?} */ allSegments = this.getCurrentSegments();
295 if (segment) {
296 for (var /** @type {?} */ i = 0; i < allSegments.length; i++) {
297 if (allSegments[i].navId === navContainer.name || allSegments[i].navId === navContainer.id) {
298 allSegments[i] = segment;
299 var /** @type {?} */ url = this._serializer.serialize(allSegments);
300 return prepareExternalUrl ? this._location.prepareExternalUrl(url) : url;
301 }
302 }
303 }
304 return '';
305 };
306 /**
307 * Each NavController will call this method when it initializes for
308 * the first time. This allows each NavController to figure out
309 * where it lives in the path and load up the correct component.
310 * \@internal
311 * @param {?} navId
312 * @param {?} name
313 * @return {?}
314 */
315 DeepLinker.prototype.getSegmentByNavIdOrName = function (navId, name) {
316 var /** @type {?} */ browserUrl = normalizeUrl(this._location.path());
317 var /** @type {?} */ segments = this._serializer.parse(browserUrl);
318 for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
319 var segment = segments_1[_i];
320 if (segment.navId === navId || segment.navId === name) {
321 return segment;
322 }
323 }
324 return null;
325 };
326 /**
327 * \@internal
328 * @param {?} segment
329 * @return {?}
330 */
331 DeepLinker.prototype.initViews = function (segment) {
332 var _this = this;
333 var /** @type {?} */ link = this._serializer.getLinkFromName(segment.name);
334 return this.getNavLinkComponent(link).then(function (component) {
335 segment.component = component;
336 var /** @type {?} */ view = new ViewController(component, segment.data);
337 view.id = segment.id;
338 if (isArray(segment.defaultHistory)) {
339 return convertToViews(_this, segment.defaultHistory).then(function (views) {
340 views.push(view);
341 return views;
342 });
343 }
344 return [view];
345 });
346 };
347 /**
348 * \@internal
349 * @param {?} browserUrl
350 * @return {?}
351 */
352 DeepLinker.prototype._isBackUrl = function (browserUrl) {
353 return (browserUrl === this._history[this._history.length - 2]);
354 };
355 /**
356 * \@internal
357 * @param {?} browserUrl
358 * @return {?}
359 */
360 DeepLinker.prototype._isCurrentUrl = function (browserUrl) {
361 return (browserUrl === this._history[this._history.length - 1]);
362 };
363 /**
364 * \@internal
365 * @param {?} browserUrl
366 * @return {?}
367 */
368 DeepLinker.prototype._historyPush = function (browserUrl) {
369 if (!this._isCurrentUrl(browserUrl)) {
370 this._history.push(browserUrl);
371 if (this._history.length > 30) {
372 this._history.shift();
373 }
374 }
375 };
376 /**
377 * \@internal
378 * @return {?}
379 */
380 DeepLinker.prototype._historyPop = function () {
381 this._history.pop();
382 if (!this._history.length) {
383 this._historyPush(this._location.path());
384 }
385 };
386 /**
387 * \@internal
388 * @param {?} tab
389 * @return {?}
390 */
391 DeepLinker.prototype._getTabSelector = function (tab) {
392 if (isPresent(tab.tabUrlPath)) {
393 return tab.tabUrlPath;
394 }
395 if (isPresent(tab.tabTitle)) {
396 return formatUrlPart(tab.tabTitle);
397 }
398 return "tab-" + tab.index;
399 };
400 /**
401 * Using the known Path of Segments, walk down all descendents
402 * from the root NavController and load each NavController according
403 * to each Segment. This is usually called after a browser URL and
404 * Path changes and needs to update all NavControllers to match
405 * the new browser URL. Because the URL is already known, it will
406 * not update the browser's URL when transitions have completed.
407 *
408 * \@internal
409 * @param {?} navContainer
410 * @param {?} segment
411 * @param {?} done
412 * @return {?}
413 */
414 DeepLinker.prototype._loadViewForSegment = function (navContainer, segment, done) {
415 if (!segment) {
416 return done(false, false);
417 }
418 if (isTabs(navContainer) || (isTab(navContainer) && navContainer.parent)) {
419 var /** @type {?} */ tabs = (((isTabs(navContainer) ? navContainer : navContainer.parent)));
420 var /** @type {?} */ selectedIndex = tabs._getSelectedTabIndex(segment.secondaryId);
421 var /** @type {?} */ tab = tabs.getByIndex(selectedIndex);
422 tab._lazyRootFromUrl = segment.name;
423 tab._lazyRootFromUrlData = segment.data;
424 tabs.select(tab, {
425 updateUrl: false,
426 animate: false
427 }, true);
428 return done(false, false);
429 }
430 var /** @type {?} */ navController = ((navContainer));
431 var /** @type {?} */ numViews = navController.length() - 1;
432 // walk backwards to see if the exact view we want to show here
433 // is already in the stack that we can just pop back to
434 for (var /** @type {?} */ i = numViews; i >= 0; i--) {
435 var /** @type {?} */ viewController = navController.getByIndex(i);
436 if (viewController && (viewController.id === segment.id || viewController.id === segment.name)) {
437 // hooray! we've already got a view loaded in the stack
438 // matching the view they wanted to show
439 if (i === numViews) {
440 // this is the last view in the stack and it's the same
441 // as the segment so there's no change needed
442 return done(false, false);
443 }
444 else {
445 // it's not the exact view as the end
446 // let's have this nav go back to this exact view
447 return navController.popTo(viewController, {
448 animate: false,
449 updateUrl: false,
450 }, done);
451 }
452 }
453 }
454 // ok, so we don't know about a view that they're navigating to
455 // so we might as well just call setRoot and make tthe view the first view
456 // this seems like the least bad option
457 return navController.setRoot(segment.component || segment.name, segment.data, {
458 id: segment.id, animate: false, updateUrl: false
459 }, done);
460 };
461 return DeepLinker;
462}());
463export { DeepLinker };
464function DeepLinker_tsickle_Closure_declarations() {
465 /**
466 * \@internal
467 * @type {?}
468 */
469 DeepLinker.prototype._history;
470 /**
471 * \@internal
472 * @type {?}
473 */
474 DeepLinker.prototype._indexAliasUrl;
475 /** @type {?} */
476 DeepLinker.prototype._app;
477 /** @type {?} */
478 DeepLinker.prototype._serializer;
479 /** @type {?} */
480 DeepLinker.prototype._location;
481 /** @type {?} */
482 DeepLinker.prototype._moduleLoader;
483 /** @type {?} */
484 DeepLinker.prototype._baseCfr;
485}
486/**
487 * @param {?} app
488 * @param {?} serializer
489 * @param {?} location
490 * @param {?} moduleLoader
491 * @param {?} cfr
492 * @return {?}
493 */
494export function setupDeepLinker(app, serializer, location, moduleLoader, cfr) {
495 var /** @type {?} */ deepLinker = new DeepLinker(app, serializer, location, moduleLoader, cfr);
496 deepLinker.init();
497 return deepLinker;
498}
499/**
500 * @param {?} browserUrl
501 * @return {?}
502 */
503export function normalizeUrl(browserUrl) {
504 browserUrl = browserUrl.trim();
505 if (browserUrl.charAt(0) !== '/') {
506 // ensure first char is a /
507 browserUrl = '/' + browserUrl;
508 }
509 if (browserUrl.length > 1 && browserUrl.charAt(browserUrl.length - 1) === '/') {
510 // ensure last char is not a /
511 browserUrl = browserUrl.substr(0, browserUrl.length - 1);
512 }
513 return browserUrl;
514}
515/**
516 * @param {?} nav
517 * @param {?} id
518 * @return {?}
519 */
520export function getNavFromTree(nav, id) {
521 while (nav) {
522 if (nav.id === id || nav.name === id) {
523 return nav;
524 }
525 nav = nav.parent;
526 }
527 return null;
528}
529//# sourceMappingURL=deep-linker.js.map
\No newline at end of file