Beispiel #1
0
export function getChainTagsForKey(obj: any, key: string) {
  let chainTags: Tag[] = [];

  let current: any = obj;
  let segments = key.split('.');

  // prevent closures
  let segment: string, descriptor: any;

  while (segments.length > 0) {
    segment = segments.shift()!;

    if (segment === '@each' && segments.length > 0) {
      assert(
        `When using @each, the value you are attempting to watch must be an array, was: ${current.toString()}`,
        Array.isArray(current) || isEmberArray(current)
      );

      segment = segments.shift()!;

      // Push the tags for each item's property
      let tags = (current as Array<any>).map(item => {
        assert(
          `When using @each to observe the array \`${current.toString()}\`, the items in the array must be objects`,
          typeof item === 'object'
        );

        return tagForProperty(item, segment);
      });

      // Push the tag for the array length itself
      chainTags.push(...tags, tagForProperty(current, '[]'));

      // There shouldn't be any more segments after an `@each`, so break
      assert(`When using @each, you can only chain one property level deep`, segments.length === 0);

      break;
    }

    let propertyTag = tagForProperty(current, segment);

    chainTags.push(propertyTag);

    descriptor = descriptorForProperty(current, segment);

    if (descriptor === undefined) {
      // TODO: Assert that current[segment] isn't an undecorated, non-MANDATORY_SETTER getter

      if (!(segment in current) && typeof current.unknownProperty === 'function') {
        current = current.unknownProperty(segment);
      } else {
        current = current[segment];
      }
    } else {
      let lastRevision = getLastRevisionFor(current, segment);

      if (propertyTag.validate(lastRevision)) {
        if (typeof descriptor.altKey === 'string') {
          // it's an alias, so just get the altkey without tracking
          track(() => {
            current = get(obj, descriptor.altKey);
          });
        } else {
          current = peekCacheFor(current).get(segment);
        }
      } else if (segments.length > 0) {
        let placeholderTag = UpdatableTag.create(CONSTANT_TAG);

        metaFor(current)
          .writableLazyChainsFor(segment)
          .push([segments.join('.'), placeholderTag]);

        chainTags.push(placeholderTag);

        break;
      }
    }

    let currentType = typeof current;

    if (current === null || (currentType !== 'object' && currentType !== 'function')) {
      // we've hit the end of the chain for now, break out
      break;
    }
  }

  return combine(chainTags);
}
Beispiel #2
0
export function get(obj: object, keyName: string): any {
  assert(
    `Get must be called with two arguments; an object and a property key`,
    arguments.length === 2
  );
  assert(
    `Cannot call get with '${keyName}' on an undefined object.`,
    obj !== undefined && obj !== null
  );
  assert(
    `The key provided to get must be a string or number, you passed ${keyName}`,
    typeof keyName === 'string' || (typeof keyName === 'number' && !isNaN(keyName))
  );
  assert(
    `'this' in paths is not supported`,
    typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0
  );

  let type = typeof obj;

  let isObject = type === 'object';
  let isFunction = type === 'function';
  let isObjectLike = isObject || isFunction;

  if (isPath(keyName)) {
    return isObjectLike ? _getPath(obj, keyName) : undefined;
  }

  let value: any;

  if (isObjectLike) {
    let tracking = isTracking();

    if (EMBER_METAL_TRACKED_PROPERTIES) {
      if (tracking) {
        consume(tagForProperty(obj, keyName));
      }
    }

    let descriptor = descriptorForProperty(obj, keyName);
    if (descriptor !== undefined) {
      return descriptor.get(obj, keyName);
    }

    if (DEBUG && HAS_NATIVE_PROXY) {
      value = getPossibleMandatoryProxyValue(obj, keyName);
    } else {
      value = obj[keyName];
    }

    // Add the tag of the returned value if it is an array, since arrays
    // should always cause updates if they are consumed and then changed
    if (
      EMBER_METAL_TRACKED_PROPERTIES &&
      tracking &&
      (Array.isArray(value) || isEmberArray(value))
    ) {
      consume(tagForProperty(value, '[]'));
    }
  } else {
    value = obj[keyName];
  }

  if (value === undefined) {
    if (
      isObject &&
      !(keyName in obj) &&
      typeof (obj as MaybeHasUnknownProperty).unknownProperty === 'function'
    ) {
      return (obj as MaybeHasUnknownProperty).unknownProperty!(keyName);
    }
  }
  return value;
}