#

API Docs for: v2.11.1
Show: Inherited Protected Private Deprecated

# File: packages/ember-application/lib/system/resolver.js

    /**
    @module ember
    @submodule ember-application
    */

    import { dictionary } from 'ember-utils';
    import { assert, info, get } from 'ember-metal';
    import {
      String as StringUtils,
      Object as EmberObject,
      Namespace
    } from 'ember-runtime';
    import validateType from '../utils/validate-type';
    import { getTemplate } from 'ember-glimmer';

    export const Resolver = EmberObject.extend({
      /*
        This will be set to the Application instance when it is
        created.

        @property namespace
      */
      namespace: null,
      normalize:         null, // required
      resolve:           null, // required
      parseName:         null, // required
      lookupDescription: null, // required
      makeToString:      null, // required
      resolveOther:      null, // required
      _logLookup:        null  // required
    });

    /**
      The DefaultResolver defines the default lookup rules to resolve
      container lookups before consulting the container for registered
      items:

      * templates are looked up on `Ember.TEMPLATES`
      * other names are looked up on the application after converting
        the name. For example, `controller:post` looks up
        `App.PostController` by default.
      * there are some nuances (see examples below)

      ### How Resolving Works

      The container calls this object's `resolve` method with the
      `fullName` argument.

      It first parses the fullName into an object using `parseName`.

      Then it checks for the presence of a type-specific instance
      method of the form `resolve[Type]` and calls it if it exists.
      For example if it was resolving 'template:post', it would call
      the `resolveTemplate` method.

      Its last resort is to call the `resolveOther` method.

      The methods of this object are designed to be easy to override
      in a subclass. For example, you could enhance how a template
      is resolved like so:

      ```javascript
      App = Ember.Application.create({
        Resolver: Ember.DefaultResolver.extend({
          resolveTemplate: function(parsedName) {
            let resolvedTemplate = this._super(parsedName);
            if (resolvedTemplate) { return resolvedTemplate; }
            return Ember.TEMPLATES['not_found'];
          }
        })
      });
      ```

      Some examples of how names are resolved:

      ```
      'template:post'           //=> Ember.TEMPLATES['post']
      'template:posts/byline'   //=> Ember.TEMPLATES['posts/byline']
      'template:posts.byline'   //=> Ember.TEMPLATES['posts/byline']
      'template:blogPost'       //=> Ember.TEMPLATES['blogPost']
                                //   OR
                                //   Ember.TEMPLATES['blog_post']
      'controller:post'         //=> App.PostController
      'controller:posts.index'  //=> App.PostsIndexController
      'controller:blog/post'    //=> Blog.PostController
      'controller:basic'        //=> Ember.Controller
      'route:post'              //=> App.PostRoute
      'route:posts.index'       //=> App.PostsIndexRoute
      'route:blog/post'         //=> Blog.PostRoute
      'route:basic'             //=> Ember.Route
      'view:post'               //=> App.PostView
      'view:posts.index'        //=> App.PostsIndexView
      'view:blog/post'          //=> Blog.PostView
      'view:basic'              //=> Ember.View
      'foo:post'                //=> App.PostFoo
      'model:post'              //=> App.Post
      ```

      @class DefaultResolver
      @namespace Ember
      @extends Ember.Object
      @public
    */

    export default EmberObject.extend({
      /**
        This will be set to the Application instance when it is
        created.

        @property namespace
        @public
      */
      namespace: null,

      init() {
        this._parseNameCache = dictionary(null);
      },
      normalize(fullName) {
        var [
          type,
          name
        ] = fullName.split(':', 2);

        assert(
          'Tried to normalize a container name without a colon (:) in it. ' +
          'You probably tried to lookup a name that did not contain a type, ' +
          'a colon, and a name. A proper lookup name would be `view:post`.',
          fullName.split(':').length === 2
        );

        if (type !== 'template') {
          var result = name;

          if (result.indexOf('.') > -1) {
            result = result.replace(/\.(.)/g, function(m) {
              return m.charAt(1).toUpperCase();
            });
          }

          if (name.indexOf('_') > -1) {
            result = result.replace(/_(.)/g, function(m) {
              return m.charAt(1).toUpperCase();
            });
          }

          if (name.indexOf('-') > -1) {
            result = result.replace(/-(.)/g, function(m) {
              return m.charAt(1).toUpperCase();
            });
          }

          return type + ':' + result;
        } else {
          return fullName;
        }
      },

      /**
        This method is called via the container's resolver method.
        It parses the provided `fullName` and then looks up and
        returns the appropriate template or class.

        @method resolve
        @param {String} fullName the lookup string
        @return {Object} the resolved factory
        @public
      */
      resolve(fullName) {
        var parsedName = this.parseName(fullName);
        var resolveMethodName = parsedName.resolveMethodName;
        var resolved;

        if (this[resolveMethodName]) {
          resolved = this[resolveMethodName](parsedName);
        }

        resolved = resolved || this.resolveOther(parsedName);

        if (parsedName.root && parsedName.root.LOG_RESOLVER) {
          this._logLookup(resolved, parsedName);
        }

        if (resolved) {
          validateType(resolved, parsedName);
        }

        return resolved;
      },

      /**
        Convert the string name of the form 'type:name' to
        a Javascript object with the parsed aspects of the name
        broken out.

        @param {String} fullName the lookup string
        @method parseName
        @protected
      */

      parseName(fullName) {
        return this._parseNameCache[fullName] || (
          (this._parseNameCache[fullName] = this._parseName(fullName))
        );
      },

      _parseName(fullName) {
        let [
          type,
          fullNameWithoutType
        ] = fullName.split(':');

        var name = fullNameWithoutType;
        var namespace = get(this, 'namespace');
        var root = namespace;
        let lastSlashIndex = name.lastIndexOf('/');
        let dirname = lastSlashIndex !== -1 ? name.slice(0, lastSlashIndex) : null;

        if (type !== 'template' && lastSlashIndex !== -1) {
          let parts = name.split('/');
          name = parts[parts.length - 1];
          let namespaceName = StringUtils.capitalize(parts.slice(0, -1).join('.'));
          root = Namespace.byName(namespaceName);

          assert(
            'You are looking for a ' + name + ' ' + type + ' in the ' +
            namespaceName + ' namespace, but the namespace could not be found',
            root
          );
        }

        let resolveMethodName = fullNameWithoutType === 'main' ? 'Main' : StringUtils.classify(type);

        if (!(name && type)) {
          throw new TypeError('Invalid fullName: `' + fullName + '`, must be of the form `type:name` ');
        }

        return {
          fullName,
          type,
          fullNameWithoutType,
          dirname,
          name,
          root,
          resolveMethodName: 'resolve' + resolveMethodName
        };
      },

      /**
        Returns a human-readable description for a fullName. Used by the
        Application namespace in assertions to describe the
        precise name of the class that Ember is looking for, rather than
        container keys.

        @param {String} fullName the lookup string
        @method lookupDescription
        @protected
      */
      lookupDescription(fullName) {
        let parsedName = this.parseName(fullName);
        let description;

        if (parsedName.type === 'template') {
          return 'template at ' + parsedName.fullNameWithoutType.replace(/\./g, '/');
        }

        description = parsedName.root + '.' + StringUtils.classify(parsedName.name).replace(/\./g, '');

        if (parsedName.type !== 'model') {
          description += StringUtils.classify(parsedName.type);
        }

        return description;
      },

      makeToString(factory, fullName) {
        return factory.toString();
      },

      /**
        Given a parseName object (output from `parseName`), apply
        the conventions expected by `Ember.Router`

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method useRouterNaming
        @protected
      */
      useRouterNaming(parsedName) {
        parsedName.name = parsedName.name.replace(/\./g, '_');
        if (parsedName.name === 'basic') {
          parsedName.name = '';
        }
      },
      /**
        Look up the template in Ember.TEMPLATES

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveTemplate
        @protected
      */
      resolveTemplate(parsedName) {
        let templateName = parsedName.fullNameWithoutType.replace(/\./g, '/');

        return getTemplate(templateName) || getTemplate(StringUtils.decamelize(templateName));
      },

      /**
        Lookup the view using `resolveOther`

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveView
        @protected
      */
      resolveView(parsedName) {
        this.useRouterNaming(parsedName);
        return this.resolveOther(parsedName);
      },

      /**
        Lookup the controller using `resolveOther`

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveController
        @protected
      */
      resolveController(parsedName) {
        this.useRouterNaming(parsedName);
        return this.resolveOther(parsedName);
      },
      /**
        Lookup the route using `resolveOther`

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveRoute
        @protected
      */
      resolveRoute(parsedName) {
        this.useRouterNaming(parsedName);
        return this.resolveOther(parsedName);
      },

      /**
        Lookup the model on the Application namespace

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveModel
        @protected
      */
      resolveModel(parsedName) {
        let className = StringUtils.classify(parsedName.name);
        let factory = get(parsedName.root, className);

        return factory;
      },
      /**
        Look up the specified object (from parsedName) on the appropriate
        namespace (usually on the Application)

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveHelper
        @protected
      */
      resolveHelper(parsedName) {
        return this.resolveOther(parsedName);
      },
      /**
        Look up the specified object (from parsedName) on the appropriate
        namespace (usually on the Application)

        @param {Object} parsedName a parseName object with the parsed
          fullName lookup string
        @method resolveOther
        @protected
      */
      resolveOther(parsedName) {
        let className = StringUtils.classify(parsedName.name) + StringUtils.classify(parsedName.type);
        let factory = get(parsedName.root, className);
        return factory;
      },

      resolveMain(parsedName) {
        let className = StringUtils.classify(parsedName.type);
        return get(parsedName.root, className);
      },

      /**
       @method _logLookup
       @param {Boolean} found
       @param {Object} parsedName
       @private
      */
      _logLookup(found, parsedName) {
        let symbol, padding;

        if (found) {
          symbol = '[✓]';
        } else {
          symbol = '[ ]';
        }

        if (parsedName.fullName.length > 60) {
          padding = '.';
        } else {
          padding = new Array(60 - parsedName.fullName.length).join('.');
        }

        info(symbol, parsedName.fullName, padding, this.lookupDescription(parsedName.fullName));
      },

      /**
       Used to iterate all items of a given type.

       @method knownForType
       @param {String} type the type to search for
       @private
       */
      knownForType(type) {
        let namespace = get(this, 'namespace');
        let suffix = StringUtils.classify(type);
        let typeRegexp = new RegExp(`${suffix}$`);

        let known = dictionary(null);
        let knownKeys = Object.keys(namespace);
        for (let index = 0; index < knownKeys.length; index++) {
          let name = knownKeys[index];

          if (typeRegexp.test(name)) {
            let containerName = this.translateToContainerFullname(type, name);

            known[containerName] = true;
          }
        }

        return known;
      },

      /**
       Converts provided name from the backing namespace into a container lookup name.

       Examples:

       App.FooBarHelper -> helper:foo-bar
       App.THelper -> helper:t

       @method translateToContainerFullname
       @param {String} type
       @param {String} name
       @private
       */

      translateToContainerFullname(type, name) {
        let suffix = StringUtils.classify(type);
        let namePrefix = name.slice(0, suffix.length * -1);
        let dasherizedName = StringUtils.dasherize(namePrefix);

        return `${type}:${dasherizedName}`;
      }
    });