<commit_before>import {Compiler, ElementRef, Injector, provide, NgZone, DynamicComponentLoader, AppViewManager, Renderer} from 'angular2/angular2';

import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {Keyboard} from '../../util/keyboard';
import {ViewController} from './view-controller';
import {Animation} from '../../animations/animation';
import {SwipeBackGesture} from './swipe-back';
import {isBoolean, array} from '../../util/util';
import {rafFrames} from '../../util/dom';

/**
 * _For examples on the basic usage of NavController, check out the [Navigation section](../../../../components/#navigation)
 * of the Component docs._
 *
 * NavController is the base class for navigation controller components like
 * [`Nav`](../Nav/) and [`Tab`](../../Tabs/Tab/). You use navigation controllers
 * to navigate to [pages](#creating_pages) in your app. At a basic level, a
 * navigation controller is an array of pages representing a particular history
 * (of a Tab for example). This array can be manipulated to navigate throughout
 * an app by pushing and popping pages or inserting and removing them at
 * arbitrary locations in history.
 *
 * The current page is the last one in the array, or the top of the stack if we
 * think of it that way.  [Pushing](#push) a new page onto the top of the
 * navigation stack causes the new page to be animated in, while [popping](#pop)
 * the current page will navigate to the previous page in the stack.
 *
 * Unless you are using a directive like [NavPush](../NavPush/), or need a
 * specific NavController, most times you will inject and use a reference to the
 * nearest NavController to manipulate the navigation stack.
 *
 * <h3 id="injecting_nav_controller">Injecting NavController</h3>
 * Injecting NavController will always get you an instance of the nearest
 * NavController, regardless of whether it is a Tab or a Nav.
 *
 * Behind the scenes, when Ionic instantiates a new NavController, it creates an
 * injector with NavController bound to that instance (usually either a Nav or
 * Tab) and adds the injector to its own providers.  For more information on
 * providers and dependency injection, see [Providers and DI]().
 *
 * ```ts
 * // class NavController
 * this.providers = Injector.resolve([
 *   provide(NavController, {useValue: this})
 * ]);
 * ```
 *
 * Instead, you can inject NavController and know that it is the correct
 * navigation controller for most situations (for more advanced situations, see
 * [Menu](../../Menu/Menu/) and [Tab](../../Tab/Tab/)).
 *
 * ```ts
 *  class MyComponent {
 *    constructor(nav: NavController) {
 *      this.nav = nav;
 *    }
 *  }
 * ```
 *
 * <h2 id="creating_pages">Page creation</h2>
 * _For more information on the `@Page` decorator see the [@Page API
 * reference](../../../config/Page/)._
 *
 * Pages are created when they are added to the navigation stack.  For methods
 * like [push()](#push), the NavController takes any component class that is
 * decorated with @Page as its first argument.  The NavController then
 * [compiles]() that component, adds it to the DOM in a similar fashion to
 * Angular's [DynamicComponentLoader](https://angular.io/docs/js/latest/api/core/DynamicComponentLoader-interface.html),
 * and animates it into view.
 *
 * By default, pages are cached and left in the DOM if they are navigated away
 * from but still in the navigation stack (the exiting page on a `push()` for
 * example).  They are destroyed when removed from the navigation stack (on
 * [pop()](#pop) or [setRoot()](#setRoot)).
 *
 *
 * <h2 id="Lifecycle">Lifecycle events</h2>
 * Lifecycle events are fired during various stages of navigation.  They can be
 * defined in any `@Page` decorated component class.
 *
 * ```ts
 * @Page({
 *   template: 'Hello World'
 * })
 * class HelloWorld {
 *   onPageLoaded() {
 *     console.log("I'm alive!");
 *   }
 * }
 * ```
 *
 * - `onPageLoaded` - Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The `onPageLoaded` event is good place to put your setup code for the page.
 * - `onPageWillEnter` - Runs when the page is about to enter and become the active page.
 * - `onPageDidEnter` - Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.
 * - `onPageWillLeave` - Runs when the page is about to leave and no longer be the active page.
 * - `onPageDidLeave` - Runs when the page has finished leaving and is no longer the active page.
 * - `onPageWillUnload` - Runs when the page is about to be destroyed and have its elements removed.
 * - `onPageDidUnload` - Runs after the page has been destroyed and its elements have been removed.
 *
 */
export class NavController extends Ion {

  constructor(
    parentnavCtrl: NavController,
    app: IonicApp,
    config: Config,
    keyboard: Keyboard,
    elementRef: ElementRef,
    compiler: Compiler,
    loader: DynamicComponentLoader,
    viewManager: AppViewManager,
    zone: NgZone,
    renderer: Renderer
  ) {
    super(elementRef, config);

    this.parent = parentnavCtrl;
    this.app = app;
    this.config = config;
    this.keyboard = keyboard;

    this._compiler = compiler;
    this._loader = loader;
    this._viewManager = viewManager;
    this._zone = zone;
    this._renderer = renderer;

    this._views = [];
    this._trnsTime = 0;

    this._sbTrans = null;
    this._sbEnabled = config.get('swipeBackEnabled') || false;
    this._sbThreshold = config.get('swipeBackThreshold') || 40;

    this.id = ++ctrlIds;
    this._ids = -1;

    // build a new injector for child ViewControllers to use
    this.providers = Injector.resolve([
      provide(NavController, {useValue: this})
    ]);
  }

  /**
   * Boolean if the nav controller is actively transitioning or not.
   * @return {bool}
   */
  isTransitioning() {
    return (this._trnsTime > Date.now());
  }

  setTransitioning(isTransitioning, fallback=700) {
    this._trnsTime = (isTransitioning ? Date.now() + fallback : 0);
  }

  /**
   * TODO
   * @name NavController#push
   * @param {Component} The name of the component you want to push on the navigation stack
   * @param {Component} [params={}] The name of the component you want to push on the navigation stack, plus additional data you want to pass as parameters
   * @param {Component} [opts={}]  The name of the component you want to push on the navigation stack, plus additional options for the transition
   * @returns {Promise} Returns a promise when the transition is completed
   */
  push(componentType, params = {}, opts = {}, callback) {
    if (!componentType) {
      return Promise.reject('invalid componentType to push');
    }

    if (typeof componentType !== 'function') {
      throw 'Loading component must be a component class, not "' + componentType.toString() + '"';
    }

    if (this.isTransitioning()) {
      console.debug('nav controller actively transitioning');
      return Promise.reject();
    }

    this.setTransitioning(true, 500);

    let promise = null;
    if (!callback) {
      promise = new Promise(res => { callback = res; });
    }

    // do not animate if this is the first in the stack
    if (!this._views.length && !opts.animateFirst) {
      opts.animate = false;
    }

    // default the direction to "forward"
    opts.direction = opts.direction || 'forward';

    // the active view is going to be the leaving one (if one exists)
    let leavingView = this.getActive() || new ViewController();
    leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true);
    leavingView.shouldDestroy = !leavingView.shouldCache;
    if (leavingView.shouldDestroy) {
      leavingView.willUnload();
    }

    // create a new ViewController
    let enteringView = new ViewController(this, componentType, params);
    enteringView.shouldDestroy = false;
    enteringView.shouldCache = false;
    enteringView.pageType = opts.pageType;
    enteringView.handle = opts.handle || null;

    // add the view to the stack
    this._add(enteringView);

    if (this.router) {
      // notify router of the state change
      this.router.stateChange('push', enteringView, params);
    }

    // start the transition
    this._transition(enteringView, leavingView, opts, callback);

    return promise;
  }

  /**
   * TODO
   * @name NavController#pop
   * @param {Object} [opts={}] Any additional option for the transition
   * @returns {Promise} Returns a promise when the transition is completed
   */
  pop(opts = {}) {
    if (!opts.animateFirst && !this.canGoBack()) {
      return Promise.reject();
    }

    if (this.isTransitioning()) {
      console.debug('nav controller actively transitioning');
      return Promise.reject();
    }

    this.setTransitioning(true, 500);

    let resolve;
    let promise = new Promise(res => { resolve = res; });

    // default the direction to "back"
    opts.direction = opts.direction || 'back';

    // get the active view and set that it is staged to be leaving
    // was probably the one popped from the stack
    let leavingView = this.getActive() || new ViewController();
    leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : false);
    leavingView.shouldDestroy = !leavingView.shouldCache;
    if (leavingView.shouldDestroy) {
      leavingView.willUnload();
    }

    // the entering view is now the new last view
    // Note: we might not have an entering view if this is the
    // only view on the history stack.
    let enteringView = this.getPrevious(leavingView);
    if (this.router) {
      // notify router of the state change
      this.router.stateChange('pop', enteringView);
    }

    // start the transition
    this._transition(enteringView, leavingView, opts, resolve);

    return promise;
  }

  /**
   * @private
   * Pop to a specific view in the history stack
   *
   * @param view {ViewController} to pop to
   * @param opts {object} pop options
   */
  popTo(viewCtrl, opts = {}) {

    // Get the target index of the view to pop to
    let viewIndex = this._views.indexOf(viewCtrl);
    let targetIndex = viewIndex + 1;

    // Don't pop to the view if it wasn't found, or the target is beyond the view list
    if (viewIndex < 0 || targetIndex > this._views.length - 1) {
      return Promise.resolve();
    }

    // ensure the entering view is shown
    this._renderView(viewCtrl, true);

    let resolve = null;
    let promise = new Promise(res => { resolve = res; });
    opts.direction = opts.direction || 'back';

    let leavingView = this.getActive() || new ViewController();

    // get the views to auto remove without having to do a transiton for each
    // the last view (the currently active one) will do a normal transition out
    if (this._views.length > 1) {
      let autoRemoveItems = this._views.slice(targetIndex, this._views.length);
      let popView;
      for (let i = 0; i < autoRemoveItems.length; i++) {
        popView = autoRemoveItems[i];
        popView.shouldDestroy = true;
        popView.shouldCache = false;
        popView.willUnload();

        // only the leaving view should be shown, all others hide
        this._renderView(popView, (popView === leavingView));
      }
    }

    if (this.router) {
      this.router.stateChange('pop', viewCtrl);
    }

    this._transition(viewCtrl, leavingView, opts, resolve);

    return promise;
  }

  /**
   * Pop to the root view.
   * @param opts extra animation options
   */
  popToRoot(opts = {}) {
    return this.popTo(this.first(), opts);
  }

  /**
   * Inserts a view into the nav stack at the specified index.
   * @param {Index} The index where you want to insert the view
   * @param {Component} The name of the component you want to insert into the nav stack
   * @returns {Promise} Returns a promise when the view has been inserted into the navigation stack
   */
  insert(index, componentType, params = {}, opts = {}) {
    if (!componentType || index < 0) {
      return Promise.reject();
    }

    // push it onto the end
    if (index >= this._views.length) {
      return this.push(componentType, params, opts);
    }

    // create new ViewController, but don't render yet
    let viewCtrl = new ViewController(this, componentType);
    viewCtrl.state = CACHED_STATE;
    viewCtrl.shouldDestroy = false;
    viewCtrl.shouldCache = false;

    this._incrementId(viewCtrl);
    this._views.splice(index, 0, viewCtrl);
    return Promise.resolve();
  }

  /**
   * Removes a view from the nav stack at the specified index.
   * @param {Index} Remove the view from the nav stack at that index
   * @returns {Promise} Returns a promise when the view has been removed<commit_msg>fix(navController): pass params to insert<commit_after>import {Compiler, ElementRef, Injector, provide, NgZone, DynamicComponentLoader, AppViewManager, Renderer} from 'angular2/angular2';

import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {Keyboard} from '../../util/keyboard';
import {ViewController} from './view-controller';
import {Animation} from '../../animations/animation';
import {SwipeBackGesture} from './swipe-back';
import {isBoolean, array} from '../../util/util';
import {rafFrames} from '../../util/dom';

/**
 * _For examples on the basic usage of NavController, check out the [Navigation section](../../../../components/#navigation)
 * of the Component docs._
 *
 * NavController is the base class for navigation controller components like
 * [`Nav`](../Nav/) and [`Tab`](../../Tabs/Tab/). You use navigation controllers
 * to navigate to [pages](#creating_pages) in your app. At a basic level, a
 * navigation controller is an array of pages representing a particular history
 * (of a Tab for example). This array can be manipulated to navigate throughout
 * an app by pushing and popping pages or inserting and removing them at
 * arbitrary locations in history.
 *
 * The current page is the last one in the array, or the top of the stack if we
 * think of it that way.  [Pushing](#push) a new page onto the top of the
 * navigation stack causes the new page to be animated in, while [popping](#pop)
 * the current page will navigate to the previous page in the stack.
 *
 * Unless you are using a directive like [NavPush](../NavPush/), or need a
 * specific NavController, most times you will inject and use a reference to the
 * nearest NavController to manipulate the navigation stack.
 *
 * <h3 id="injecting_nav_controller">Injecting NavController</h3>
 * Injecting NavController will always get you an instance of the nearest
 * NavController, regardless of whether it is a Tab or a Nav.
 *
 * Behind the scenes, when Ionic instantiates a new NavController, it creates an
 * injector with NavController bound to that instance (usually either a Nav or
 * Tab) and adds the injector to its own providers.  For more information on
 * providers and dependency injection, see [Providers and DI]().
 *
 * ```ts
 * // class NavController
 * this.providers = Injector.resolve([
 *   provide(NavController, {useValue: this})
 * ]);
 * ```
 *
 * Instead, you can inject NavController and know that it is the correct
 * navigation controller for most situations (for more advanced situations, see
 * [Menu](../../Menu/Menu/) and [Tab](../../Tab/Tab/)).
 *
 * ```ts
 *  class MyComponent {
 *    constructor(nav: NavController) {
 *      this.nav = nav;
 *    }
 *  }
 * ```
 *
 * <h2 id="creating_pages">Page creation</h2>
 * _For more information on the `@Page` decorator see the [@Page API
 * reference](../../../config/Page/)._
 *
 * Pages are created when they are added to the navigation stack.  For methods
 * like [push()](#push), the NavController takes any component class that is
 * decorated with @Page as its first argument.  The NavController then
 * [compiles]() that component, adds it to the DOM in a similar fashion to
 * Angular's [DynamicComponentLoader](https://angular.io/docs/js/latest/api/core/DynamicComponentLoader-interface.html),
 * and animates it into view.
 *
 * By default, pages are cached and left in the DOM if they are navigated away
 * from but still in the navigation stack (the exiting page on a `push()` for
 * example).  They are destroyed when removed from the navigation stack (on
 * [pop()](#pop) or [setRoot()](#setRoot)).
 *
 *
 * <h2 id="Lifecycle">Lifecycle events</h2>
 * Lifecycle events are fired during various stages of navigation.  They can be
 * defined in any `@Page` decorated component class.
 *
 * ```ts
 * @Page({
 *   template: 'Hello World'
 * })
 * class HelloWorld {
 *   onPageLoaded() {
 *     console.log("I'm alive!");
 *   }
 * }
 * ```
 *
 * - `onPageLoaded` - Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The `onPageLoaded` event is good place to put your setup code for the page.
 * - `onPageWillEnter` - Runs when the page is about to enter and become the active page.
 * - `onPageDidEnter` - Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.
 * - `onPageWillLeave` - Runs when the page is about to leave and no longer be the active page.
 * - `onPageDidLeave` - Runs when the page has finished leaving and is no longer the active page.
 * - `onPageWillUnload` - Runs when the page is about to be destroyed and have its elements removed.
 * - `onPageDidUnload` - Runs after the page has been destroyed and its elements have been removed.
 *
 */
export class NavController extends Ion {

  constructor(
    parentnavCtrl: NavController,
    app: IonicApp,
    config: Config,
    keyboard: Keyboard,
    elementRef: ElementRef,
    compiler: Compiler,
    loader: DynamicComponentLoader,
    viewManager: AppViewManager,
    zone: NgZone,
    renderer: Renderer
  ) {
    super(elementRef, config);

    this.parent = parentnavCtrl;
    this.app = app;
    this.config = config;
    this.keyboard = keyboard;

    this._compiler = compiler;
    this._loader = loader;
    this._viewManager = viewManager;
    this._zone = zone;
    this._renderer = renderer;

    this._views = [];
    this._trnsTime = 0;

    this._sbTrans = null;
    this._sbEnabled = config.get('swipeBackEnabled') || false;
    this._sbThreshold = config.get('swipeBackThreshold') || 40;

    this.id = ++ctrlIds;
    this._ids = -1;

    // build a new injector for child ViewControllers to use
    this.providers = Injector.resolve([
      provide(NavController, {useValue: this})
    ]);
  }

  /**
   * Boolean if the nav controller is actively transitioning or not.
   * @return {bool}
   */
  isTransitioning() {
    return (this._trnsTime > Date.now());
  }

  setTransitioning(isTransitioning, fallback=700) {
    this._trnsTime = (isTransitioning ? Date.now() + fallback : 0);
  }

  /**
   * TODO
   * @name NavController#push
   * @param {Component} The name of the component you want to push on the navigation stack
   * @param {Component} [params={}] The name of the component you want to push on the navigation stack, plus additional data you want to pass as parameters
   * @param {Component} [opts={}]  The name of the component you want to push on the navigation stack, plus additional options for the transition
   * @returns {Promise} Returns a promise when the transition is completed
   */
  push(componentType, params = {}, opts = {}, callback) {
    if (!componentType) {
      return Promise.reject('invalid componentType to push');
    }

    if (typeof componentType !== 'function') {
      throw 'Loading component must be a component class, not "' + componentType.toString() + '"';
    }

    if (this.isTransitioning()) {
      console.debug('nav controller actively transitioning');
      return Promise.reject();
    }

    this.setTransitioning(true, 500);

    let promise = null;
    if (!callback) {
      promise = new Promise(res => { callback = res; });
    }

    // do not animate if this is the first in the stack
    if (!this._views.length && !opts.animateFirst) {
      opts.animate = false;
    }

    // default the direction to "forward"
    opts.direction = opts.direction || 'forward';

    // the active view is going to be the leaving one (if one exists)
    let leavingView = this.getActive() || new ViewController();
    leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true);
    leavingView.shouldDestroy = !leavingView.shouldCache;
    if (leavingView.shouldDestroy) {
      leavingView.willUnload();
    }

    // create a new ViewController
    let enteringView = new ViewController(this, componentType, params);
    enteringView.shouldDestroy = false;
    enteringView.shouldCache = false;
    enteringView.pageType = opts.pageType;
    enteringView.handle = opts.handle || null;

    // add the view to the stack
    this._add(enteringView);

    if (this.router) {
      // notify router of the state change
      this.router.stateChange('push', enteringView, params);
    }

    // start the transition
    this._transition(enteringView, leavingView, opts, callback);

    return promise;
  }

  /**
   * TODO
   * @name NavController#pop
   * @param {Object} [opts={}] Any additional option for the transition
   * @returns {Promise} Returns a promise when the transition is completed
   */
  pop(opts = {}) {
    if (!opts.animateFirst && !this.canGoBack()) {
      return Promise.reject();
    }

    if (this.isTransitioning()) {
      console.debug('nav controller actively transitioning');
      return Promise.reject();
    }

    this.setTransitioning(true, 500);

    let resolve;
    let promise = new Promise(res => { resolve = res; });

    // default the direction to "back"
    opts.direction = opts.direction || 'back';

    // get the active view and set that it is staged to be leaving
    // was probably the one popped from the stack
    let leavingView = this.getActive() || new ViewController();
    leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : false);
    leavingView.shouldDestroy = !leavingView.shouldCache;
    if (leavingView.shouldDestroy) {
      leavingView.willUnload();
    }

    // the entering view is now the new last view
    // Note: we might not have an entering view if this is the
    // only view on the history stack.
    let enteringView = this.getPrevious(leavingView);
    if (this.router) {
      // notify router of the state change
      this.router.stateChange('pop', enteringView);
    }

    // start the transition
    this._transition(enteringView, leavingView, opts, resolve);

    return promise;
  }

  /**
   * @private
   * Pop to a specific view in the history stack
   *
   * @param view {ViewController} to pop to
   * @param opts {object} pop options
   */
  popTo(viewCtrl, opts = {}) {

    // Get the target index of the view to pop to
    let viewIndex = this._views.indexOf(viewCtrl);
    let targetIndex = viewIndex + 1;

    // Don't pop to the view if it wasn't found, or the target is beyond the view list
    if (viewIndex < 0 || targetIndex > this._views.length - 1) {
      return Promise.resolve();
    }

    // ensure the entering view is shown
    this._renderView(viewCtrl, true);

    let resolve = null;
    let promise = new Promise(res => { resolve = res; });
    opts.direction = opts.direction || 'back';

    let leavingView = this.getActive() || new ViewController();

    // get the views to auto remove without having to do a transiton for each
    // the last view (the currently active one) will do a normal transition out
    if (this._views.length > 1) {
      let autoRemoveItems = this._views.slice(targetIndex, this._views.length);
      let popView;
      for (let i = 0; i < autoRemoveItems.length; i++) {
        popView = autoRemoveItems[i];
        popView.shouldDestroy = true;
        popView.shouldCache = false;
        popView.willUnload();

        // only the leaving view should be shown, all others hide
        this._renderView(popView, (popView === leavingView));
      }
    }

    if (this.router) {
      this.router.stateChange('pop', viewCtrl);
    }

    this._transition(viewCtrl, leavingView, opts, resolve);

    return promise;
  }

  /**
   * Pop to the root view.
   * @param opts extra animation options
   */
  popToRoot(opts = {}) {
    return this.popTo(this.first(), opts);
  }

  /**
   * Inserts a view into the nav stack at the specified index.
   * @param {Index} The index where you want to insert the view
   * @param {Component} The name of the component you want to insert into the nav stack
   * @returns {Promise} Returns a promise when the view has been inserted into the navigation stack
   */
  insert(index, componentType, params = {}, opts = {}) {
    if (!componentType || index < 0) {
      return Promise.reject();
    }

    // push it onto the end
    if (index >= this._views.length) {
      return this.push(componentType, params, opts);
    }

    // create new ViewController, but don't render yet
    let viewCtrl = new ViewController(this, componentType, params);
    viewCtrl.state = CACHED_STATE;
    viewCtrl.shouldDestroy = false;
    viewCtrl.shouldCache = false;

    this._incrementId(viewCtrl);
    this._views.splice(index, 0, viewCtrl);
    return Promise.resolve();
  }

  /**
   * Removes a view from the nav stack at the specified index.
   * @param {Index} Remove the view from the nav stack at that index
   * @returns {Promise} Returns a promise when the view has been removed
{'fraction_non_alphanumeric': 0.10910918651407361, 'fraction_numerical': 0.0022424992267244047, 'mean_word_length': 3.1663981958762886, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 29, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6525209', 'n_tokens_mistral': 7287, 'n_tokens_neox': 7138, 'n_words': 2788}