API Docs for: 0.1.1.cbe27efc
Show:

File: addon/services/path-inspector.js

/**
 * Provides additional non-invasive route tree introspection using a parallel tree of nodes
 *
 *        // Example Node:
 *        {
 *          nodeName: 'index',
 *          isLeafNode: true,
 *          routeName: 'index',
 *          children: [],
 *          depth: 1,
 *          parent: {
 *            nodeName: 'application',
 *            isLeafNode: false,
 *            routeName: 'application',
 *            depth: 0,
 *            children: [] // this would contain the same outer node we are dealing with
 *          }
 *        }
 *
 * @class EmberCliRoutePathInspector.Services.PathInspector
 * @constructor
 * @extends Ember.Service
 */

import Ember from 'ember';

const {
  assert,
  computed,
  typeOf
  } = Ember;

const rootRouteName = 'application';
const lastSegment = /\.([^.]+)$/;

export default Ember.Service.extend({

  router: {
    router: {
      recognizer: {
        names: {}
      }
    }
  },

  /**
   * A list of all route paths in the application
   *
   *        let routeNames = this.get('pathInspectorService.routes'); // ex. ['index', 'foo.bar', 'baz.qux']
   *
   * @property {[String]} routes
   * @public
   */
  routes: computed('router.router.recognizer.names', function () {
    const routes = this.get('router.router.recognizer.names');

    assert('Should have been given an object of route names', typeOf(routes) === 'object');
    assert('At a minimum the names object should contain the application route name', routes.hasOwnProperty(rootRouteName));

    return Object.keys(routes).filter(route => !route.match(/(loading|error)/));
  }),

  /**
   * A list of all leaf route names in the application
   *
   *        // Assuming a boilerplate application with only an index route.
   *        let leafRouteNames = this.get('pathInspectorService.leafRouteNames'); // ['index']
   *
   * @property {[String]} leafRouteNames
   * @public
   */
  leafRouteNames: computed(function () {
    const leafRouteMap = this.get('leafRouteMap');

    return Object.keys(leafRouteMap).filter(key => leafRouteMap[key]);
  }),

  /**
   * A hash where route names make up the keys, which are paired with either a true or false boolean value indicating
   * whether or not the keyed route name is that of a leaf route
   *
   *        // Assuming a boilerplate application with only an index route
   *        let leafRouteMap = this.get('pathInspectorService.leafRouteMap'); // {application: false, 'application.index': true}
   *
   * @property {Object} leafRouteMap
   * @public
   */
  leafRouteMap: computed('routes.[]', function () {
    // We don't want the application route because, by convention, Ember does not prepend it to any routeNames.
    // This means the algorithm will determine that it is a leaf route when it is not.
    const routes = this.get('routes').filter(routeName => routeName !== rootRouteName);

    const leafRouteMap = routes.reduce((leafRouteMap, routeName) => {
      leafRouteMap[routeName] = true;

      return leafRouteMap;
    }, {});

    leafRouteMap[rootRouteName] = false;

    // Set all second to last nodes for each route path to false (leaves all leaf routes to true)
    return routes.reduce((leafRouteMap, routeName) => {
      if (routeName.indexOf('.') > -1) {
        leafRouteMap[routeName.replace(lastSegment, '')] = false;
      }

      return leafRouteMap;
    }, leafRouteMap);
  }),

  /**
   * A parallel tree of nodes to that of the applications route map/tree.
   *
   *        // Assuming the following routes: application, application.index
   *        let routeMapTree = this.get('pathInspectorService.routeMapTree');
   *
   *        // result:
   *              {
   *                nodeName: 'application',
   *                routeName: 'application',
   *                isLeafNode: false,
   *                depth: 0,
   *                children: [
   *                  nodeName: 'index',
   *                  routeName: 'index',
   *                  children: [],
   *                  depth: 1,
   *                  parent: {} // the same outer node we are dealing with
   *                ]
   *              }
   *
   * @property {Object} routeMapTree
   * @public
   */
  routeMapTree: computed(function () {
    const routeMapTree = {
      nodeName: rootRouteName,
      routeName: rootRouteName,
      children: [],
      depth: 0,
      isLeafNode: false
    };

    this.get('routes')
      .filter(route => route !== rootRouteName)
      .forEach(routeName => {
        let currentNode = routeMapTree;

        routeName.split('.').forEach((nodeName) => {
          let nextNode = Ember.A(currentNode.children).find(node => node.nodeName === nodeName);

          if (!nextNode) {
            nextNode = {
              parent: currentNode,
              nodeName: nodeName,
              children: [],
              depth: currentNode.depth + 1
            };

            currentNode.children.push(nextNode);
          }

          currentNode = nextNode;
        });

        currentNode.routeName = routeName;
        currentNode.isLeafNode = this.isLeafRouteName(routeName);
      });

    return routeMapTree;
  }),

  /**
   * Determines whether or not a given route is a leaf route within the application
   *
   *       let isLeafRoute = this.get('pathInspectorService').isLeafRoute(this); // true / false
   *
   * @method isLeafRoute
   * @param {Ember.Route} route An application route to inspect and determine whether or not it is a leaf route
   * @returns {Boolean} Whether or not the route is a leaf route
   * @public
   */
  isLeafRoute({routeName}) {
    return this.isLeafRouteName(routeName);
  },

  /**
   * Determines whether or not a given routeName is that of a leaf route within the application.
   *
   *        let isLeafRouteName = this.get('pathInspectorService').isLeafRouteName(this.get('routeName')); // true / false
   *
   * @method isLeafRouteName
   * @param {String} candidateRouteName An application route name to inspect and determine whether or not it is a leaf route.
   * @returns {Boolean} Whether or not the route name is that of an application leaf route
   * @public
   */
  isLeafRouteName(candidateRouteName) {
    const leafRouteMap = this.get('leafRouteMap');

    assert('Route Inspector: You queried a route that is not part of this application', leafRouteMap.hasOwnProperty(candidateRouteName));

    return leafRouteMap[candidateRouteName];
  },

  /**
   * Retrieves the route names for the siblings of a given route name
   *
   *        // Assuming the following routes: application, application.index, application.foo
   *        let siblingPaths = this.get('pathInspectorService').siblingPathsForRouteName('application.index'); // ['foo']
   *
   * @method siblingPathsForRouteName
   * @param {String} routeName A route name to fetch the sibling route names for
   * @returns {[String]} A list of found sibling route names
   * @public
   */
  siblingPathsForRouteName(routeName) {
    return this.siblingNodesForRouteName(routeName).map(node => node.routeName);
  },

  /**
   * Retrieves the parallel route tree nodes representing the siblings for a given route name
   *
   *        // Assuming the following routes: application, application.index, application.foo
   *        let siblingNodes = this.get('pathInspectorService').siblingNodesForRouteName('application.index');
   *
   *        // result:
   *              [
   *                {
   *                  nodeName: 'foo',
   *                  isLeafNode: true,
   *                  routeName: 'foo',
   *                  children: [],
   *                  depth: 1,
   *                  parent: {
   *                    nodeName: 'application'
   *                  }
   *                }
   *              ]
   *
   * @method siblingNodesForRouteName
   * @param {String} routeName A route name to fetch the sibling parallel tree nodes for
   * @returns {[Object]} A list of parallel tree nodes representing the siblings for a given route name
   * @public
   */
  siblingNodesForRouteName(routeName) {
    if (routeName === rootRouteName) {
      return [];
    }

    return this.nodeForRouteName(routeName).parent.children.filter(siblingNode => siblingNode.routeName !== routeName);
  },

  /**
   * Retrieves the parallel route tree node representing the a given route name.
   *
   *        let node = this.get('pathInspectorService').nodeForRouteName('application.index');
   *
   *        // result:
   *              {
   *                nodeName: 'index',
   *                isLeafNode: true,
   *                routeName: 'index',
   *                children: [],
   *                depth: 1,
   *                parent: {
   *                  nodeName: 'application',
   *                  isLeafNode: false,
   *                  routeName: 'application',
   *                  depth: 0,
   *                  children: [] // this would contain the same outer node we are dealing with
   *                }
   *              }
   *
   * @method nodeForRouteName
   * @param {String} routeName An application route name to fetch a parallel tree node for
   * @returns {Object} The parallel tree node for a given route name
   * @public
   */
  nodeForRouteName(routeName) {
    assert('Route Inspector: You queried a node for a route that does not exist!', this.get('leafRouteMap').hasOwnProperty(routeName));

    if (routeName === rootRouteName) {
      return this.get('routeMapTree');
    }

    return routeName
      .split('.')
      .reduce((parentNode, nodeName) => {
        return parentNode.children.find(childNode => childNode.nodeName === nodeName);
      }, this.get('routeMapTree'));
  }
});