API Docs for: v2.11.0-beta.7
Show:

File: packages/ember-runtime/lib/system/namespace.js

/**
@module ember
@submodule ember-runtime
*/
import { guidFor } from 'ember-utils';
import Ember, {
  get,
  Mixin,
  hasUnprocessedMixins,
  clearUnprocessedMixins,
} from 'ember-metal'; // Preloaded into namespaces
import { context } from 'ember-environment';
import { NAME_KEY } from 'ember-utils';
import EmberObject from './object';

let searchDisabled = false;

export function isSearchDisabled() {
  return searchDisabled;
}

export function setSearchDisabled(flag) {
  searchDisabled = !!flag;
}

/**
  A Namespace is an object usually used to contain other objects or methods
  such as an application or framework. Create a namespace anytime you want
  to define one of these new containers.

  # Example Usage

  ```javascript
  MyFramework = Ember.Namespace.create({
    VERSION: '1.0.0'
  });
  ```

  @class Namespace
  @namespace Ember
  @extends Ember.Object
  @public
*/
const Namespace = EmberObject.extend({
  isNamespace: true,

  init() {
    Namespace.NAMESPACES.push(this);
    Namespace.PROCESSED = false;
  },

  toString() {
    let name = get(this, 'name') || get(this, 'modulePrefix');
    if (name) { return name; }

    findNamespaces();
    return this[NAME_KEY];
  },

  nameClasses() {
    processNamespace([this.toString()], this, {});
  },

  destroy() {
    let namespaces = Namespace.NAMESPACES;
    let toString = this.toString();

    if (toString) {
      context.lookup[toString] = undefined;
      delete Namespace.NAMESPACES_BY_ID[toString];
    }
    namespaces.splice(namespaces.indexOf(this), 1);
    this._super(...arguments);
  }
});

Namespace.reopenClass({
  NAMESPACES: [Ember],
  NAMESPACES_BY_ID: {
    Ember: Ember
  },
  PROCESSED: false,
  processAll: processAllNamespaces,
  byName(name) {
    if (!searchDisabled) {
      processAllNamespaces();
    }

    return NAMESPACES_BY_ID[name];
  }
});

let NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;

let hasOwnProp = ({}).hasOwnProperty;

function processNamespace(paths, root, seen) {
  let idx = paths.length;

  NAMESPACES_BY_ID[paths.join('.')] = root;

  // Loop over all of the keys in the namespace, looking for classes
  for (let key in root) {
    if (!hasOwnProp.call(root, key)) { continue; }
    let obj = root[key];

    // If we are processing the `Ember` namespace, for example, the
    // `paths` will start with `["Ember"]`. Every iteration through
    // the loop will update the **second** element of this list with
    // the key, so processing `Ember.View` will make the Array
    // `['Ember', 'View']`.
    paths[idx] = key;

    // If we have found an unprocessed class
    if (obj && obj.toString === classToString && !obj[NAME_KEY]) {
      // Replace the class' `toString` with the dot-separated path
      // and set its `NAME_KEY`
      obj[NAME_KEY] = paths.join('.');

    // Support nested namespaces
    } else if (obj && obj.isNamespace) {
      // Skip aliased namespaces
      if (seen[guidFor(obj)]) { continue; }
      seen[guidFor(obj)] = true;

      // Process the child namespace
      processNamespace(paths, obj, seen);
    }
  }

  paths.length = idx; // cut out last item
}

function isUppercase(code) {
  return code >= 65 && // A
         code <= 90;   // Z
}

function tryIsNamespace(lookup, prop) {
  try {
    let obj = lookup[prop];
    return obj && obj.isNamespace && obj;
  } catch (e) {
    // continue
  }
}

function findNamespaces() {
  if (Namespace.PROCESSED) {
    return;
  }
  let lookup = context.lookup;
  let keys = Object.keys(lookup);
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i];
    // Only process entities that start with uppercase A-Z
    if (!isUppercase(key.charCodeAt(0))) {
      continue;
    }
    let obj = tryIsNamespace(lookup, key);
    if (obj) {
      obj[NAME_KEY] = key;
    }
  }
}

function superClassString(mixin) {
  let superclass = mixin.superclass;
  if (superclass) {
    if (superclass[NAME_KEY]) {
      return superclass[NAME_KEY];
    }
    return superClassString(superclass);
  }
}

function calculateToString(target) {
  let str;

  if (!searchDisabled) {
    processAllNamespaces();
    // can also be set by processAllNamespaces
    str = target[NAME_KEY];
    if (str) {
      return str;
    } else {
      str = superClassString(target);
      str = str ? '(subclass of ' + str + ')' : str;
    }
  }
  if (str) {
    return str;
  } else {
    return '(unknown mixin)';
  }
}

function classToString() {
  let name = this[NAME_KEY];
  if (name) { return name; }

  return (this[NAME_KEY] = calculateToString(this));
}

function processAllNamespaces() {
  let unprocessedNamespaces = !Namespace.PROCESSED;
  let unprocessedMixins = hasUnprocessedMixins();

  if (unprocessedNamespaces) {
    findNamespaces();
    Namespace.PROCESSED = true;
  }

  if (unprocessedNamespaces || unprocessedMixins) {
    let namespaces = Namespace.NAMESPACES;
    let namespace;

    for (let i = 0; i < namespaces.length; i++) {
      namespace = namespaces[i];
      processNamespace([namespace.toString()], namespace, {});
    }

    clearUnprocessedMixins();
  }
}

Mixin.prototype.toString = classToString; // ES6TODO: altering imported objects. SBB.

export default Namespace;