File

projects/wms-framework/src/lib/basecomponentmodel/DependencyObject.ts

Description

Dependency object support class

Implements

Hashable

Index

Properties
Methods
Accessors

Properties

Protected bindingExpressions
Default value : new SimpleDictionary< string, BindingExpression >()
Public BindingValidationError
Default value : new SubscriptionEvent< (sender: any, e: BindingValidationErrorEventArgs) => void >()

Event for validation errors

Public change
Type : SubscriptionEvent<void>
Default value : new SubscriptionEvent()

Infrastructure event for notifying event changes

Public Dispatcher
Type : Dispatcher
Default value : Dispatcher.GetDispatcher()
isEnableSetPropertiesRegistry
Type : boolean
Default value : true

Flag which indicates if the dependency property set mechanism is enable

Public IsInitializingBindings
Type : boolean
Default value : false

Property to determine if bindings are being initialized

previousValidationMessage
Type : string
Default value : ''
Protected properties
Type : object
Default value : {}
Private setPropertiesRegistry
Type : Map<string | boolean>
Default value : new Map()

Registry with dependencies properties which has been set

validationerr
Default value : false
validationMessagesStack
Type : Array<>
Default value : []

Keeps a queue of validation messages registered on the current Dependency Object

Methods

Private addRelatedSubscriptionIfRequired
addRelatedSubscriptionIfRequired(event: SubscriptionEvent<any>, theHandler: any, bindingExpression: BindingExpression)
Parameters :
Name Type Optional
event SubscriptionEvent<any> No
theHandler any No
bindingExpression BindingExpression No
Returns : void
Private addSubscriptionForTwoWayBindings
addSubscriptionForTwoWayBindings(dependencyProperty: DependencyProperty, binding: Binding, bindingExpression: BindingExpression)
Parameters :
Name Type Optional
dependencyProperty DependencyProperty No
binding Binding No
bindingExpression BindingExpression No
Returns : void
addsValidationMessage
addsValidationMessage(propKey: Binding | string, message: string)

Adds a new validation message associated to a binding or a property name

Parameters :
Name Type Optional
propKey Binding | string No
message string No
Returns : void
Private beginSubscribeToMultiPropertyPathChanges
beginSubscribeToMultiPropertyPathChanges(context: any, binding: Binding, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression)
Parameters :
Name Type Optional
context any No
binding Binding No
dependencyProperty DependencyProperty No
bindingExpression BindingExpression No
Returns : void
Private checkForSubscriptoinToDataErrorInfo
checkForSubscriptoinToDataErrorInfo(currentContext: any, binding: Binding, propertyToSubscribe: string, bindingExpression: BindingExpression)
Parameters :
Name Type Optional
currentContext any No
binding Binding No
propertyToSubscribe string No
bindingExpression BindingExpression No
Returns : void
Public clearValue
clearValue(property: DependencyProperty)

Clears the dependency property value

Parameters :
Name Type Optional
property DependencyProperty No
Returns : void
Equals
Equals(obj: any)

Compares this dependency object agains another object.

Parameters :
Name Type Optional
obj any No
Returns : boolean

{boolean}

GetChild
GetChild(idx: number)

Gets the child element at the index position

Parameters :
Name Type Optional
idx number No
Returns : DependencyObject

{DependencyObject}

GetChildrenCount
GetChildrenCount()

Method o know the amount of children the parent has

Returns : number

{number}

GetHashCode
GetHashCode()

Gets a hash code for this dependency object.

Returns : number

{number}

Public getValue
getValue(property: DependencyProperty)

Gets the value of a dependency property

Parameters :
Name Type Optional
property DependencyProperty No
Returns : any
Public getValue
getValue(property: string)
Parameters :
Name Type Optional
property string No
Returns : any
Public getValue
getValue(property: string | DependencyProperty)
Parameters :
Name Type Optional
property string | DependencyProperty No
Returns : any
Private handleErrorsOnContext
handleErrorsOnContext(errorCtxt: INotifyDataErrorInfo, args: DataErrorsChangedEventArgs)

Check and register possible errors in an error context

Parameters :
Name Type Optional
errorCtxt INotifyDataErrorInfo No
args DataErrorsChangedEventArgs No
Returns : void
Public IsPropertySet
IsPropertySet(propertyName: string)

Indicates if the property has been already set.

Parameters :
Name Type Optional
propertyName string No
Returns : any
Private performDataErrorValidationIfRequired
performDataErrorValidationIfRequired(binding: Binding, theBindingContext: any)

Performs validation on model if the model implementes IDataErrorINfo

Parameters :
Name Type Optional
binding Binding No
theBindingContext any No
Returns : void
Private performRemoveValidationIfInValidationError
performRemoveValidationIfInValidationError(binding: Binding)

Performs the remove validation if the InValidationError is true

Parameters :
Name Type Optional
binding Binding No
Returns : void
Public performTargetObjectBindingUpdate
performTargetObjectBindingUpdate(dependencyProperty: DependencyProperty, binding: Binding)

Updates the target object value associated to the binding.

Parameters :
Name Type Optional
dependencyProperty DependencyProperty No
binding Binding No
Returns : void
Private preprocessValue
preprocessValue(property: DependencyProperty, value: any)

Preprocess value to be set to dependency property

Parameters :
Name Type Optional
property DependencyProperty No
value any No
Returns : any
Private recreateSubscriptionsForMultiPropertyBindingPath
recreateSubscriptionsForMultiPropertyBindingPath(currentContext: any, outerContext: any, properties: string[], propertyIndex: number, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression, subscriptionsForCurrentBindingPath: Array<>)
Parameters :
Name Type Optional
currentContext any No
outerContext any No
properties string[] No
propertyIndex number No
dependencyProperty DependencyProperty No
bindingExpression BindingExpression No
subscriptionsForCurrentBindingPath Array<> No
Returns : void
Private registerSetPropertyIfRequired
registerSetPropertyIfRequired(name: string)

Register set property if required

Parameters :
Name Type Optional
name string No
Returns : void
Private removeSubscriptionsForMultiPropertyBindingPath
removeSubscriptionsForMultiPropertyBindingPath(subscriptionsForCurrentBindingPath: [], propertyIndex: number)
Parameters :
Name Type Optional
subscriptionsForCurrentBindingPath [] No
propertyIndex number No
Returns : void
removeValidationMessage
removeValidationMessage(propKey: Binding | string)

Removes a validation message associated to a binding or a property name

Parameters :
Name Type Optional
propKey Binding | string No
Returns : void
SetBinding
SetBinding(dependencyProperty: DependencyProperty, binding: Binding)

Sets a binding to the specified property at runtime

Parameters :
Name Type Optional Description
dependencyProperty DependencyProperty No

property to bind

binding Binding No

binding object

Returns : void
Public setValue
setValue(property: DependencyProperty, value: any)

Sets the value for a dependency property

Parameters :
Name Type Optional
property DependencyProperty No
value any No
Returns : void
Public setValueForScopedRegion
setValueForScopedRegion(property: DependencyProperty, value: any, scopedRegionManager: IRegionManager)

Sets the value for a dependency property. This is a workaround to support Scoped Region Managers. A research is required to find out the way to get the application region Manager when scoped region managers are used (when creating the DelayedRegionBehavior, the correct scoped region manager must be passed as an argument).

Parameters :
Name Type Optional
property DependencyProperty No
value any No
scopedRegionManager IRegionManager No
Returns : void
Protected shouldPreventDefaultSourceUpdate
shouldPreventDefaultSourceUpdate(dependencyProperty: DependencyProperty, binding: BindingExpression)
Parameters :
Name Type Optional
dependencyProperty DependencyProperty No
binding BindingExpression No
Returns : boolean
Private subscribeToBindingPathChanges
subscribeToBindingPathChanges(context: any, binding: Binding, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression)
Parameters :
Name Type Optional
context any No
binding Binding No
dependencyProperty DependencyProperty No
bindingExpression BindingExpression No
Returns : void
Private subscribeToErrorsChangedInModel
subscribeToErrorsChangedInModel(errorCtxt: INotifyDataErrorInfo, propertyToSubscribe: string, bindingExpression: BindingExpression)
Parameters :
Name Type Optional
errorCtxt INotifyDataErrorInfo No
propertyToSubscribe string No
bindingExpression BindingExpression No
Returns : void
Private subscribeToPropertyChanges
subscribeToPropertyChanges(currentContext: any, outerContext: any, properties: string[], propertyIndex: number, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression, subscriptionsForCurrentBindingPath: Array<>)
Parameters :
Name Type Optional
currentContext any No
outerContext any No
properties string[] No
propertyIndex number No
dependencyProperty DependencyProperty No
bindingExpression BindingExpression No
subscriptionsForCurrentBindingPath Array<> No
Returns : void
Private updateSourceOfBinding
updateSourceOfBinding(binding: Binding, outerContext: any, dependencyProperty: DependencyProperty)

Updates the source of a binding

Parameters :
Name Type Optional
binding Binding No
outerContext any No
dependencyProperty DependencyProperty No
Returns : void

Accessors

validationMessage
getvalidationMessage()

Get the latest validation message registered

InValidationError
getInValidationError()

Gets the validation error condition

This property is true if the control is in a validation error condition

setInValidationError(value)

Sets the validation error condition

This property is true if the control is in a validation error condition

Parameters :
Name Optional
value No
Returns : void
import {
  convertTypeTo,
  ReflectionHelper,
} from '../baseframework/ReflectionSupport';
import { SimpleDictionary } from '../baseframework/SimpleDictionary';
import { Hashable } from '../baseframework/Hashable';
import { SubscriptionEvent } from '../utils/SubscriptionEvent';
import { Binding } from './Bindings/Binding';
import { BindingExpression } from './Bindings/BindingExpression';
import { BindingValidationErrorEventArgs } from './Bindings/BindingValidationErrorEventArgs';
import { BindingMode } from './Bindings/BindingMode';
import {
  calculateBindingValue,
  resolveBindingContext,
} from './Bindings/BindingUtils';
import { BindingValidationErrorEventAction } from './Bindings/BindingValidationErrorEventAction';
import {
  CoreDependencyProperty,
  DependencyProperty,
} from './DependencyProperty';
import { DependencyPropertyChangedEventArgs } from './DependencyPropertyChangedEventArgs';
import { Dispatcher } from './Dispatcher';
import type {
  DataErrorsChangedEventArgs,
  INotifyDataErrorInfo,
} from './INotifyDataErrorInfo';
import { IDataErrorInfo } from '../baseframework/IDataErrorInfo';
import { IRegionManager } from '../regionsframework';

/**
 * Dependency object support class
 *
 * @export
 * @class DependencyObject
 * @implements {Hashable}
 * @wType System.Windows.DependencyObject
 */
export class DependencyObject implements Hashable {
  public Dispatcher: Dispatcher = Dispatcher.GetDispatcher();
  /**
   * Infrastructure event for notifying event changes
   *
   * @memberof DependencyObject
   */
  public change: SubscriptionEvent<(propertyName: string) => void> =
    new SubscriptionEvent();

  protected properties = {};
  protected bindingExpressions = new SimpleDictionary<
    string,
    BindingExpression
  >();
  #validationerr = false;
  #previousValidationMessage: string = '';

  /**
   * Event for validation errors
   *
   * @memberof DependencyObject
   * @wIgnore
   */
  public BindingValidationError = new SubscriptionEvent<
    (sender: any, e: BindingValidationErrorEventArgs) => void
  >();

  /**
   * Get the latest validation message registered
   *
   * @readonly
   * @memberof DependencyObject
   * @wIgnore
   */
  public get validationMessage() {
    if (this.validationMessagesStack.length > 0) {
      return this.validationMessagesStack[
        this.validationMessagesStack.length - 1
      ][1];
    }
    return '';
  }

  /**
   * Keeps a queue of validation messages registered on the current Dependency Object
   *
   * @private
   * @type {(Array<[Binding|string, string]>)}
   * @memberof DependencyObject
   * @wIgnore
   */
  validationMessagesStack: Array<[Binding | string, string]> = [];

  /**
   * Registry with dependencies properties which has been set
   *
   * @private
   * @type {Map<string, boolean>}
   * @memberof DependencyObject
   * @wIgnore
   */
  private setPropertiesRegistry: Map<string, boolean> = new Map();

  /**
   * Flag which indicates if the dependency property set mechanism is enable
   *
   * @type {boolean}
   * @memberof DependencyObject
   * @wIgnore
   */
  isEnableSetPropertiesRegistry: boolean = true;

  /**
   * Adds a new validation message associated to a binding or a property name
   *
   * @private
   * @param {(Binding|string)} propKey
   * @param {string} message
   * @memberof DependencyObject
   */
  addsValidationMessage(propKey: Binding | string, message: string): void {
    const key: Binding | string =
      typeof propKey == 'string' ? propKey.trim() : propKey;
    if (!!key) {
      const idx = this.validationMessagesStack.findIndex(
        ([innerKey, value]) => innerKey === key
      );
      if (idx >= 0) {
        this.validationMessagesStack[idx] = [key, message];
      } else {
        this.validationMessagesStack.push([key, message]);
      }
    }
    this.InValidationError = this.validationMessagesStack.length > 0;
  }

  /**
   * Removes a validation message associated to a binding or a property name
   *
   * @protected
   * @param {(Binding|string)} propKey
   * @memberof DependencyObject
   */
  removeValidationMessage(propKey: Binding | string): void {
    const key: Binding | string =
      typeof propKey == 'string' ? propKey.trim() : propKey;
    const idx = this.validationMessagesStack.findIndex(
      ([innerKey, value]) => innerKey === key
    );
    if (idx >= 0) {
      this.validationMessagesStack.splice(idx, 1);
    }

    this.InValidationError = this.validationMessagesStack.length > 0;
  }

  /**
   * Property to determine if bindings are being initialized
   *
   * @type {boolean}
   * @memberof DependencyObject
   * @wIgnore
   */
  public IsInitializingBindings: boolean = false;

  /**
   * Gets a hash code for this dependency object.
   *
   * @return {*}  {number}
   * @memberof DependencyObject
   * @wIgnore
   */
  GetHashCode(): number {
    // Super fast default hash for all dependency objects.
    // Cons: Makes dictionay lookups slow.
    return 0;
  }

  /**
   * Compares this dependency object agains another object.
   *
   * @param {*} obj
   * @return {*}  {boolean}
   * @memberof DependencyObject
   * @wIgnore
   */
  Equals(obj: any): boolean {
    return this === obj;
  }

  /**
   * Clears the dependency property value
   *
   * @param {DependencyProperty} property
   * @memberof DependencyObject
   * @wMethod ClearValue
   */
  public clearValue(property: DependencyProperty) {
    this.setValue(property, property.defaultValue ?? null);
    if (this.IsPropertySet(property.name)) {
      this.setPropertiesRegistry.delete(property.name);
    }
  }

  /**
   * Gets the value of a dependency property
   *
   * @param {DependencyProperty} property
   * @memberof DependencyObject
   * @wMethod GetValue
   */
  public getValue(property: DependencyProperty);
  public getValue(property: string);
  public getValue(property: string | DependencyProperty): any {
    if (typeof property === 'string') {
      return this.properties[property];
    } else {
      const value = this.properties[property.name];
      if (typeof value === 'undefined') {
        return property.defaultValue;
      } else {
        return value;
      }
    }
  }

  /**
   * Sets the value for a dependency property
   *
   * @param {DependencyProperty} property
   * @param {*} value
   * @memberof DependencyObject
   * @wMethod SetValue
   */
  public setValue(property: DependencyProperty, value: any) {
    const oldValue =
      this.properties[property.name] === undefined
        ? property.defaultValue
        : this.properties[property.name];
    const newValue = this.preprocessValue(property, value);
    if (oldValue !== newValue) {
      this.properties[property.name] = newValue;
      this.registerSetPropertyIfRequired(property.name);
      if (property.changedCallback) {
        const args = new DependencyPropertyChangedEventArgs();
        args.OldValue = oldValue;
        args.NewValue = newValue;
        args.DependencyProperty = property;
        property.changedCallback(this, args);
      }
      if ((this as any).change) {
        (this as any).change.fire([property.name]);
      }
    }
  }

  /**
   * Sets the value for a dependency property.
   * This is a workaround to support Scoped Region Managers. A research is required to find out the way to get the application region Manager when
   * scoped region managers are used (when creating the DelayedRegionBehavior, the correct scoped region manager must be passed as an argument).
   *
   * @param {DependencyProperty} property
   * @param {*} value
   * @param {*} scopedRegionManager
   * @memberof DependencyObject
   * @wMethod SetValue
   */
  public setValueForScopedRegion(
    property: DependencyProperty,
    value: any,
    scopedRegionManager: IRegionManager
  ) {
    const oldValue =
      this.properties[property.name] === undefined
        ? property.defaultValue
        : this.properties[property.name];
    const newValue = this.preprocessValue(property, value);
    if (oldValue !== newValue) {
      this.properties[property.name] = newValue;
      this.registerSetPropertyIfRequired(property.name);
      if (property.changedCallback) {
        const args = new DependencyPropertyChangedEventArgs();
        args.OldValue = oldValue;
        args.NewValue = newValue;
        args.ScopedRegionManager = scopedRegionManager;
        args.DependencyProperty = property;
        property.changedCallback(this, args);
      }
      if ((this as any).change) {
        (this as any).change.fire([property.name]);
      }
    }
  }

  /**
   * Sets a binding to the specified property at runtime
   *
   * @param {DependencyProperty} dependencyProperty property to bind
   * @param {Binding} binding binding object
   * @memberof DependencyObject
   * @wIgnore
   */
  SetBinding(dependencyProperty: DependencyProperty, binding: Binding) {
    // If the binding already exists remove and cleanup any subscriptions
    if (this.bindingExpressions.containsKey(dependencyProperty.name)) {
      const existingItem = this.bindingExpressions.getItem(
        dependencyProperty.name
      );
      existingItem.OnDetach(this, dependencyProperty);
    }

    const bindingExpression = new BindingExpression(this, binding);
    this.bindingExpressions.setItem(dependencyProperty.name, bindingExpression);
    bindingExpression.OnAttach(this, dependencyProperty);
    if (binding.Mode === BindingMode.TwoWay) {
      this.addSubscriptionForTwoWayBindings(
        dependencyProperty,
        binding,
        bindingExpression
      );
    }

    if (
      !(
        binding.CalculateRelativeSource(this) ||
        binding.Source ||
        binding.ElementName ||
        binding.ElementInstance
      )
    ) {
      const addedHandler = this.change.addHandler((propName) => {
        if (propName === 'DataContext') {
          const context = (<any>this).DataContext ?? binding.CustomContext;
          this.subscribeToBindingPathChanges(
            context,
            binding,
            dependencyProperty,
            bindingExpression
          );
        }
      });
      bindingExpression.AddRelatedSubscription(this.change, addedHandler);
    }

    const theBindingContext = resolveBindingContext(binding, this);
    this.subscribeToBindingPathChanges(
      theBindingContext,
      binding,
      dependencyProperty,
      bindingExpression
    );
  }

  /**
   * Indicates if the property has been already set.
   *
   * @param {string} propertyName
   * @returns
   * @memberof DependencyObject
   * @wIgnore
   */
  public IsPropertySet(propertyName: string) {
    if (this.setPropertiesRegistry.has(propertyName)) {
      return this.setPropertiesRegistry.get(propertyName);
    }
    return false;
  }

  private addSubscriptionForTwoWayBindings(
    dependencyProperty: DependencyProperty,
    binding: Binding,
    bindingExpression: BindingExpression
  ) {
    const updateSourceFunc = this.change.addHandler((prop) => {
      if (prop === dependencyProperty.name) {
        bindingExpression.isDirty = true;
        if (
          !binding.inSynchronizing &&
          !this.shouldPreventDefaultSourceUpdate(
            dependencyProperty,
            bindingExpression
          )
        ) {
          this.performTargetObjectBindingUpdate(dependencyProperty, binding);
          bindingExpression.isDirty = false;
        } else {
          const theBindingContext =
            (<any>this).DataContext ?? binding.CustomContext;
          this.performDataErrorValidationIfRequired(binding, theBindingContext);
        }
      }
    });
    this.addRelatedSubscriptionIfRequired(
      this.change,
      updateSourceFunc,
      bindingExpression
    );
    bindingExpression.updateSourceFunction = () => {
      this.performTargetObjectBindingUpdate(dependencyProperty, binding);
      bindingExpression.isDirty = false;
    };
    this.performDataErrorValidationIfRequired(
      binding,
      (this as any).DataContext ?? binding.CustomContext
    );
  }

  /**
   * Updates the target object value associated to the binding.
   *
   * @param {DependencyProperty} dependencyProperty
   * @param {Binding} binding
   * @memberof DependencyObject
   */
  public performTargetObjectBindingUpdate(
    dependencyProperty: DependencyProperty,
    binding: Binding
  ) {
    const newValue = (this as any)[dependencyProperty.name];
    try {
      const theBindingContext =
        (<any>this).DataContext ?? binding.CustomContext;
      binding.ApplyBinding(newValue, theBindingContext);
      this.performRemoveValidationIfInValidationError(binding);
      this.performDataErrorValidationIfRequired(binding, theBindingContext);
    } catch (e) {
      if (binding.ValidatesOnExceptions) {
        this.performRemoveValidationIfInValidationError(binding);
        this.addsValidationMessage(binding, e?.Message ?? e.toString());
        this.BindingValidationError.fire([
          this,
          { Action: BindingValidationErrorEventAction.Added },
        ]);
      } else {
        console.log('Binding was not applied: ' + e);
      }
    }
  }

  /**
   *  Performs the remove validation if the InValidationError is true
   *
   * @private
   * @param {Binding} binding
   * @memberof DependencyObject
   */
  private performRemoveValidationIfInValidationError(binding: Binding) {
    if (binding.ValidatesOnExceptions) {
      if (this.InValidationError) {
        this.BindingValidationError.fire([
          this,
          { Action: BindingValidationErrorEventAction.Removed },
        ]);
      }
      this.removeValidationMessage(binding);
    }
  }

  /**
   *  Performs validation on model if the model implementes IDataErrorINfo
   *
   * @private
   * @param {Binding} binding
   * @param {*} theBindingContext
   * @memberof DependencyObject
   */
  private performDataErrorValidationIfRequired(
    binding: Binding,
    theBindingContext: any
  ) {
    if (
      binding.ValidatesOnDataErrors &&
      binding.Path.isSingleProperty() &&
      ReflectionHelper.isInterfaceIsSupported(
        theBindingContext,
        originalDataErrorInfoInterface
      )
    ) {
      const error = (theBindingContext as IDataErrorInfo).getItem(
        binding.Path.path
      );
      if (error && error !== '') {
        this.addsValidationMessage(binding, error);
      }
    }
  }

  private addRelatedSubscriptionIfRequired(
    event: SubscriptionEvent<any>,
    theHandler: any,
    bindingExpression: BindingExpression
  ) {
    if (this.IsInitializingBindings === true) {
      bindingExpression.AddRelatedSubscription(event, theHandler);
    }
  }

  private subscribeToBindingPathChanges(
    context: any,
    binding: Binding,
    dependencyProperty: DependencyProperty,
    bindingExpression: BindingExpression
  ) {
    const isValidContext = Binding.IsValidBindingContext(context);
    if (isValidContext && binding.Path.isSingleProperty()) {
      this.subscribeToPropertyChanges(
        context,
        context,
        [binding.Path.prop],
        0,
        dependencyProperty,
        bindingExpression,
        null
      );
    } else {
      this.beginSubscribeToMultiPropertyPathChanges(
        context,
        binding,
        dependencyProperty,
        bindingExpression
      );
    }
    if (isValidContext) {
      this.updateSourceOfBinding(binding, context, dependencyProperty);
    }
  }

  private beginSubscribeToMultiPropertyPathChanges(
    context: any,
    binding: Binding,
    dependencyProperty: DependencyProperty,
    bindingExpression: BindingExpression
  ) {
    const isValidContext = Binding.IsValidBindingContext(context);
    let currentContext = context;
    const parts = binding.Path.getPathPartsPropertyNames();
    const subscriptions: Array<[SubscriptionEvent<any>, any]> =
      parts.length > 1 ? parts.map((_) => null) : null;
    if (isValidContext && parts.length > 0) {
      if (typeof currentContext === 'object') {
        this.subscribeToPropertyChanges(
          currentContext,
          context,
          parts,
          0,
          dependencyProperty,
          bindingExpression,
          subscriptions
        );
        currentContext = currentContext[parts[0]];
      }
      for (let i = 1; i < parts.length; i++) {
        const pathPart = parts[i];
        if (currentContext) {
          this.subscribeToPropertyChanges(
            currentContext,
            context,
            parts,
            i,
            dependencyProperty,
            bindingExpression,
            subscriptions
          );
          currentContext = currentContext[pathPart];
        } else {
          break;
        }
      }
    }
  }

  private subscribeToPropertyChanges(
    currentContext: any,
    outerContext: any,
    properties: string[],
    propertyIndex: number,
    dependencyProperty: DependencyProperty,
    bindingExpression: BindingExpression,
    subscriptionsForCurrentBindingPath: Array<[SubscriptionEvent<any>, any]>
  ): void {
    const propertyToSubscribe = properties[propertyIndex];
    const binding = bindingExpression.ParentBinding;
    if (currentContext?.PropertyChanged instanceof SubscriptionEvent) {
      const addedHandler = currentContext.PropertyChanged.addHandler(
        (obj, args) => {
          if (
            args?.PropertyName.toLowerCase() ===
            propertyToSubscribe.toLowerCase()
          ) {
            this.updateSourceOfBinding(
              binding,
              outerContext,
              dependencyProperty
            );
            this.removeSubscriptionsForMultiPropertyBindingPath(
              subscriptionsForCurrentBindingPath,
              propertyIndex
            );
            this.recreateSubscriptionsForMultiPropertyBindingPath(
              currentContext,
              outerContext,
              properties,
              propertyIndex,
              dependencyProperty,
              bindingExpression,
              subscriptionsForCurrentBindingPath
            );
          }
        }
      );
      this.addRelatedSubscriptionIfRequired(
        currentContext.PropertyChanged,
        addedHandler,
        bindingExpression
      );
      if (subscriptionsForCurrentBindingPath) {
        subscriptionsForCurrentBindingPath[propertyIndex] = [
          currentContext.PropertyChanged,
          addedHandler,
        ];
      }
    } else if (currentContext instanceof DependencyObject) {
      const addedHandler = currentContext.change.addHandler((propName) => {
        if (propName === propertyToSubscribe) {
          this.updateSourceOfBinding(binding, outerContext, dependencyProperty);
        }
      });
      this.addRelatedSubscriptionIfRequired(
        currentContext.change,
        addedHandler,
        bindingExpression
      );
      if (subscriptionsForCurrentBindingPath) {
        subscriptionsForCurrentBindingPath[propertyIndex] = [
          currentContext.change,
          addedHandler,
        ];
      }
    }

    this.checkForSubscriptoinToDataErrorInfo(
      currentContext,
      binding,
      propertyToSubscribe,
      bindingExpression
    );
  }

  private removeSubscriptionsForMultiPropertyBindingPath(
    subscriptionsForCurrentBindingPath: [SubscriptionEvent<any>, any][],
    propertyIndex: number
  ) {
    if (subscriptionsForCurrentBindingPath) {
      for (
        let i = propertyIndex + 1;
        i < subscriptionsForCurrentBindingPath.length;
        i++
      ) {
        if (subscriptionsForCurrentBindingPath[i]) {
          subscriptionsForCurrentBindingPath[i][0].removeHandler(
            subscriptionsForCurrentBindingPath[i][1]
          );
        }
      }
    }
  }

  private recreateSubscriptionsForMultiPropertyBindingPath(
    currentContext: any,
    outerContext: any,
    properties: string[],
    propertyIndex: number,
    dependencyProperty: DependencyProperty,
    bindingExpression: BindingExpression,
    subscriptionsForCurrentBindingPath: Array<[SubscriptionEvent<any>, any]>
  ) {
    const propertyToSubscribe = properties[propertyIndex];
    if (properties.length - 1 > propertyIndex) {
      if (currentContext[propertyToSubscribe]) {
        let currentObj = currentContext[propertyToSubscribe];
        for (let i = propertyIndex + 1; i < properties.length; i++) {
          const nextPropertyName = properties[i];
          if (currentObj && nextPropertyName in currentObj) {
            this.subscribeToPropertyChanges(
              currentObj,
              outerContext,
              properties,
              i,
              dependencyProperty,
              bindingExpression,
              subscriptionsForCurrentBindingPath
            );
            currentObj = currentObj[nextPropertyName];
          } else {
            break;
          }
        }
      }
    }
  }

  private checkForSubscriptoinToDataErrorInfo(
    currentContext: any,
    binding: Binding,
    propertyToSubscribe: string,
    bindingExpression: BindingExpression
  ) {
    const isForcedErrorCheck =
      currentContext.constructor.IsINotifyDataErrorInfoSupported === true;
    if (
      (binding.ValidatesOnNotifyDataErrors === true &&
        ReflectionHelper.isInterfaceIsSupported(
          currentContext,
          originalErrorNotificationInterfaceName
        )) ||
      isForcedErrorCheck
    ) {
      const errorInterfaceObj = isForcedErrorCheck
        ? (currentContext as INotifyDataErrorInfo)
        : convertTypeTo<INotifyDataErrorInfo>(
            currentContext,
            ReflectionHelper.getInterfaceRuntimeTypeInfo(
              originalErrorNotificationInterfaceName
            )
          );
      this.subscribeToErrorsChangedInModel(
        errorInterfaceObj,
        propertyToSubscribe,
        bindingExpression
      );
    }
  }

  private subscribeToErrorsChangedInModel(
    errorCtxt: INotifyDataErrorInfo,
    propertyToSubscribe: string,
    bindingExpression: BindingExpression
  ) {
    const addedHandler = errorCtxt.ErrorsChanged.addHandler((sender, args) => {
      if (args?.PropertyName === propertyToSubscribe) {
        this.handleErrorsOnContext(errorCtxt, args);
      }
    });
    this.addRelatedSubscriptionIfRequired(
      errorCtxt.ErrorsChanged,
      addedHandler,
      bindingExpression
    );
    if (errorCtxt.HasErrors) {
      addedHandler(this, { PropertyName: propertyToSubscribe });
    }
  }

  /**
   * Check and register possible errors in an error context
   *
   * @private
   * @param {INotifyDataErrorInfo} errorCtxt
   * @param {DataErrorsChangedEventArgs} args
   * @return {*}
   * @memberof DependencyObject
   */
  private handleErrorsOnContext(
    errorCtxt: INotifyDataErrorInfo,
    args: DataErrorsChangedEventArgs
  ) {
    const errorsTmp = errorCtxt.GetErrors(args.PropertyName);
    if (errorsTmp != null) {
      const errors = [...errorsTmp];
      if (errors.length > 0) {
        this.addsValidationMessage(
          args.PropertyName,
          errors[0] != null
            ? ((errors[0] as any).ToString ?? errors[0].toString).apply(
                errors[0]
              ) ?? ''
            : ''
        );

        return;
      }
    }
    // Clear errors otherwise
    this.removeValidationMessage(args.PropertyName);
  }

  /**
   * Updates the source of a binding
   *
   * @private
   * @param {Binding} binding
   * @param {*} outerContext
   * @param {DependencyProperty} dependencyProperty
   * @param {boolean} [forceUpdate=false]
   * @memberof DependencyObject
   */
  private updateSourceOfBinding(
    binding: Binding,
    outerContext: any,
    dependencyProperty: DependencyProperty
  ): void {
    binding.inSynchronizing = true;
    try {
      const value = calculateBindingValue(
        binding,
        outerContext,
        dependencyProperty
      );
      if (value !== undefined) {
        if (binding.ValidatesOnExceptions) {
          if (this.InValidationError) {
            this.BindingValidationError.fire([
              this,
              { Action: BindingValidationErrorEventAction.Removed },
            ]);
          }
          this.removeValidationMessage(binding);
        }
        this.setValue(dependencyProperty, value);
        this.performDataErrorValidationIfRequired(binding, outerContext);
      }
    } finally {
      binding.inSynchronizing = false;
    }
  }

  /**
   *  Gets the validation error condition
   *
   *  This property is true if the control is in a validation error condition
   *
   * @memberof DependencyProperty
   * @wIgnore
   */
  public get InValidationError() {
    return this.#validationerr;
  }
  /**
   *  Sets the validation error condition
   *
   *  This property is true if the control is in a validation error condition
   *
   * @memberof DependencyProperty
   * @wIgnore
   */
  public set InValidationError(value) {
    if (value !== this.#validationerr) {
      this.#validationerr = value;
      this.change.fire(['validationError']);
    } else if (this.#previousValidationMessage != this.validationMessage) {
      this.change.fire(['validationError']);
    }
    this.#previousValidationMessage = this.validationMessage;
  }

  protected shouldPreventDefaultSourceUpdate(
    dependencyProperty: DependencyProperty,
    binding: BindingExpression
  ) {
    if (binding.listenToLostFocus && binding.IsTargetFocused()) {
      return true;
    }
    return false;
  }

  /**
   * Preprocess value to be set to dependency property
   *
   * @private
   * @param {DependencyProperty} property
   * @param {*} value
   * @returns
   * @memberof DependencyObject
   */
  private preprocessValue(property: DependencyProperty, value: any) {
    if (property instanceof CoreDependencyProperty) {
      return property.preprocessSetValue(value);
    }
    return value;
  }

  /**
   * Register set property if required
   *
   * @private
   * @param {string} name
   * @memberof DependencyObject
   */
  private registerSetPropertyIfRequired(name: string) {
    if (this.isEnableSetPropertiesRegistry) {
      this.setPropertiesRegistry.set(name, true);
    }
  }

  /**
   * Method o know the amount of children the parent has
   *
   * @return {*}  {number}
   * @memberof DependencyObject
   * @wIgnore
   */
  GetChildrenCount(): number {
    return 0;
  }

  /**
   * Gets the child element at the index position
   *
   * @param {number} idx
   * @return {*}  {DependencyObject}
   * @memberof DependencyObject
   * @wIgnore
   */
  GetChild(idx: number): DependencyObject {
    return null;
  }
}

/**
 * Original name of error notification interfaces
 * @type {*}
 */
const originalErrorNotificationInterfaceName =
  'System.ComponentModel.INotifyDataErrorInfo';

/**
 * Name of the original contract for data error info
 */
const originalDataErrorInfoInterface = 'System.ComponentModel.IDataErrorInfo';

result-matching ""

    No results matching ""