#

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

# File: packages/ember-metal/lib/properties.js

    /**
    @module ember-metal
    */

    import { assert } from './debug';
    import isEnabled from './features';
    import { meta as metaFor, peekMeta } from './meta';
    import { overrideChains } from './property_events';
    // ..........................................................
    // DESCRIPTOR
    //

    /**
      Objects of this type can implement an interface to respond to requests to
      get and set. The default implementation handles simple properties.

      @class Descriptor
      @private
    */
    export function Descriptor() {
      this.isDescriptor = true;
    }

    const REDEFINE_SUPPORTED = (function () {
      // https://github.com/spalger/kibana/commit/b7e35e6737df585585332857a4c397dc206e7ff9
      let a = Object.create(Object.prototype, {
        prop: {
          configurable: true,
          value: 1
        }
      });

      Object.defineProperty(a, 'prop', {
        configurable: true,
        value: 2
      });

      return a.prop === 2;
    }());
    // ..........................................................
    // DEFINING PROPERTIES API
    //

    export function MANDATORY_SETTER_FUNCTION(name) {
      function SETTER_FUNCTION(value) {
        let m = peekMeta(this);
        if (!m.isInitialized(this)) {
          m.writeValues(name, value);
        } else {
          assert(`You must use Ember.set() to set the \`${name}\` property (of ${this}) to \`${value}\`.`, false);
        }
      }

      SETTER_FUNCTION.isMandatorySetter = true;
      return SETTER_FUNCTION;
    }

    export function DEFAULT_GETTER_FUNCTION(name) {
      return function GETTER_FUNCTION() {
        let meta = peekMeta(this);
        return meta && meta.peekValues(name);
      };
    }

    import { UNDEFINED } from './meta';

    export function INHERITING_GETTER_FUNCTION(name) {
      function IGETTER_FUNCTION() {
        let meta = peekMeta(this);
        let val = meta && meta.readInheritedValue('values', name);

        if (val === UNDEFINED) {
          let proto = Object.getPrototypeOf(this);
          return proto && proto[name];
        } else {
          return val;
        }
      }

      IGETTER_FUNCTION.isInheritingGetter = true;
      return IGETTER_FUNCTION;
    }

    /**
      NOTE: This is a low-level method used by other parts of the API. You almost
      never want to call this method directly. Instead you should use
      `Ember.mixin()` to define new properties.

      Defines a property on an object. This method works much like the ES5
      `Object.defineProperty()` method except that it can also accept computed
      properties and other special descriptors.

      Normally this method takes only three parameters. However if you pass an
      instance of `Descriptor` as the third param then you can pass an
      optional value as the fourth parameter. This is often more efficient than
      creating new descriptor hashes for each property.

      ## Examples

      ```javascript
      // ES5 compatible mode
      Ember.defineProperty(contact, 'firstName', {
        writable: true,
        configurable: false,
        enumerable: true,
        value: 'Charles'
      });

      // define a simple property
      Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');

      // define a computed property
      Ember.defineProperty(contact, 'fullName', Ember.computed('firstName', 'lastName', function() {
        return this.firstName+' '+this.lastName;
      }));
      ```

      @private
      @method defineProperty
      @for Ember
      @param {Object} obj the object to define this property on. This may be a prototype.
      @param {String} keyName the name of the property
      @param {Descriptor} [desc] an instance of `Descriptor` (typically a
        computed property) or an ES5 descriptor.
        You must provide this or `data` but not both.
      @param {*} [data] something other than a descriptor, that will
        become the explicit value of this property.
    */
    export function defineProperty(obj, keyName, desc, data, meta) {
      let possibleDesc, existingDesc, watching, value;

      if (!meta) {
        meta = metaFor(obj);
      }
      let watchEntry = meta.peekWatching(keyName);
      possibleDesc = obj[keyName];
      existingDesc = (possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc.isDescriptor) ? possibleDesc : undefined;

      watching = watchEntry !== undefined && watchEntry > 0;

      if (existingDesc) {
        existingDesc.teardown(obj, keyName);
      }

      if (desc instanceof Descriptor) {
        value = desc;
        if (isEnabled('mandatory-setter')) {
          if (watching) {
            Object.defineProperty(obj, keyName, {
              configurable: true,
              enumerable: true,
              writable: true,
              value: value
            });
          } else {
            obj[keyName] = value;
          }
        } else {
          obj[keyName] = value;
        }
        if (desc.setup) { desc.setup(obj, keyName); }
      } else {
        if (desc == null) {
          value = data;

          if (isEnabled('mandatory-setter')) {
            if (watching) {
              meta.writeValues(keyName, data);

              let defaultDescriptor = {
                configurable: true,
                enumerable: true,
                set: MANDATORY_SETTER_FUNCTION(keyName),
                get: DEFAULT_GETTER_FUNCTION(keyName)
              };

              if (REDEFINE_SUPPORTED) {
                Object.defineProperty(obj, keyName, defaultDescriptor);
              } else {
                handleBrokenPhantomDefineProperty(obj, keyName, defaultDescriptor);
              }
            } else {
              obj[keyName] = data;
            }
          } else {
            obj[keyName] = data;
          }
        } else {
          value = desc;

          // fallback to ES5
          Object.defineProperty(obj, keyName, desc);
        }
      }

      // if key is being watched, override chains that
      // were initialized with the prototype
      if (watching) { overrideChains(obj, keyName, meta); }

      // The `value` passed to the `didDefineProperty` hook is
      // either the descriptor or data, whichever was passed.
      if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }

      return this;
    }

    function handleBrokenPhantomDefineProperty(obj, keyName, desc) {
      // https://github.com/ariya/phantomjs/issues/11856
      Object.defineProperty(obj, keyName, { configurable: true, writable: true, value: 'iCry' });
      Object.defineProperty(obj, keyName, desc);
    }