projects/wms-framework/src/lib/basecomponentmodel/DependencyObject.ts
Dependency object support class
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 |
Private addRelatedSubscriptionIfRequired | ||||||||||||
addRelatedSubscriptionIfRequired(event: SubscriptionEvent<any>, theHandler: any, bindingExpression: BindingExpression)
|
||||||||||||
Parameters :
Returns :
void
|
Private addSubscriptionForTwoWayBindings | ||||||||||||
addSubscriptionForTwoWayBindings(dependencyProperty: DependencyProperty, binding: Binding, bindingExpression: BindingExpression)
|
||||||||||||
Parameters :
Returns :
void
|
addsValidationMessage | |||||||||
addsValidationMessage(propKey: Binding | string, message: string)
|
|||||||||
Adds a new validation message associated to a binding or a property name
Parameters :
Returns :
void
|
Private beginSubscribeToMultiPropertyPathChanges | |||||||||||||||
beginSubscribeToMultiPropertyPathChanges(context: any, binding: Binding, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression)
|
|||||||||||||||
Parameters :
Returns :
void
|
Private checkForSubscriptoinToDataErrorInfo | |||||||||||||||
checkForSubscriptoinToDataErrorInfo(currentContext: any, binding: Binding, propertyToSubscribe: string, bindingExpression: BindingExpression)
|
|||||||||||||||
Parameters :
Returns :
void
|
Public clearValue | ||||||
clearValue(property: DependencyProperty)
|
||||||
Clears the dependency property value
Parameters :
Returns :
void
|
Equals | ||||||
Equals(obj: any)
|
||||||
Compares this dependency object agains another object.
Parameters :
Returns :
boolean
{boolean} |
GetChild | ||||||
GetChild(idx: number)
|
||||||
Gets the child element at the index position
Parameters :
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 :
Returns :
any
|
Public getValue | ||||||
getValue(property: string)
|
||||||
Parameters :
Returns :
any
|
Public getValue | ||||||
getValue(property: string | DependencyProperty)
|
||||||
Parameters :
Returns :
any
|
Private handleErrorsOnContext | |||||||||
handleErrorsOnContext(errorCtxt: INotifyDataErrorInfo, args: DataErrorsChangedEventArgs)
|
|||||||||
Check and register possible errors in an error context
Parameters :
Returns :
void
|
Public IsPropertySet | ||||||
IsPropertySet(propertyName: string)
|
||||||
Indicates if the property has been already set.
Parameters :
Returns :
any
|
Private performDataErrorValidationIfRequired |
performDataErrorValidationIfRequired(binding: Binding, theBindingContext: any)
|
Performs validation on model if the model implementes IDataErrorINfo
Returns :
void
|
Private performRemoveValidationIfInValidationError | ||||||
performRemoveValidationIfInValidationError(binding: Binding)
|
||||||
Performs the remove validation if the InValidationError is true
Parameters :
Returns :
void
|
Public performTargetObjectBindingUpdate | |||||||||
performTargetObjectBindingUpdate(dependencyProperty: DependencyProperty, binding: Binding)
|
|||||||||
Updates the target object value associated to the binding.
Parameters :
Returns :
void
|
Private preprocessValue | |||||||||
preprocessValue(property: DependencyProperty, value: any)
|
|||||||||
Preprocess value to be set to dependency property
Parameters :
Returns :
any
|
Private recreateSubscriptionsForMultiPropertyBindingPath | ||||||||||||||||||||||||
recreateSubscriptionsForMultiPropertyBindingPath(currentContext: any, outerContext: any, properties: string[], propertyIndex: number, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression, subscriptionsForCurrentBindingPath: Array<>)
|
||||||||||||||||||||||||
Parameters :
Returns :
void
|
Private registerSetPropertyIfRequired | ||||||
registerSetPropertyIfRequired(name: string)
|
||||||
Register set property if required
Parameters :
Returns :
void
|
Private removeSubscriptionsForMultiPropertyBindingPath | |||||||||
removeSubscriptionsForMultiPropertyBindingPath(subscriptionsForCurrentBindingPath: [], propertyIndex: number)
|
|||||||||
Parameters :
Returns :
void
|
removeValidationMessage | ||||||
removeValidationMessage(propKey: Binding | string)
|
||||||
Removes a validation message associated to a binding or a property name
Parameters :
Returns :
void
|
SetBinding | ||||||||||||
SetBinding(dependencyProperty: DependencyProperty, binding: Binding)
|
||||||||||||
Sets a binding to the specified property at runtime
Parameters :
Returns :
void
|
Public setValue | |||||||||
setValue(property: DependencyProperty, value: any)
|
|||||||||
Sets the value for a dependency property
Parameters :
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 :
Returns :
void
|
Protected shouldPreventDefaultSourceUpdate | |||||||||
shouldPreventDefaultSourceUpdate(dependencyProperty: DependencyProperty, binding: BindingExpression)
|
|||||||||
Parameters :
Returns :
boolean
|
Private subscribeToBindingPathChanges | |||||||||||||||
subscribeToBindingPathChanges(context: any, binding: Binding, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression)
|
|||||||||||||||
Parameters :
Returns :
void
|
Private subscribeToErrorsChangedInModel | ||||||||||||
subscribeToErrorsChangedInModel(errorCtxt: INotifyDataErrorInfo, propertyToSubscribe: string, bindingExpression: BindingExpression)
|
||||||||||||
Parameters :
Returns :
void
|
Private subscribeToPropertyChanges | ||||||||||||||||||||||||
subscribeToPropertyChanges(currentContext: any, outerContext: any, properties: string[], propertyIndex: number, dependencyProperty: DependencyProperty, bindingExpression: BindingExpression, subscriptionsForCurrentBindingPath: Array<>)
|
||||||||||||||||||||||||
Parameters :
Returns :
void
|
Private updateSourceOfBinding | ||||||||||||
updateSourceOfBinding(binding: Binding, outerContext: any, dependencyProperty: DependencyProperty)
|
||||||||||||
Updates the source of a binding
Parameters :
Returns :
void
|
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 :
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';