nav-controller-base.js 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. import { EventEmitter, Input, ReflectiveInjector } from '@angular/core';
  2. import { DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX, STATE_ATTACHED, STATE_DESTROYED, STATE_INITIALIZED, STATE_NEW, convertToViews, } from './nav-util';
  3. import { setZIndex } from './nav-util';
  4. import { isBlank, isNumber, isPresent, isTrueProperty } from '../util/util';
  5. import { ViewController, isViewController } from './view-controller';
  6. import { Ion } from '../components/ion';
  7. import { NavController } from './nav-controller';
  8. import { NavParams } from './nav-params';
  9. import { SwipeBackGesture } from './swipe-back';
  10. /**
  11. * @hidden
  12. * This class is for internal use only. It is not exported publicly.
  13. */
  14. export class NavControllerBase extends Ion {
  15. constructor(parent, _app, config, plt, elementRef, _zone, renderer, _cfr, _gestureCtrl, _trnsCtrl, _linker, _domCtrl, _errHandler) {
  16. super(config, elementRef, renderer);
  17. this.parent = parent;
  18. this._app = _app;
  19. this.config = config;
  20. this.plt = plt;
  21. this._zone = _zone;
  22. this._cfr = _cfr;
  23. this._gestureCtrl = _gestureCtrl;
  24. this._trnsCtrl = _trnsCtrl;
  25. this._linker = _linker;
  26. this._domCtrl = _domCtrl;
  27. this._errHandler = _errHandler;
  28. this._ids = -1;
  29. this._init = false;
  30. this._queue = [];
  31. this._trnsId = null;
  32. this._trnsTm = false;
  33. this._views = [];
  34. this._zIndexOffset = 0;
  35. this.viewDidLoad = new EventEmitter();
  36. this.viewWillEnter = new EventEmitter();
  37. this.viewDidEnter = new EventEmitter();
  38. this.viewWillLeave = new EventEmitter();
  39. this.viewDidLeave = new EventEmitter();
  40. this.viewWillUnload = new EventEmitter();
  41. this._sbEnabled = config.getBoolean('swipeBackEnabled');
  42. this._children = [];
  43. this.id = 'n' + (++ctrlIds);
  44. this._destroyed = false;
  45. }
  46. get swipeBackEnabled() {
  47. return this._sbEnabled;
  48. }
  49. set swipeBackEnabled(val) {
  50. this._sbEnabled = isTrueProperty(val);
  51. this._swipeBackCheck();
  52. }
  53. push(page, params, opts, done) {
  54. return this._queueTrns({
  55. insertStart: -1,
  56. insertViews: [{ page: page, params: params }],
  57. opts: opts,
  58. }, done);
  59. }
  60. insert(insertIndex, page, params, opts, done) {
  61. return this._queueTrns({
  62. insertStart: insertIndex,
  63. insertViews: [{ page: page, params: params }],
  64. opts: opts,
  65. }, done);
  66. }
  67. insertPages(insertIndex, insertPages, opts, done) {
  68. return this._queueTrns({
  69. insertStart: insertIndex,
  70. insertViews: insertPages,
  71. opts: opts,
  72. }, done);
  73. }
  74. pop(opts, done) {
  75. return this._queueTrns({
  76. removeStart: -1,
  77. removeCount: 1,
  78. opts: opts,
  79. }, done);
  80. }
  81. popTo(indexOrViewCtrl, opts, done) {
  82. let config = {
  83. removeStart: -1,
  84. removeCount: -1,
  85. opts: opts
  86. };
  87. if (isViewController(indexOrViewCtrl)) {
  88. config.removeView = indexOrViewCtrl;
  89. config.removeStart = 1;
  90. }
  91. else if (isNumber(indexOrViewCtrl)) {
  92. config.removeStart = indexOrViewCtrl + 1;
  93. }
  94. return this._queueTrns(config, done);
  95. }
  96. popToRoot(opts, done) {
  97. return this._queueTrns({
  98. removeStart: 1,
  99. removeCount: -1,
  100. opts: opts,
  101. }, done);
  102. }
  103. popAll() {
  104. let promises = [];
  105. for (var i = this._views.length - 1; i >= 0; i--) {
  106. promises.push(this.pop(null));
  107. }
  108. return Promise.all(promises);
  109. }
  110. remove(startIndex, removeCount = 1, opts, done) {
  111. return this._queueTrns({
  112. removeStart: startIndex,
  113. removeCount: removeCount,
  114. opts: opts,
  115. }, done);
  116. }
  117. removeView(viewController, opts, done) {
  118. return this._queueTrns({
  119. removeView: viewController,
  120. removeStart: 0,
  121. removeCount: 1,
  122. opts: opts,
  123. }, done);
  124. }
  125. setRoot(pageOrViewCtrl, params, opts, done) {
  126. return this.setPages([{ page: pageOrViewCtrl, params: params }], opts, done);
  127. }
  128. setPages(viewControllers, opts, done) {
  129. if (isBlank(opts)) {
  130. opts = {};
  131. }
  132. // if animation wasn't set to true then default it to NOT animate
  133. if (opts.animate !== true) {
  134. opts.animate = false;
  135. }
  136. return this._queueTrns({
  137. insertStart: 0,
  138. insertViews: viewControllers,
  139. removeStart: 0,
  140. removeCount: -1,
  141. opts: opts
  142. }, done);
  143. }
  144. // _queueTrns() adds a navigation stack change to the queue and schedules it to run:
  145. // 1. _nextTrns(): consumes the next transition in the queue
  146. // 2. _viewInit(): initializes enteringView if required
  147. // 3. _viewTest(): ensures canLeave/canEnter returns true, so the operation can continue
  148. // 4. _postViewInit(): add/remove the views from the navigation stack
  149. // 5. _transitionInit(): initializes the visual transition if required and schedules it to run
  150. // 6. _viewAttachToDOM(): attaches the enteringView to the DOM
  151. // 7. _transitionStart(): called once the transition actually starts, it initializes the Animation underneath.
  152. // 8. _transitionFinish(): called once the transition finishes
  153. // 9. _cleanup(): syncs the navigation internal state with the DOM. For example it removes the pages from the DOM or hides/show them.
  154. _queueTrns(ti, done) {
  155. const promise = new Promise((resolve, reject) => {
  156. ti.resolve = resolve;
  157. ti.reject = reject;
  158. });
  159. ti.done = done;
  160. // Normalize empty
  161. if (ti.insertViews && ti.insertViews.length === 0) {
  162. ti.insertViews = undefined;
  163. }
  164. // Enqueue transition instruction
  165. this._queue.push(ti);
  166. // if there isn't a transition already happening
  167. // then this will kick off this transition
  168. this._nextTrns();
  169. return promise;
  170. }
  171. _success(result, ti) {
  172. if (this._queue === null) {
  173. this._fireError('nav controller was destroyed', ti);
  174. return;
  175. }
  176. this._init = true;
  177. this._trnsId = null;
  178. // ensure we're not transitioning here
  179. this.setTransitioning(false);
  180. this._swipeBackCheck();
  181. // let's see if there's another to kick off
  182. this._nextTrns();
  183. if (ti.done) {
  184. ti.done(result.hasCompleted, result.requiresTransition, result.enteringName, result.leavingName, result.direction);
  185. }
  186. ti.resolve(result.hasCompleted);
  187. }
  188. _failed(rejectReason, ti) {
  189. if (this._queue === null) {
  190. this._fireError('nav controller was destroyed', ti);
  191. return;
  192. }
  193. this._trnsId = null;
  194. this._queue.length = 0;
  195. // let's see if there's another to kick off
  196. this.setTransitioning(false);
  197. this._swipeBackCheck();
  198. this._nextTrns();
  199. this._fireError(rejectReason, ti);
  200. }
  201. _fireError(rejectReason, ti) {
  202. if (ti.done) {
  203. ti.done(false, false, rejectReason);
  204. }
  205. if (ti.reject && !this._destroyed) {
  206. ti.reject(rejectReason);
  207. }
  208. else {
  209. ti.resolve(false);
  210. }
  211. }
  212. _nextTrns() {
  213. // this is the framework's bread 'n butta function
  214. // only one transition is allowed at any given time
  215. if (this.isTransitioning()) {
  216. return false;
  217. }
  218. // there is no transition happening right now
  219. // get the next instruction
  220. const ti = this._queue.shift();
  221. if (!ti) {
  222. return false;
  223. }
  224. // set that this nav is actively transitioning
  225. let enteringView;
  226. let leavingView;
  227. this._startTI(ti)
  228. .then(() => this._loadLazyLoading(ti))
  229. .then(() => {
  230. leavingView = this.getActive();
  231. enteringView = this._getEnteringView(ti, leavingView);
  232. if (!leavingView && !enteringView) {
  233. throw 'no views in the stack to be removed';
  234. }
  235. if (enteringView && enteringView._state === STATE_NEW) {
  236. this._viewInit(enteringView);
  237. }
  238. // Needs transition?
  239. ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
  240. })
  241. .then(() => this._viewTest(enteringView, leavingView, ti))
  242. .then(() => this._postViewInit(enteringView, leavingView, ti))
  243. .then(() => this._transition(enteringView, leavingView, ti))
  244. .then((result) => this._success(result, ti))
  245. .catch((rejectReason) => this._failed(rejectReason, ti));
  246. return true;
  247. }
  248. _startTI(ti) {
  249. const viewsLength = this._views.length;
  250. if (isPresent(ti.removeView)) {
  251. (void 0) /* assert */;
  252. (void 0) /* assert */;
  253. const index = this.indexOf(ti.removeView);
  254. if (index < 0) {
  255. return Promise.reject('removeView was not found');
  256. }
  257. ti.removeStart += index;
  258. }
  259. if (isPresent(ti.removeStart)) {
  260. if (ti.removeStart < 0) {
  261. ti.removeStart = (viewsLength - 1);
  262. }
  263. if (ti.removeCount < 0) {
  264. ti.removeCount = (viewsLength - ti.removeStart);
  265. }
  266. ti.leavingRequiresTransition = (ti.removeCount > 0) && ((ti.removeStart + ti.removeCount) === viewsLength);
  267. }
  268. if (ti.insertViews) {
  269. // allow -1 to be passed in to auto push it on the end
  270. // and clean up the index if it's larger then the size of the stack
  271. if (ti.insertStart < 0 || ti.insertStart > viewsLength) {
  272. ti.insertStart = viewsLength;
  273. }
  274. ti.enteringRequiresTransition = (ti.insertStart === viewsLength);
  275. }
  276. this.setTransitioning(true);
  277. return Promise.resolve();
  278. }
  279. _loadLazyLoading(ti) {
  280. const insertViews = ti.insertViews;
  281. if (insertViews) {
  282. (void 0) /* assert */;
  283. return convertToViews(this._linker, insertViews).then((viewControllers) => {
  284. (void 0) /* assert */;
  285. viewControllers = viewControllers.filter(v => v !== null);
  286. if (viewControllers.length === 0) {
  287. throw 'invalid views to insert';
  288. }
  289. // Check all the inserted view are correct
  290. for (var i = 0; i < viewControllers.length; i++) {
  291. var view = viewControllers[i];
  292. var nav = view._nav;
  293. if (nav && nav !== this) {
  294. throw 'inserted view was already inserted';
  295. }
  296. if (view._state === STATE_DESTROYED) {
  297. throw 'inserted view was already destroyed';
  298. }
  299. }
  300. ti.insertViews = viewControllers;
  301. });
  302. }
  303. return Promise.resolve();
  304. }
  305. _getEnteringView(ti, leavingView) {
  306. const insertViews = ti.insertViews;
  307. if (insertViews) {
  308. // grab the very last view of the views to be inserted
  309. // and initialize it as the new entering view
  310. return insertViews[insertViews.length - 1];
  311. }
  312. const removeStart = ti.removeStart;
  313. if (isPresent(removeStart)) {
  314. var views = this._views;
  315. var removeEnd = removeStart + ti.removeCount;
  316. var i;
  317. var view;
  318. for (i = views.length - 1; i >= 0; i--) {
  319. view = views[i];
  320. if ((i < removeStart || i >= removeEnd) && view !== leavingView) {
  321. return view;
  322. }
  323. }
  324. }
  325. return null;
  326. }
  327. _postViewInit(enteringView, leavingView, ti) {
  328. (void 0) /* assert */;
  329. (void 0) /* assert */;
  330. (void 0) /* assert */;
  331. const opts = ti.opts || {};
  332. const insertViews = ti.insertViews;
  333. const removeStart = ti.removeStart;
  334. const removeCount = ti.removeCount;
  335. let view;
  336. let i;
  337. let destroyQueue;
  338. // there are views to remove
  339. if (isPresent(removeStart)) {
  340. (void 0) /* assert */;
  341. (void 0) /* assert */;
  342. destroyQueue = [];
  343. for (i = 0; i < removeCount; i++) {
  344. view = this._views[i + removeStart];
  345. if (view && view !== enteringView && view !== leavingView) {
  346. destroyQueue.push(view);
  347. }
  348. }
  349. // default the direction to "back"
  350. opts.direction = opts.direction || DIRECTION_BACK;
  351. }
  352. const finalBalance = this._views.length + (insertViews ? insertViews.length : 0) - (removeCount ? removeCount : 0);
  353. (void 0) /* assert */;
  354. if (finalBalance === 0 && !this._isPortal) {
  355. console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`, this, this.getNativeElement());
  356. throw 'navigation stack needs at least one root page';
  357. }
  358. // At this point the transition can not be rejected, any throw should be an error
  359. // there are views to insert
  360. if (insertViews) {
  361. // manually set the new view's id if an id was passed in the options
  362. if (isPresent(opts.id)) {
  363. enteringView.id = opts.id;
  364. }
  365. // add the views to the
  366. for (i = 0; i < insertViews.length; i++) {
  367. view = insertViews[i];
  368. this._insertViewAt(view, ti.insertStart + i);
  369. }
  370. if (ti.enteringRequiresTransition) {
  371. // default to forward if not already set
  372. opts.direction = opts.direction || DIRECTION_FORWARD;
  373. }
  374. }
  375. // if the views to be removed are in the beginning or middle
  376. // and there is not a view that needs to visually transition out
  377. // then just destroy them and don't transition anything
  378. // batch all of lifecycles together
  379. // let's make sure, callbacks are zoned
  380. if (destroyQueue && destroyQueue.length > 0) {
  381. this._zone.run(() => {
  382. for (i = 0; i < destroyQueue.length; i++) {
  383. view = destroyQueue[i];
  384. this._willLeave(view, true);
  385. this._didLeave(view);
  386. this._willUnload(view);
  387. }
  388. });
  389. // once all lifecycle events has been delivered, we can safely detroy the views
  390. for (i = 0; i < destroyQueue.length; i++) {
  391. this._destroyView(destroyQueue[i]);
  392. }
  393. }
  394. // set which animation it should use if it wasn't set yet
  395. if (ti.requiresTransition && !opts.animation) {
  396. if (isPresent(ti.removeStart)) {
  397. opts.animation = (leavingView || enteringView).getTransitionName(opts.direction);
  398. }
  399. else {
  400. opts.animation = (enteringView || leavingView).getTransitionName(opts.direction);
  401. }
  402. }
  403. ti.opts = opts;
  404. }
  405. /**
  406. * DOM WRITE
  407. */
  408. _viewInit(enteringView) {
  409. (void 0) /* assert */;
  410. (void 0) /* assert */;
  411. // render the entering view, and all child navs and views
  412. // entering view has not been initialized yet
  413. const componentProviders = ReflectiveInjector.resolve([
  414. { provide: NavController, useValue: this },
  415. { provide: ViewController, useValue: enteringView },
  416. { provide: NavParams, useValue: enteringView.getNavParams() }
  417. ]);
  418. const componentFactory = this._linker.resolveComponent(enteringView.component);
  419. const childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, this._viewport.parentInjector);
  420. // create ComponentRef and set it to the entering view
  421. enteringView.init(componentFactory.create(childInjector, []));
  422. enteringView._state = STATE_INITIALIZED;
  423. this._preLoad(enteringView);
  424. }
  425. _viewAttachToDOM(view, componentRef, viewport) {
  426. (void 0) /* assert */;
  427. (void 0) /* assert */;
  428. // fire willLoad before change detection runs
  429. this._willLoad(view);
  430. // render the component ref instance to the DOM
  431. // ******** DOM WRITE ****************
  432. viewport.insert(componentRef.hostView, viewport.length);
  433. view._state = STATE_ATTACHED;
  434. if (view._cssClass) {
  435. // the ElementRef of the actual ion-page created
  436. var pageElement = componentRef.location.nativeElement;
  437. // ******** DOM WRITE ****************
  438. this._renderer.setElementClass(pageElement, view._cssClass, true);
  439. }
  440. componentRef.changeDetectorRef.detectChanges();
  441. // successfully finished loading the entering view
  442. // fire off the "didLoad" lifecycle events
  443. this._zone.run(this._didLoad.bind(this, view));
  444. }
  445. _viewTest(enteringView, leavingView, ti) {
  446. // Only test canLeave/canEnter if there is transition
  447. if (!ti.requiresTransition) {
  448. return Promise.resolve();
  449. }
  450. const promises = [];
  451. if (leavingView) {
  452. promises.push(leavingView._lifecycleTest('Leave'));
  453. }
  454. if (enteringView) {
  455. promises.push(enteringView._lifecycleTest('Enter'));
  456. }
  457. if (promises.length === 0) {
  458. return Promise.resolve();
  459. }
  460. // darn, async promises, gotta wait for them to resolve
  461. return Promise.all(promises).then((values) => {
  462. if (values.some(result => result === false)) {
  463. throw 'canEnter/Leave returned false';
  464. }
  465. }).catch((reason) => {
  466. // Do not
  467. ti.reject = null;
  468. throw reason;
  469. });
  470. }
  471. _transition(enteringView, leavingView, ti) {
  472. if (!ti.requiresTransition) {
  473. // transition is not required, so we are already done!
  474. // they're inserting/removing the views somewhere in the middle or
  475. // beginning, so visually nothing needs to animate/transition
  476. // resolve immediately because there's no animation that's happening
  477. return Promise.resolve({
  478. hasCompleted: true,
  479. requiresTransition: false
  480. });
  481. }
  482. const opts = ti.opts;
  483. // figure out if this transition is the root one or a
  484. // child of a parent nav that has the root transition
  485. this._trnsId = this._trnsCtrl.getRootTrnsId(this);
  486. if (this._trnsId === null) {
  487. // this is the root transition, meaning all child navs and their views
  488. // should be added as a child transition to this one
  489. this._trnsId = this._trnsCtrl.nextId();
  490. }
  491. // create the transition options
  492. const animationOpts = {
  493. animation: opts.animation,
  494. direction: opts.direction,
  495. duration: (opts.animate === false ? 0 : opts.duration),
  496. easing: opts.easing,
  497. isRTL: this._config.plt.isRTL,
  498. ev: opts.ev,
  499. };
  500. // create the transition animation from the TransitionController
  501. // this will either create the root transition, or add it as a child transition
  502. const transition = this._trnsCtrl.get(this._trnsId, enteringView, leavingView, animationOpts);
  503. // ensure any swipeback transitions are cleared out
  504. this._sbTrns && this._sbTrns.destroy();
  505. this._sbTrns = null;
  506. // swipe to go back root transition
  507. if (transition.isRoot() && opts.progressAnimation) {
  508. this._sbTrns = transition;
  509. }
  510. // transition start has to be registered before attaching the view to the DOM!
  511. const promise = new Promise(resolve => transition.registerStart(resolve)).then(() => {
  512. return this._transitionStart(transition, enteringView, leavingView, opts);
  513. });
  514. if (enteringView && (enteringView._state === STATE_INITIALIZED)) {
  515. // render the entering component in the DOM
  516. // this would also render new child navs/views
  517. // which may have their very own async canEnter/Leave tests
  518. // ******** DOM WRITE ****************
  519. this._viewAttachToDOM(enteringView, enteringView._cmp, this._viewport);
  520. }
  521. if (!transition.hasChildren) {
  522. // lowest level transition, so kick it off and let it bubble up to start all of them
  523. transition.start();
  524. }
  525. return promise;
  526. }
  527. _transitionStart(transition, enteringView, leavingView, opts) {
  528. (void 0) /* assert */;
  529. this._trnsId = null;
  530. // set the correct zIndex for the entering and leaving views
  531. // ******** DOM WRITE ****************
  532. setZIndex(this, enteringView, leavingView, opts.direction, this._renderer);
  533. // always ensure the entering view is viewable
  534. // ******** DOM WRITE ****************
  535. enteringView && enteringView._domShow(true, this._renderer);
  536. // always ensure the leaving view is viewable
  537. // ******** DOM WRITE ****************
  538. leavingView && leavingView._domShow(true, this._renderer);
  539. // initialize the transition
  540. transition.init();
  541. // we should animate (duration > 0) if the pushed page is not the first one (startup)
  542. // or if it is a portal (modal, actionsheet, etc.)
  543. const isFirstPage = !this._init && this._views.length === 1;
  544. const shouldNotAnimate = isFirstPage && !this._isPortal;
  545. const canNotAnimate = this._config.get('animate') === false;
  546. if (shouldNotAnimate || canNotAnimate) {
  547. opts.animate = false;
  548. }
  549. if (opts.animate === false) {
  550. // if it was somehow set to not animation, then make the duration zero
  551. transition.duration(0);
  552. }
  553. // create a callback that needs to run within zone
  554. // that will fire off the willEnter/Leave lifecycle events at the right time
  555. transition.beforeAddRead(this._viewsWillLifecycles.bind(this, enteringView, leavingView));
  556. // get the set duration of this transition
  557. const duration = transition.getDuration();
  558. // create a callback for when the animation is done
  559. const promise = new Promise(resolve => {
  560. transition.onFinish(resolve);
  561. });
  562. if (transition.isRoot()) {
  563. // this is the top most, or only active transition, so disable the app
  564. // add XXms to the duration the app is disabled when the keyboard is open
  565. if (duration > DISABLE_APP_MINIMUM_DURATION && opts.disableApp !== false) {
  566. // if this transition has a duration and this is the root transition
  567. // then set that the app is actively disabled
  568. this._app.setEnabled(false, duration + ACTIVE_TRANSITION_OFFSET, opts.minClickBlockDuration);
  569. }
  570. else {
  571. (void 0) /* console.debug */;
  572. }
  573. // cool, let's do this, start the transition
  574. if (opts.progressAnimation) {
  575. // this is a swipe to go back, just get the transition progress ready
  576. // kick off the swipe animation start
  577. transition.progressStart();
  578. }
  579. else {
  580. // only the top level transition should actually start "play"
  581. // kick it off and let it play through
  582. // ******** DOM WRITE ****************
  583. transition.play();
  584. }
  585. }
  586. return promise.then(() => this._zone.run(() => {
  587. return this._transitionFinish(transition, opts);
  588. }));
  589. }
  590. _transitionFinish(transition, opts) {
  591. const hasCompleted = transition.hasCompleted;
  592. const enteringView = transition.enteringView;
  593. const leavingView = transition.leavingView;
  594. // mainly for testing
  595. let enteringName;
  596. let leavingName;
  597. if (hasCompleted) {
  598. // transition has completed (went from 0 to 1)
  599. if (enteringView) {
  600. enteringName = enteringView.name;
  601. this._didEnter(enteringView);
  602. }
  603. if (leavingView) {
  604. leavingName = leavingView.name;
  605. this._didLeave(leavingView);
  606. }
  607. this._cleanup(enteringView);
  608. }
  609. else {
  610. // If transition does not complete, we have to cleanup anyway, because
  611. // previous pages in the stack are not hidden probably.
  612. this._cleanup(leavingView);
  613. }
  614. if (transition.isRoot()) {
  615. // this is the root transition
  616. // it's safe to destroy this transition
  617. this._trnsCtrl.destroy(transition.trnsId);
  618. // it's safe to enable the app again
  619. this._app.setEnabled(true);
  620. // mark ourselves as not transitioning - `deepLinker navchange` requires this
  621. // TODO - probably could be resolved in a better way
  622. this.setTransitioning(false);
  623. if (!this.hasChildren() && opts.updateUrl !== false) {
  624. // notify deep linker of the nav change
  625. // if a direction was provided and should update url
  626. this._linker.navChange(opts.direction);
  627. }
  628. if (opts.keyboardClose !== false) {
  629. // the keyboard is still open!
  630. // no problem, let's just close for them
  631. this.plt.focusOutActiveElement();
  632. }
  633. }
  634. return {
  635. hasCompleted: hasCompleted,
  636. requiresTransition: true,
  637. enteringName: enteringName,
  638. leavingName: leavingName,
  639. direction: opts.direction
  640. };
  641. }
  642. _viewsWillLifecycles(enteringView, leavingView) {
  643. if (enteringView || leavingView) {
  644. this._zone.run(() => {
  645. // Here, the order is important. WillLeave must be called before WillEnter.
  646. if (leavingView) {
  647. const willUnload = enteringView ? leavingView.index > enteringView.index : true;
  648. this._willLeave(leavingView, willUnload);
  649. }
  650. enteringView && this._willEnter(enteringView);
  651. });
  652. }
  653. }
  654. _insertViewAt(view, index) {
  655. const existingIndex = this._views.indexOf(view);
  656. if (existingIndex > -1) {
  657. // this view is already in the stack!!
  658. // move it to its new location
  659. (void 0) /* assert */;
  660. this._views.splice(index, 0, this._views.splice(existingIndex, 1)[0]);
  661. }
  662. else {
  663. (void 0) /* assert */;
  664. // this is a new view to add to the stack
  665. // create the new entering view
  666. view._setNav(this);
  667. // give this inserted view an ID
  668. this._ids++;
  669. if (!view.id) {
  670. view.id = `${this.id}-${this._ids}`;
  671. }
  672. // insert the entering view into the correct index in the stack
  673. this._views.splice(index, 0, view);
  674. }
  675. }
  676. _removeView(view) {
  677. (void 0) /* assert */;
  678. const views = this._views;
  679. const index = views.indexOf(view);
  680. (void 0) /* assert */;
  681. if (index >= 0) {
  682. views.splice(index, 1);
  683. }
  684. }
  685. _destroyView(view) {
  686. view._destroy(this._renderer);
  687. this._removeView(view);
  688. }
  689. /**
  690. * DOM WRITE
  691. */
  692. _cleanup(activeView) {
  693. // ok, cleanup time!! Destroy all of the views that are
  694. // INACTIVE and come after the active view
  695. // only do this if the views exist, though
  696. if (!this._destroyed) {
  697. const activeViewIndex = this._views.indexOf(activeView);
  698. const views = this._views;
  699. let reorderZIndexes = false;
  700. let view;
  701. let i;
  702. for (i = views.length - 1; i >= 0; i--) {
  703. view = views[i];
  704. if (i > activeViewIndex) {
  705. // this view comes after the active view
  706. // let's unload it
  707. this._willUnload(view);
  708. this._destroyView(view);
  709. }
  710. else if (i < activeViewIndex && !this._isPortal) {
  711. // this view comes before the active view
  712. // and it is not a portal then ensure it is hidden
  713. view._domShow(false, this._renderer);
  714. }
  715. if (view._zIndex <= 0) {
  716. reorderZIndexes = true;
  717. }
  718. }
  719. if (!this._isPortal && reorderZIndexes) {
  720. for (i = 0; i < views.length; i++) {
  721. view = views[i];
  722. // ******** DOM WRITE ****************
  723. view._setZIndex(view._zIndex + INIT_ZINDEX + 1, this._renderer);
  724. }
  725. }
  726. }
  727. }
  728. _preLoad(view) {
  729. (void 0) /* assert */;
  730. view._preLoad();
  731. }
  732. _willLoad(view) {
  733. (void 0) /* assert */;
  734. try {
  735. view._willLoad();
  736. }
  737. catch (e) {
  738. this._errHandler && this._errHandler.handleError(e);
  739. }
  740. }
  741. _didLoad(view) {
  742. (void 0) /* assert */;
  743. (void 0) /* assert */;
  744. try {
  745. view._didLoad();
  746. this.viewDidLoad.emit(view);
  747. this._app.viewDidLoad.emit(view);
  748. }
  749. catch (e) {
  750. this._errHandler && this._errHandler.handleError(e);
  751. }
  752. }
  753. _willEnter(view) {
  754. (void 0) /* assert */;
  755. (void 0) /* assert */;
  756. try {
  757. view._willEnter();
  758. this.viewWillEnter.emit(view);
  759. this._app.viewWillEnter.emit(view);
  760. }
  761. catch (e) {
  762. this._errHandler && this._errHandler.handleError(e);
  763. }
  764. }
  765. _didEnter(view) {
  766. (void 0) /* assert */;
  767. (void 0) /* assert */;
  768. try {
  769. view._didEnter();
  770. this.viewDidEnter.emit(view);
  771. this._app.viewDidEnter.emit(view);
  772. }
  773. catch (e) {
  774. this._errHandler && this._errHandler.handleError(e);
  775. }
  776. }
  777. _willLeave(view, willUnload) {
  778. (void 0) /* assert */;
  779. (void 0) /* assert */;
  780. try {
  781. view._willLeave(willUnload);
  782. this.viewWillLeave.emit(view);
  783. this._app.viewWillLeave.emit(view);
  784. }
  785. catch (e) {
  786. this._errHandler && this._errHandler.handleError(e);
  787. }
  788. }
  789. _didLeave(view) {
  790. (void 0) /* assert */;
  791. (void 0) /* assert */;
  792. try {
  793. view._didLeave();
  794. this.viewDidLeave.emit(view);
  795. this._app.viewDidLeave.emit(view);
  796. }
  797. catch (e) {
  798. this._errHandler && this._errHandler.handleError(e);
  799. }
  800. }
  801. _willUnload(view) {
  802. (void 0) /* assert */;
  803. (void 0) /* assert */;
  804. try {
  805. view._willUnload();
  806. this.viewWillUnload.emit(view);
  807. this._app.viewWillUnload.emit(view);
  808. }
  809. catch (e) {
  810. this._errHandler && this._errHandler.handleError(e);
  811. }
  812. }
  813. hasChildren() {
  814. return this._children && this._children.length > 0;
  815. }
  816. getActiveChildNavs() {
  817. return this._children;
  818. }
  819. getAllChildNavs() {
  820. return this._children;
  821. }
  822. registerChildNav(container) {
  823. this._children.push(container);
  824. }
  825. unregisterChildNav(nav) {
  826. this._children = this._children.filter(child => child !== nav);
  827. }
  828. destroy() {
  829. const views = this._views;
  830. let view;
  831. for (var i = 0; i < views.length; i++) {
  832. view = views[i];
  833. view._willUnload();
  834. view._destroy(this._renderer);
  835. }
  836. // release swipe back gesture and transition
  837. this._sbGesture && this._sbGesture.destroy();
  838. this._sbTrns && this._sbTrns.destroy();
  839. this._queue = this._views = this._sbGesture = this._sbTrns = null;
  840. // Unregister navcontroller
  841. if (this.parent && this.parent.unregisterChildNav) {
  842. this.parent.unregisterChildNav(this);
  843. }
  844. else if (this._app) {
  845. this._app.unregisterRootNav(this);
  846. }
  847. this._destroyed = true;
  848. }
  849. swipeBackStart() {
  850. if (this.isTransitioning() || this._queue.length > 0) {
  851. return;
  852. }
  853. // default the direction to "back";
  854. const opts = {
  855. direction: DIRECTION_BACK,
  856. progressAnimation: true
  857. };
  858. this._queueTrns({
  859. removeStart: -1,
  860. removeCount: 1,
  861. opts: opts,
  862. }, null);
  863. }
  864. swipeBackProgress(stepValue) {
  865. if (this._sbTrns && this._sbGesture) {
  866. // continue to disable the app while actively dragging
  867. this._app.setEnabled(false, ACTIVE_TRANSITION_DEFAULT);
  868. this.setTransitioning(true);
  869. // set the transition animation's progress
  870. this._sbTrns.progressStep(stepValue);
  871. }
  872. }
  873. swipeBackEnd(shouldComplete, currentStepValue, velocity) {
  874. if (this._sbTrns && this._sbGesture) {
  875. // the swipe back gesture has ended
  876. var dur = this._sbTrns.getDuration() / (Math.abs(velocity) + 1);
  877. this._sbTrns.progressEnd(shouldComplete, currentStepValue, dur);
  878. }
  879. }
  880. _swipeBackCheck() {
  881. if (this.canSwipeBack()) {
  882. if (!this._sbGesture) {
  883. this._sbGesture = new SwipeBackGesture(this.plt, this, this._gestureCtrl, this._domCtrl);
  884. }
  885. this._sbGesture.listen();
  886. }
  887. else if (this._sbGesture) {
  888. this._sbGesture.unlisten();
  889. }
  890. }
  891. canSwipeBack() {
  892. return (this._sbEnabled &&
  893. !this._isPortal &&
  894. !this._children.length &&
  895. !this.isTransitioning() &&
  896. this._app.isEnabled() &&
  897. this.canGoBack());
  898. }
  899. canGoBack() {
  900. const activeView = this.getActive();
  901. return !!(activeView && activeView.enableBack());
  902. }
  903. isTransitioning() {
  904. return this._trnsTm;
  905. }
  906. setTransitioning(isTransitioning) {
  907. this._trnsTm = isTransitioning;
  908. }
  909. getActive() {
  910. return this._views[this._views.length - 1];
  911. }
  912. isActive(view) {
  913. return (view === this.getActive());
  914. }
  915. getByIndex(index) {
  916. return this._views[index];
  917. }
  918. getPrevious(view) {
  919. // returns the view controller which is before the given view controller.
  920. if (!view) {
  921. view = this.getActive();
  922. }
  923. const views = this._views;
  924. const index = views.indexOf(view);
  925. return (index > 0) ? views[index - 1] : null;
  926. }
  927. first() {
  928. // returns the first view controller in this nav controller's stack.
  929. return this._views[0];
  930. }
  931. last() {
  932. // returns the last page in this nav controller's stack.
  933. const views = this._views;
  934. return views[views.length - 1];
  935. }
  936. indexOf(view) {
  937. // returns the index number of the given view controller.
  938. return this._views.indexOf(view);
  939. }
  940. length() {
  941. return this._views.length;
  942. }
  943. getViews() {
  944. return this._views;
  945. }
  946. /**
  947. * Return a view controller
  948. */
  949. getViewById(id) {
  950. for (const vc of this._views) {
  951. if (vc && vc.id === id) {
  952. return vc;
  953. }
  954. }
  955. return null;
  956. }
  957. isSwipeBackEnabled() {
  958. return this._sbEnabled;
  959. }
  960. dismissPageChangeViews() {
  961. for (let view of this._views) {
  962. if (view.data && view.data.dismissOnPageChange) {
  963. view.dismiss().catch(() => { });
  964. }
  965. }
  966. }
  967. setViewport(val) {
  968. this._viewport = val;
  969. }
  970. resize() {
  971. const active = this.getActive();
  972. if (!active) {
  973. return;
  974. }
  975. const content = active.getIONContent();
  976. content && content.resize();
  977. }
  978. goToRoot(_opts) {
  979. return Promise.reject(new Error('goToRoot needs to be implemented by child class'));
  980. }
  981. /*
  982. * @private
  983. */
  984. getType() {
  985. return 'nav';
  986. }
  987. /*
  988. * @private
  989. */
  990. getSecondaryIdentifier() {
  991. return null;
  992. }
  993. /**
  994. * Returns the active child navigation.
  995. */
  996. getActiveChildNav() {
  997. console.warn('(getActiveChildNav) is deprecated and will be removed in the next major release. Use getActiveChildNavs instead.');
  998. return this._children[this._children.length - 1];
  999. }
  1000. }
  1001. NavControllerBase.propDecorators = {
  1002. 'swipeBackEnabled': [{ type: Input },],
  1003. };
  1004. let ctrlIds = -1;
  1005. const DISABLE_APP_MINIMUM_DURATION = 64;
  1006. const ACTIVE_TRANSITION_DEFAULT = 5000;
  1007. const ACTIVE_TRANSITION_OFFSET = 2000;
  1008. //# sourceMappingURL=nav-controller-base.js.map