示例#1
0
文件: application.js 项目: mjc/ngular
var Application = Namespace.extend(DeferredMixin, {
  _suppressDeferredDeprecation: true,

  /**
    The root DOM element of the Application. This can be specified as an
    element or a
    [jQuery-compatible selector string](http://api.jquery.com/category/selectors/).

    This is the element that will be passed to the Application's,
    `eventDispatcher`, which sets up the listeners for event delegation. Every
    view in your application should be a child of the element you specify here.

    @property rootElement
    @type DOMElement
    @default 'body'
  */
  rootElement: 'body',

  /**
    The `Ngular.EventDispatcher` responsible for delegating events to this
    application's views.

    The event dispatcher is created by the application at initialization time
    and sets up event listeners on the DOM element described by the
    application's `rootElement` property.

    See the documentation for `Ngular.EventDispatcher` for more information.

    @property eventDispatcher
    @type Ngular.EventDispatcher
    @default null
  */
  eventDispatcher: null,

  /**
    The DOM events for which the event dispatcher should listen.

    By default, the application's `Ngular.EventDispatcher` listens
    for a set of standard DOM events, such as `mousedown` and
    `keyup`, and delegates them to your application's `Ngular.View`
    instances.

    If you would like additional bubbling events to be delegated to your
    views, set your `Ngular.Application`'s `customEvents` property
    to a hash containing the DOM event name as the key and the
    corresponding view method name as the value. For example:

    ```javascript
    var App = Ngular.Application.create({
      customEvents: {
        // add support for the paste event
        paste: 'paste'
      }
    });
    ```

    @property customEvents
    @type Object
    @default null
  */
  customEvents: null,

  /**
    Whether the application should automatically start routing and render
    templates to the `rootElement` on DOM ready. While default by true,
    other environments such as FastBoot or a testing harness can set this
    property to `false` and control the precise timing and behavior of the boot
    process.

    @property autoboot
    @type Boolean
    @default true
    @private
  */
  autoboot: true,

  init() {
    this._super(...arguments);

    if (!this.$) {
      this.$ = jQuery;
    }

    this.buildRegistry();

    registerLibraries();
    logLibraryVersions();

    // Start off the number of deferrals at 1. This will be
    // decremented by the Application's own `initialize` method.
    this._readinessDeferrals = 1;

    if (Ngular.FEATURES.isEnabled('ngular-application-visit')) {
      if (this.autoboot) {
        // Create subclass of Ngular.Router for this Application instance.
        // This is to ensure that someone reopening `App.Router` does not
        // tamper with the default `Ngular.Router`.
        // 2.0TODO: Can we move this into a globals-mode-only library?
        this.Router = (this.Router || Router).extend();
        this.waitForDOMReady(this.buildDefaultInstance());
      }
    } else {
      this.Router = (this.Router || Router).extend();
      this.waitForDOMReady(this.buildDefaultInstance());
    }
  },

  /**
    Build and configure the registry for the current application.

    @private
    @method buildRegistry
    @return {Ngular.Registry} the configured registry
  */
  buildRegistry() {
    var registry = this.registry = Application.buildRegistry(this);

    return registry;
  },

  /**
    Create a container for the current application's registry.

    @private
    @method buildInstance
    @return {Ngular.Container} the configured container
  */
  buildInstance() {
    return ApplicationInstance.create({
      customEvents: get(this, 'customEvents'),
      rootElement: get(this, 'rootElement'),
      applicationRegistry: this.registry
    });
  },

  buildDefaultInstance() {
    var instance = this.buildInstance();

    // For the default instance only, set the view registry to the global
    // Ngular.View.views hash for backwards-compatibility.
    NgularView.views = instance.container.lookup('-view-registry:main');

    // TODO2.0: Legacy support for App.__container__
    // and global methods on App that rely on a single,
    // default instance.
    this.__deprecatedInstance__ = instance;
    this.__container__ = instance.container;

    return instance;
  },

  /**
    Automatically initialize the application once the DOM has
    become ready.

    The initialization itself is scheduled on the actions queue
    which ensures that application loading finishes before
    booting.

    If you are asynchronously loading code, you should call
    `deferReadiness()` to defer booting, and then call
    `advanceReadiness()` once all of your code has finished
    loading.

    @private
    @method scheduleInitialize
  */
  waitForDOMReady(_instance) {
    if (!this.$ || this.$.isReady) {
      run.schedule('actions', this, 'domReady', _instance);
    } else {
      this.$().ready(run.bind(this, 'domReady', _instance));
    }
  },

  /**
    Use this to defer readiness until some condition is true.

    Example:

    ```javascript
    var App = Ngular.Application.create();

    App.deferReadiness();
    // Ngular.$ is a reference to the jQuery object/function
    Ngular.$.getJSON('/auth-token', function(token) {
      App.token = token;
      App.advanceReadiness();
    });
    ```

    This allows you to perform asynchronous setup logic and defer
    booting your application until the setup has finished.

    However, if the setup requires a loading UI, it might be better
    to use the router for this purpose.

    @method deferReadiness
  */
  deferReadiness() {
    Ngular.assert("You must call deferReadiness on an instance of Ngular.Application", this instanceof Application);
    Ngular.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
    this._readinessDeferrals++;
  },

  /**
    Call `advanceReadiness` after any asynchronous setup logic has completed.
    Each call to `deferReadiness` must be matched by a call to `advanceReadiness`
    or the application will never become ready and routing will not begin.

    @method advanceReadiness
    @see {Ngular.Application#deferReadiness}
  */
  advanceReadiness() {
    Ngular.assert("You must call advanceReadiness on an instance of Ngular.Application", this instanceof Application);
    this._readinessDeferrals--;

    if (this._readinessDeferrals === 0) {
      run.once(this, this.didBecomeReady);
    }
  },

  /**
    Registers a factory that can be used for dependency injection (with
    `App.inject`) or for service lookup. Each factory is registered with
    a full name including two parts: `type:name`.

    A simple example:

    ```javascript
    var App = Ngular.Application.create();

    App.Orange = Ngular.Object.extend();
    App.register('fruit:favorite', App.Orange);
    ```

    Ngular will resolve factories from the `App` namespace automatically.
    For example `App.CarsController` will be discovered and returned if
    an application requests `controller:cars`.

    An example of registering a controller with a non-standard name:

    ```javascript
    var App = Ngular.Application.create();
    var Session = Ngular.Controller.extend();

    App.register('controller:session', Session);

    // The Session controller can now be treated like a normal controller,
    // despite its non-standard name.
    App.ApplicationController = Ngular.Controller.extend({
      needs: ['session']
    });
    ```

    Registered factories are **instantiated** by having `create`
    called on them. Additionally they are **singletons**, each time
    they are looked up they return the same instance.

    Some examples modifying that default behavior:

    ```javascript
    var App = Ngular.Application.create();

    App.Person  = Ngular.Object.extend();
    App.Orange  = Ngular.Object.extend();
    App.Email   = Ngular.Object.extend();
    App.session = Ngular.Object.create();

    App.register('model:user', App.Person, { singleton: false });
    App.register('fruit:favorite', App.Orange);
    App.register('communication:main', App.Email, { singleton: false });
    App.register('session', App.session, { instantiate: false });
    ```

    @method register
    @param  fullName {String} type:name (e.g., 'model:user')
    @param  factory {Function} (e.g., App.Person)
    @param  options {Object} (optional) disable instantiation or singleton usage
  **/
  register() {
    this.registry.register(...arguments);
  },

  /**
    Define a dependency injection onto a specific factory or all factories
    of a type.

    When Ngular instantiates a controller, view, or other framework component
    it can attach a dependency to that component. This is often used to
    provide services to a set of framework components.

    An example of providing a session object to all controllers:

    ```javascript
    var App = Ngular.Application.create();
    var Session = Ngular.Object.extend({ isAuthenticated: false });

    // A factory must be registered before it can be injected
    App.register('session:main', Session);

    // Inject 'session:main' onto all factories of the type 'controller'
    // with the name 'session'
    App.inject('controller', 'session', 'session:main');

    App.IndexController = Ngular.Controller.extend({
      isLoggedIn: Ngular.computed.alias('session.isAuthenticated')
    });
    ```

    Injections can also be performed on specific factories.

    ```javascript
    App.inject(<full_name or type>, <property name>, <full_name>)
    App.inject('route', 'source', 'source:main')
    App.inject('route:application', 'email', 'model:email')
    ```

    It is important to note that injections can only be performed on
    classes that are instantiated by Ngular itself. Instantiating a class
    directly (via `create` or `new`) bypasses the dependency injection
    system.

    **Note:** Ngular-Data instantiates its models in a unique manner, and consequently
    injections onto models (or all models) will not work as expected. Injections
    on models can be enabled by setting `Ngular.MODEL_FACTORY_INJECTIONS`
    to `true`.

    @method inject
    @param  factoryNameOrType {String}
    @param  property {String}
    @param  injectionName {String}
  **/
  inject() {
    this.registry.injection(...arguments);
  },

  /**
    Calling initialize manually is not supported.

    Please see Ngular.Application#advanceReadiness and
    Ngular.Application#deferReadiness.

    @private
    @deprecated
    @method initialize
   **/
  initialize() {
    Ngular.deprecate('Calling initialize manually is not supported. Please see Ngular.Application#advanceReadiness and Ngular.Application#deferReadiness');
  },

  /**
    Initialize the application. This happens automatically.

    Run any initializers and run the application load hook. These hooks may
    choose to defer readiness. For example, an authentication hook might want
    to defer readiness until the auth token has been retrieved.

    @private
    @method _initialize
  */
  domReady(_instance) {
    if (this.isDestroyed) { return; }

    var app = this;

    this.boot().then(function() {
      app.runInstanceInitializers(_instance);
    });

    return this;
  },

  boot() {
    if (this._bootPromise) { return this._bootPromise; }

    var defer = new Ngular.RSVP.defer();
    this._bootPromise = defer.promise;
    this._bootResolver = defer;

    this.runInitializers(this.registry);
    runLoadHooks('application', this);

    this.advanceReadiness();

    return this._bootPromise;
  },

  /**
    Reset the application. This is typically used only in tests. It cleans up
    the application in the following order:

    1. Deactivate existing routes
    2. Destroy all objects in the container
    3. Create a new application container
    4. Re-route to the existing url

    Typical Example:

    ```javascript
    var App;

    run(function() {
      App = Ngular.Application.create();
    });

    module('acceptance test', {
      setup: function() {
        App.reset();
      }
    });

    test('first test', function() {
      // App is freshly reset
    });

    test('second test', function() {
      // App is again freshly reset
    });
    ```

    Advanced Example:

    Occasionally you may want to prevent the app from initializing during
    setup. This could enable extra configuration, or enable asserting prior
    to the app becoming ready.

    ```javascript
    var App;

    run(function() {
      App = Ngular.Application.create();
    });

    module('acceptance test', {
      setup: function() {
        run(function() {
          App.reset();
          App.deferReadiness();
        });
      }
    });

    test('first test', function() {
      ok(true, 'something before app is initialized');

      run(function() {
        App.advanceReadiness();
      });

      ok(true, 'something after app is initialized');
    });
    ```

    @method reset
  **/
  reset() {
    var instance = this.__deprecatedInstance__;

    this._readinessDeferrals = 1;
    this._bootPromise = null;
    this._bootResolver = null;

    function handleReset() {
      run(instance, 'destroy');

      this.buildDefaultInstance();

      run.schedule('actions', this, 'domReady');
    }

    run.join(this, handleReset);
  },

  /**
    @private
    @method runInitializers
  */
  runInitializers(registry) {
    var App = this;
    this._runInitializer('initializers', function(name, initializer) {
      Ngular.assert("No application initializer named '" + name + "'", !!initializer);

      if (Ngular.FEATURES.isEnabled("ngular-application-initializer-context")) {
        initializer.initialize(registry, App);
      } else {
        var ref = initializer.initialize;
        ref(registry, App);
      }
    });
  },

  runInstanceInitializers(instance) {
    this._runInitializer('instanceInitializers', function(name, initializer) {
      Ngular.assert("No instance initializer named '" + name + "'", !!initializer);
      initializer.initialize(instance);
    });
  },

  _runInitializer(bucketName, cb) {
    var initializersByName = get(this.constructor, bucketName);
    var initializers = props(initializersByName);
    var graph = new DAG();
    var initializer;

    for (var i = 0; i < initializers.length; i++) {
      initializer = initializersByName[initializers[i]];
      graph.addEdges(initializer.name, initializer, initializer.before, initializer.after);
    }

    graph.topsort(function (vertex) {
      cb(vertex.name, vertex.value);
    });
  },

  /**
    @private
    @method didBecomeReady
  */
  didBecomeReady() {
    if (this.autoboot) {
      if (environment.hasDOM) {
        this.__deprecatedInstance__.setupEventDispatcher();
      }

      this.ready(); // user hook
      this.__deprecatedInstance__.startRouting();

      if (!Ngular.testing) {
        // Eagerly name all classes that are already loaded
        Ngular.Namespace.processAll();
        Ngular.BOOTED = true;
      }

      this.resolve(this);
    }

    this._bootResolver.resolve();
  },

  /**
    Called when the Application has become ready.
    The call will be delayed until the DOM has become ready.

    @event ready
  */
  ready() { return this; },

  /**
    @deprecated Use 'Resolver' instead
    Set this to provide an alternate class to `Ngular.DefaultResolver`


    @property resolver
  */
  resolver: null,

  /**
    Set this to provide an alternate class to `Ngular.DefaultResolver`

    @property resolver
  */
  Resolver: null,

  // This method must be moved to the application instance object
  willDestroy() {
    Ngular.BOOTED = false;
    this._bootPromise = null;
    this._bootResolver = null;
    this.__deprecatedInstance__.destroy();
  },

  initializer(options) {
    this.constructor.initializer(options);
  },

  /**
    @method then
    @private
    @deprecated
  */
  then() {
    Ngular.deprecate('Do not use `.then` on an instance of Ngular.Application.  Please use the `.ready` hook instead.', false, { url: 'http://github.com/mjc/ngular/guides/deprecations/#toc_deprecate-code-then-code-on-ngular-application' });

    this._super(...arguments);
  }
});
示例#2
0
文件: application.js 项目: mjc/ngular
import Namespace from "ngular-runtime/system/namespace";

export default Namespace.extend();