import { DependencyContainer, inject, injectable } from 'tsyringe';
import { UIElement } from '../basecomponentmodel/UIElement';
import { InvalidOperationException } from '../baseframework/Exceptions';
import {
ReflectionHelper,
RuntimeTypeInfo,
} from '../baseframework/ReflectionSupport';
import { SimpleDictionary } from '../baseframework/SimpleDictionary';
import { SubscriptionEvent } from '../utils/SubscriptionEvent';
import {
regionManagerProperty,
regionNameProperty,
} from './directives/RegionManager.directive';
import {
containerInjectionToken,
regionManagerInjectionToken,
regionViewRegistryInjectionToken,
} from './injectionTokens';
import { IRegion } from './IRegion';
import { IRegionCollection } from './IRegionCollection';
import { IRegionManager } from './IRegionManager';
import { IRegionViewRegistry } from './IRegionViewRegistry';
import { RegionAdapterMappings } from './RegionAdapterMappings';
import { ServiceLocator } from './servicelocation/ServiceLocator';
/**
* Auxiliary Type for WeakRefs
*/
type WeakRef<T> = {
deref: () => T;
};
/**
* Internal constant to access weak references
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
let WeakRefActualType = (window as any).WeakRef as new (
obj: any
) => WeakRef<any>;
let weakReferencesSupported = typeof WeakRefActualType !== 'undefined';
/**
* Exported function to customize the weak reference configuration (exported for testing)
* @param weakRefType
*/
export const changeWeakReferenceConfiguration = (weakRefType: any) => {
weakReferencesSupported = typeof weakRefType !== 'undefined';
WeakRefActualType = weakRefType;
};
/**
* Implementation of region manager
*
* @export
* @class RegionManager
* @implements {IRegionManager}
* @wType Microsoft.Practices.Prism.Regions.RegionManager
*/
@injectable()
export class RegionManager implements IRegionManager {
public static UpdatingRegions = new SubscriptionEvent();
regionsCollection: IRegionCollection;
private regionsTargets = new SimpleDictionary<
string,
UIElement | WeakRef<UIElement>
>();
private verifyingForRegionsToRemove = false;
get Regions(): IRegionCollection {
return this.regionsCollection;
}
set Regions(value: IRegionCollection) {}
constructor(
private mappings: RegionAdapterMappings,
@inject(regionViewRegistryInjectionToken)
private viewsRegistry: IRegionViewRegistry,
@inject(containerInjectionToken) private container: DependencyContainer
) {
this.regionsCollection = new RegionCollection(this);
if (weakReferencesSupported) {
RegionManager.UpdatingRegions.addHandler(() => {
if (!this.verifyingForRegionsToRemove) {
this.verifyingForRegionsToRemove = true;
try {
for (const viewPairToCheck of this.regionsTargets) {
if (
WeakRefActualType &&
viewPairToCheck[1] instanceof WeakRefActualType &&
typeof viewPairToCheck[1].deref() === 'undefined'
) {
console.log('collecting weak viewref');
this.Regions.Remove(viewPairToCheck[0]);
}
}
} finally {
this.verifyingForRegionsToRemove = false;
}
}
});
}
}
/**
* Sets the region name for the given view
*
* @static
* @param {unknown} view
* @param {string} regionName
* @memberof RegionManager
*/
public static SetRegionName(view: unknown, regionName: string) {
/* istanbul ignore else */
if (view instanceof UIElement) {
view.setValue(regionNameProperty, regionName);
} else {
throw new Error('Unexpected view when setting region');
}
}
/**
* Sets the region name for the given view in an scoped RegionManager
*
* @static
* @param {unknown} view
* @param {string} regionName
* @memberof RegionManager
*/
public static SetRegionNameInScopedRegion(
view: unknown,
regionName: string,
scopedRegionManager: IRegionManager
) {
/* istanbul ignore else */
if (view instanceof UIElement) {
view.setValueForScopedRegion(
regionNameProperty,
regionName,
scopedRegionManager
);
} else {
throw new Error('Unexpected view when setting region');
}
}
/**
* Gets the value of the `RegionName` property for the given view
*
* @static
* @param {unknown} view
* @return {*} {string}
* @memberof RegionManager
*/
public static GetRegionName(view: unknown): string {
if (view instanceof UIElement) {
return (view.getValue(regionNameProperty) ?? null) as string;
} else {
throw new Error('Unexpected view when getting region');
}
}
/**
* Removes the region with the given name when the host is being removed from the screen
*
* @static
* @param {string} regionName
* @param {unknown} host
* @memberof RegionManager
* @wIgnore
*/
public static ProcessRegionHostBeingRemoved(
regionName: string,
host: unknown
) {
let regionManager: IRegionManager;
regionManager = ServiceLocator.current.getInstance(
regionManagerInjectionToken
) as IRegionManager;
if (regionManager) {
regionManager.Regions.Remove(regionName);
}
}
/**
* Creates a child region manager
*
* @return {*} {IRegionManager}
* @memberof RegionManager
*/
public CreateRegionManager(container?: DependencyContainer): IRegionManager {
return container
? new RegionManager(this.mappings, this.viewsRegistry, container)
: this;
}
/**
* Registers a host control to a region
*
* @param {string} regionName
* @param {UIElement} view
* @memberof RegionManager
* @wIgnore
*/
RegisterRegionTarget(regionName: string, view: UIElement) {
this.checkForDeadRegion(regionName);
this.registerRegionTarget(regionName, view);
var typeInfo = ReflectionHelper.getTypeInfo(view);
let adapter = this.mappings.getAdapter(typeInfo);
if (adapter) {
let region = adapter.Initialize(view, regionName);
let viewList: Array<unknown> = null;
for (let aView of this.viewsRegistry.GetContents(regionName)) {
region.Add(aView);
}
region.Name = regionName;
this.regionsCollection.Add(region);
} else {
throw new Error('Cannot find adapter for ' + typeInfo.FullName);
}
}
/**
* Registers a view inside a region
*
* @param {string} regionName
* @param {(((() => unknown) | RuntimeTypeInfo))} viewCreationFunc
* @memberof RegionManager
*/
public RegisterViewWithRegion(
regionName: string,
viewCreationFunc: (() => unknown) | RuntimeTypeInfo
): void {
this.viewsRegistry.RegisterViewWithRegion(regionName, viewCreationFunc);
}
/**
* Adds a view to a region
*
* @param {string} regionName
* @param {unknown} object
* @memberof RegionManager
*/
public AddToRegion(regionName: string, object: unknown): void {
if (this.regionsCollection.ContainsRegionWithName(regionName)) {
let theRegion = this.regionsCollection.getItem(regionName);
theRegion.Add(object);
} else {
throw new Error('Region not found: ' + regionName);
}
}
/**
* Raises the notification for updating regions
*
* @static
* @memberof RegionManager
* @wIgnore
*/
public static NotifyOfUpdatingRegions(): void {
RegionManager.UpdatingRegions.fire([]);
}
private checkForDeadRegion(regionName: string) {
if (this.regionsTargets.containsKey(regionName)) {
let actualTarget: any = this.regionsTargets.getItem(regionName);
if (actualTarget instanceof WeakRefActualType) {
actualTarget = actualTarget.deref();
}
if (!this.checkIfStillAttached(actualTarget)) {
this.Regions.Remove(regionName);
}
}
}
private registerRegionTarget(regionName: string, view: UIElement) {
this.regionsTargets.setItem(
regionName,
weakReferencesSupported ? new WeakRefActualType(view) : view
);
}
private checkIfStillAttached(model) {
let result = false;
let tmpModel = model;
while (tmpModel) {
result = tmpModel.getValue(regionManagerProperty) !== null;
if (result) {
break;
} else {
tmpModel = tmpModel.Parent;
}
}
return result;
}
}
export class RegionCollection implements Iterable<IRegion>, IRegionCollection {
private regionManager: IRegionManager;
private regions: SimpleDictionary<string, IRegion> = new SimpleDictionary<
string,
IRegion
>();
constructor(regionManager: IRegionManager) {
this.regionManager = regionManager;
}
Add(region: IRegion): void {
if (region == null) {
throw new Error('Region is null.');
}
if (region.Name == null) {
throw new InvalidOperationException('Region cannot be empty');
}
RegionManager.NotifyOfUpdatingRegions();
if (this.getItem(region.Name) != null) {
throw new InvalidOperationException('Region already exist');
}
this.regions.add([region.Name, region]);
region.RegionManager = this.regionManager;
}
Remove(regionName: string): boolean {
let exists = this.regions.containsKey(regionName);
if (exists) {
this.regions.removeEntry(regionName);
RegionManager.NotifyOfUpdatingRegions();
} else {
RegionManager.NotifyOfUpdatingRegions();
exists = this.regions.containsKey(regionName);
if (exists) {
this.regions.removeEntry(regionName);
}
}
return exists;
}
ContainsRegionWithName(regionName: string): boolean {
RegionManager.NotifyOfUpdatingRegions();
return this.regions.containsKey(regionName);
}
tryGetValue(key: string, valueFunc: (v: IRegion) => void): boolean {
return this.regions.tryGetValue(key, valueFunc);
}
getItem(regionName: string): IRegion {
RegionManager.NotifyOfUpdatingRegions();
return this.regions.getItem(regionName);
}
[Symbol.iterator](): Iterator<IRegion, any, undefined> {
RegionManager.NotifyOfUpdatingRegions();
return this.regions.values[Symbol.iterator]();
}
}