File

projects/wms-framework/src/lib/basecomponentmodel/Bindings/PropertyPath.ts

Description

Base class for path parts

Index

Properties
Methods

Constructor

constructor(propertyName: string)

Creates an instance of PathPart.

Parameters :
Name Type Optional Description
propertyName string No

name of the property in the path

Properties

Public propertyName
Type : string
name of the property in the path

Methods

Abstract Change
Change(obj: any, value: any)

Sets the value for the given fragment

Parameters :
Name Type Optional
obj any No
value any No
Returns : void
Abstract Evaluate
Evaluate(obj: any)

Evaluates the fragment for the given element

Parameters :
Name Type Optional
obj any No
Returns : any

{*}

import {
  ReflectionHelper,
  RuntimeTypeInfo,
} from '../../baseframework/ReflectionSupport';

/**
 * A class for representing data-binding property property paths
 *
 * For example "Prop1.Prop2"
 *
 * @export
 * @class PropertyPath
 * @wType System.Windows.PropertyPath
 */
export class PropertyPath {
  /**
   *  property string
   *
   * @type {*}
   * @memberof PropertyPath
   */
  public prop: any = null;

  /**
   *  Property path
   *
   * @type {string}
   * @memberof PropertyPath
   * @wProperty Path
   */
  public path: string = '';

  /**
   * Property path elements (created only when having more than one property)
   *
   * @type {PathPart[]}
   * @memberof PropertyPath
   */
  private parts: PathPart[] = null;

  /**
   * Creates an instance of PropertyPath.
   * @param {unknown} obj
   * @memberof PropertyPath
   */
  constructor(obj: unknown);
  constructor(obj: unknown, path: unknown[]);
  constructor(p1: unknown, p2?: unknown) {
    this.prop = p1;
    if (typeof p1 === 'string') {
      this.path = p1;
      const depPropMatch = this.path.match(/^\([^.]+\.([^.]+)\)$/);
      if (depPropMatch && depPropMatch.length === 2) {
        this.parts = [new PropertyPathPart(depPropMatch[1])];
      } else if (this.path.indexOf('.') !== -1) {
        this.parts = this.path
          .split('.')
          .filter((x) => !!x)
          .map(processParthPartString);
      }
    }
  }

  /**
   *  Gets the path string of this property path
   *
   * @readonly
   * @memberof PropertyPath
   */
  public get Path() {
    return this.path;
  }

  /**
   * Verifies if the current property path has more than one element
   *
   * @return {*}
   * @memberof PropertyPath
   */
  public isSingleProperty() {
    return this.parts === null;
  }

  /**
   *  Gets the parts of this property
   *
   * @return {*}  {string[]}
   * @memberof PropertyPath
   */
  public getPathPartsPropertyNames(): string[] {
    return this.parts?.map((part) => part.propertyName) ?? [];
  }

  /**
   * Tries to calculate the value of the property indicated
   * by this property path
   *
   * @param {*} contextObj object to get the property from (root)
   * @return {*} the value of the property or `null` if not found
   * @memberof PropertyPath
   */
  public getValueFromContextObject(contextObj: any): unknown {
    if (this.path === '.') {
      return contextObj;
    } else if (this.parts?.length > 0) {
      return this.getFromMultiParts(contextObj);
    } else if (this.prop) {
      if (!(this.prop in contextObj) && contextObj?.constructor) {
        contextObj = contextObj.constructor;
      }
      return contextObj[this.prop];
    } else {
      return contextObj;
    }
  }

  /**
   *  Sets the value of the property indicated by the current property path
   *
   * @param {*} contextObj object taken as the root of the property
   * @param {unknown} value value to set
   * @memberof PropertyPath
   */
  public setValueContextObject(contextObj: any, value: unknown) {
    if (this.parts?.length > 0) {
      let result = contextObj;
      for (let i = 0; i < this.parts.length - 1; i++) {
        if (result) {
          result = this.parts[i].Evaluate(result);
        }
      }
      if (typeof result === 'undefined') {
        console.debug(`Cannot find value of property to change: ${this.path}`);
      } else {
        this.parts[this.parts.length - 1].Change(result, value);
      }
    } else if (this.prop) {
      contextObj[this.prop] = value;
    } else {
      console.debug('Cannot change value!');
    }
  }

  /**
   *  Gets the type of the target property if available
   *
   * @param {*} contextObj object taken as the root of the property
   * @memberof PropertyPath
   */
  public getTypeOfPropertyIfAvailable(contextObj: any): RuntimeTypeInfo {
    let ownerObject: any = null;
    let property: string = null;
    if (this.parts?.length > 0) {
      let result = contextObj;
      for (let i = 0; i < this.parts.length - 1; i++) {
        if (result) {
          result = this.parts[i].Evaluate(result);
        }
      }
      if (typeof result === 'undefined' && result === null) {
        return null;
      } else {
        ownerObject = result;
        property = this.parts[this.parts.length - 1].propertyName;
      }
    } else if (this.prop) {
      ownerObject = contextObj;
      property = this.prop;
    }
    if (ownerObject && ownerObject.constructor && property) {
      const ownerType = ReflectionHelper.getTypeInfo(ownerObject.constructor);
      const propertyInfo = ownerType.getProperty(property);
      return propertyInfo?.propertyType ?? null;
    } else {
      return null;
    }
  }

  private getFromMultiParts(contextObj: any) {
    let result = contextObj;
    for (const part of this.parts) {
      if (result && typeof part.Evaluate(result) !== 'undefined') {
        // In this case the path fragment refers to an instance field or property
        result = part.Evaluate(result);
      } else if (
        result &&
        result.constructor &&
        typeof part.Evaluate(result.constructor) !== 'undefined'
      ) {
        // In this case the path fragment is referenting to a static field or property
        result = part.Evaluate(result.constructor);
      } else {
        result = undefined;
      }
    }
    if (typeof result === 'undefined') {
      console.debug(`Cannot find value of property: ${this.path}`);
    }
    return result;
  }
}

/**
 *  Base class for path parts
 *
 * @abstract
 * @class PathPart
 */
abstract class PathPart {
  /**
   * Creates an instance of PathPart.
   * @param {string} propertyName name of the property in the path
   * @memberof PathPart
   */
  constructor(public propertyName: string) {}
  /**
   *  Evaluates the fragment for the given element
   *
   * @abstract
   * @param {*} obj
   * @return {*}  {*}
   * @memberof PathPart
   */
  abstract Evaluate(obj: any): any;

  /**
   *  Sets the `value` for the given fragment
   *
   * @abstract
   * @param {*} obj
   * @param {*} value
   * @memberof PathPart
   */
  abstract Change(obj: any, value: any): void;
}

/**
 *  Path part for simple property access. For example 'a.b'
 *
 * @class PropertyPathPart
 * @extends {PathPart}
 */
class PropertyPathPart extends PathPart {
  constructor(propertyName: string) {
    super(propertyName);
  }

  /**
   *  Returns the property value
   *
   * @param {*} obj
   * @return {*}
   * @memberof PropertyPathPart
   */
  Evaluate(obj: any) {
    return obj[this.propertyName];
  }

  /**
   *  Changes the property
   *
   * @param {*} obj
   * @param {*} value
   * @memberof PropertyPathPart
   */
  Change(obj: any, value: any): void {
    obj[this.propertyName] = value;
  }
}

/**
 *  Indexer access path parts. For example `a.b[0]`
 *
 * @class IndexerPathPart
 * @extends {PathPart}
 */
class IndexerPathPart extends PathPart {
  /**
   * Creates an instance of IndexerPathPart.
   * @param {string} propertyName
   * @param {*} index
   * @memberof IndexerPathPart
   */
  constructor(propertyName: string, private index: any) {
    super(propertyName);
  }

  /**
   * Gets the array or collection index
   *
   * @param {*} obj
   * @return {*}
   * @memberof IndexerPathPart
   */
  Evaluate(obj: any) {
    const objToIndex = obj[this.propertyName];
    if (typeof objToIndex !== 'undefined') {
      if (Array.isArray(objToIndex)) {
        return objToIndex[this.index];
      } else if (
        typeof objToIndex.getItem === 'function' &&
        this.validIndex(objToIndex)
      ) {
        return objToIndex.getItem(this.index);
      }
    }
    return undefined;
  }

  /**
   *  Sets the array or collection value
   *
   * @param {*} obj
   * @param {*} value
   * @memberof IndexerPathPart
   */
  Change(obj: any, value: any): void {
    const objToIndex = obj[this.propertyName];
    if (typeof objToIndex !== 'undefined') {
      if (Array.isArray(objToIndex)) {
        objToIndex[this.index] = value;
      } else if (typeof objToIndex.setItem !== 'undefined') {
        objToIndex.setItem(this.index, value);
      }
    }
  }

  /**
   * Returns `true` if the index is below `count` or `length` of `obj`,
   * `false` otherwise.
   *
   * @private
   * @param {*} obj
   * @return {*}  {boolean}
   * @memberof IndexerPathPart
   */
  private validIndex(obj: any): boolean {
    return obj.count > this.index || obj.length > this.index;
  }
}

/**
 *  Creates an instance of the required PathPart object for the given string
 *
 * @param {string} stringPart
 * @return {*} the path part object created for the given string
 */
const processParthPartString = (stringPart: string) => {
  const match = stringPart.match(/^([^[]+)\[([^\]]+)\]$/);
  if (match && match.length > 2) {
    return new IndexerPathPart(match[1], match[2]);
  } else {
    return new PropertyPathPart(stringPart);
  }
};

result-matching ""

    No results matching ""