projects/wms-framework/src/lib/basecomponentmodel/Bindings/PropertyPath.ts
Base class for path parts
Properties |
|
Methods |
constructor(propertyName: string)
|
||||||||
Creates an instance of PathPart.
Parameters :
|
Public propertyName |
Type : string
|
name of the property in the path
|
Abstract Change |
Change(obj: any, value: any)
|
Sets the
Returns :
void
|
Abstract Evaluate | ||||||
Evaluate(obj: any)
|
||||||
Evaluates the fragment for the given element
Parameters :
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);
}
};