Home Reference Source

src/utils.js

import angular from 'angular';

// Static object members need to be overridden async since some keys
// need to be resolved AFTER the class has fully been defined. This is
// because some property decorators might rely on class decorator configs,
// and property decorators are called before class decorators.
export function addStaticGetterObjectMember(cls, propertyName, key, value, override = true) {
  const currentPropertyDescriptor = Reflect.getOwnPropertyDescriptor(cls, propertyName);
  Reflect.defineProperty(cls, propertyName, {
    configurable: true,
    get: () => {
      const newObject = getCurrentDescriptorValue(currentPropertyDescriptor);
      const resolvedKey = angular.isFunction(key) ? key() : key;
      if (override === true || !angular.isDefined(newObject[resolvedKey])) {
        newObject[resolvedKey] = value;
      }
      return newObject;
    }
  });
}

export function mergeStaticGetterObject(cls, propertyName, values, override = true) {
  // Look at the explanation above addStaticGetterObjectMember to see
  // why we do this override asynchronously...
  const currentPropertyDescriptor = Reflect.getOwnPropertyDescriptor(cls, propertyName);
  Reflect.defineProperty(cls, propertyName, {
    configurable: true,
    get: () => {
      return override
        ? Object.assign({}, getCurrentDescriptorValue(currentPropertyDescriptor), values)
        : Object.assign({}, values, getCurrentDescriptorValue(currentPropertyDescriptor));
    }
  });
}

export function addStaticGetterArrayMember(cls, propertyName, value) {
  mergeStaticGetterArray(cls, propertyName, [value]);
}

export function mergeStaticGetterArray(cls, propertyName, values) {
  const getterArray = cls[propertyName] || [];
  for (const value of values) {
    if (getterArray.indexOf(value) === -1) {
      getterArray.push(value);
    }
  }
  Reflect.defineProperty(cls, propertyName, {
    configurable: true,
    get: () => getterArray
  });
}

export function addStaticGetter(cls, property, getter) {
  Reflect.defineProperty(cls, property, {configurable: true, get: getter});
}

export function addBehavior(cls, BehaviorCls, {property, config, proxy}) {
  const internalProperty = `_${property}`;
  Reflect.defineProperty(cls.prototype, property, {
    get() {
      if (!this[internalProperty]) {
        this[internalProperty] = new BehaviorCls(this, config);
      }
      return this[internalProperty];
    }
  });

  if (proxy) {
    addProxies(cls, BehaviorCls, property, proxy);
  }
}

export function addProxies(cls, BehaviorCls, property, proxies) {
  for (const proxy of proxies) {
    const parts = proxy.split(':');
    const localName = parts[0].trim();

    // We don't want to override any methods that already exist in the prototype
    // of the target cls. If the method already exists, its the class author's
    // responsibility to call behavior methods.
    if (!Reflect.getOwnPropertyDescriptor(cls.prototype, localName)) {
      const externalName = parts[1] ? parts[1].trim() : localName;
      const descriptor = Reflect.getOwnPropertyDescriptor(BehaviorCls.prototype, externalName);

      // This should be a simple property
      if (angular.isUndefined(descriptor) || angular.isDefined(descriptor.get)) {
        Reflect.defineProperty(cls.prototype, localName, {

          /*eslint-disable no-loop-func */
          get() {
            return this[property][externalName];
          }

          /*eslint-disable no-loop-func */
        });
      } else if (angular.isDefined(descriptor.value)) {
        Reflect.defineProperty(cls.prototype, localName, {

          /*eslint-disable no-loop-func */
          value() {
            return this[property][externalName](...arguments);
          }

          /*eslint-enable no-loop-func */
        });
      }
    }
  }
}

export function Inject(injectionName) {
  return (cls, propertyName) => {
    let preparedInjectionName = injectionName;
    if (!preparedInjectionName) {
      preparedInjectionName = `${propertyName[0].toUpperCase()}${propertyName.slice(1)}`;
    }
    addStaticGetterObjectMember(cls.constructor, 'injections',
      propertyName, preparedInjectionName);
  };
}

export function Decorators(decorators) {
  return cls => {
    for (const decorator of decorators) {
      addStaticGetterArrayMember(cls, 'decorators', decorator);
    }
  };
}

export function getCurrentDescriptorValue(propertyDescriptor) {
  if (propertyDescriptor === undefined) {
    return {};
  } else if (propertyDescriptor.get) {
    return propertyDescriptor.get();
  }
  return propertyDescriptor.value;
}

export function camelcase(name) {
  return `${name[0].toUpperCase()}${name.slice(1)}`;
}