File

projects/wms-framework/src/lib/models/controls/ItemsControlModel.ts

Implements

INotifyCollectionChanged

Index

Properties
Accessors

Constructor

constructor(instance: ItemsCollectionModel)

Creates an instance of ItemsCollectionModel_INotifyCollectionChangedWrapper.

Parameters :
Name Type Optional
instance ItemsCollectionModel No

Properties

Public instance
Type : ItemsCollectionModel
isExplicitInterfaceImplementationWrapper
Default value : true

Used to recognize if the class is an explicit implementation

Accessors

CollectionChanged
getCollectionChanged()

Collection changed subsccription event

import { Control } from '../../basecomponentmodel/Control';
import { Dependency } from '../../basecomponentmodel/Dependency';
import { DependencyObject } from '../../basecomponentmodel/DependencyObject';
import { DependencyProperty } from '../../basecomponentmodel/DependencyProperty';
import { DependencyPropertyChangedEventArgs } from '../../basecomponentmodel/DependencyPropertyChangedEventArgs';
import { FrameworkElement } from '../../basecomponentmodel/FrameworkElement';
import { tryToConvertType } from '../../baseframework/ReflectionSupport';
import {
  CollectionChangeAction,
  CollectionChangeInfo,
  IComparer,
  IList,
  INotifyCollectionChanged,
  SimpleList,
} from '../../baseframework/collections';
import { ReflectionHelper } from '../../baseframework/ReflectionSupport';
import { asExplicitImplementation } from '../../decorators/AsExplicitImplementation';
import { ClassInfo } from '../../decorators/ClassInfo';
import { AngularComponentId } from '../../helpers/AngularComponentId';
import { SubscriptionEvent } from '../../utils/SubscriptionEvent';

/**
 * Model class to represent Image component
 *
 * @export
 * @class ItemsControlModel
 * @extends {Control}
 * @wType System.Windows.Controls.ItemsControl
 * @wType Telerik.Windows.Controls.ItemsControl
 */
export class ItemsControlModel extends Control {
  /**
   * DisplayMemberPathProperty dependency property
   *
   * @static
   * @type {DependencyProperty}
   * @memberof ItemsControlModel
   */
  static DisplayMemberPathProperty: DependencyProperty = new DependencyProperty(
    'DisplayMemberPath',
    null,
    null
  );

  /**
   * ItemsPanelProperty dependency property
   *
   * @static
   * @type {DependencyProperty}
   * @memberof ItemsControlModel
   */
  static ItemsPanelProperty: DependencyProperty = new DependencyProperty(
    'ItemsPanel',
    null,
    null
  );

  /**
   * ItemsSourceProperty dependency property
   *
   * @static
   * @type {DependencyProperty}
   * @memberof ItemsControlModel
   */
  static ItemsSourceProperty: DependencyProperty = new DependencyProperty(
    'ItemsSource',
    null,
    ItemsControlModel.ItemsSourceCallback
  );

  /**
   * ItemTemplateProperty dependency property
   *
   * @static
   * @type {DependencyProperty}
   * @memberof ItemsControlModel
   */
  static ItemTemplateProperty: DependencyProperty = new DependencyProperty(
    'ItemTemplate',
    null,
    null
  );

  /**
   * Force sync items collection
   *
   * @memberof ItemsControlModel
   */
  public forceSyncItemsWithItemsSource() {
    if (this.ItemsSource) {
      this.setItemsProperty(this.ItemsSource);
    }
  }

  private itemContainerGenerator: any = null;

  /**
   * Call this method to fill the items property
   *
   * @private
   * @param {*} collection
   * @memberof ItemsControlModel
   */
  private setItemsProperty(collection: any) {
    this.items.clear();
    if (collection) {
      this.items.setWithCollection(collection);
    }
  }

  /**
   * Call this method to fill the items property
   *
   * @private
   * @param {*} collection
   * @memberof ItemsControlModel
   */
  private setItemsPropertyEvent(
    collection: any,
    info: CollectionChangeInfo = null
  ): void {
    if (info) {
      this.propagateCollectionChanges(collection, info);
    } else {
      this.rebuildCollection(collection);
    }
  }

  public items: ItemsCollectionModel;

  /**
   * Items source
   *
   * @type {Iterable<any>}
   * @memberof ItemsControlModel
   */
  @Dependency(ItemsControlModel.ItemsSourceProperty)
  public ItemsSource: Iterable<any>;

  /**
   * The items panel
   *
   * @type {*}
   * @memberof ItemsControlModel
   */
  @Dependency(ItemsControlModel.ItemsPanelProperty)
  public ItemsPanel: any;

  /**
   * The display member path
   *
   * @type {string}
   * @memberof ItemsControlModel
   */
  @Dependency(ItemsControlModel.DisplayMemberPathProperty)
  public DisplayMemberPath: string;

  /**
   * The item template
   *
   * @type {*}
   * @memberof ItemsControlModel
   */
  @Dependency(ItemsControlModel.ItemTemplateProperty)
  public ItemTemplate: any;

  /**
   * Identifies the angular component associated with the model
   *
   * @type {string}
   * @memberof ItemsControlModel
   */
  public AngularComponentId = AngularComponentId.itemsControl;

  /**
   * Rebuild collection without triggering
   * reset event and restore the items
   *
   * @private
   * @param {*} collection
   * @memberof ItemsControlModel
   */
  private rebuildCollection(collection: any, info: any = null) {
    this.items.clearWithoutReset();
    /* istanbul ignore else */
    if (collection) {
      this.items.setWithCollection(collection);
    }
    /* istanbul ignore else */
    if (info) {
      this.items.fire(info);
    }
  }

  /**
   *  Gets or sets the item container generator
   *
   * @type {*}
   * @memberof ItemsControlModel
   */
  public get ItemContainerGenerator(): any {
    return this.itemContainerGenerator;
  }
  public set ItemContainerGenerator(v: any) {
    this.itemContainerGenerator = v;
  }

  /**
   * Return the amount of children the parent has
   *
   * @return {*}  {number}
   * @memberof ItemsControlModel
   * @wIgnore
   */
  GetChildrenCount(): number {
    return this.items.count;
  }

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

  constructor() {
    super();
    this.items = new ItemsCollectionModel();
    this.SetUpHierarchyListeners(this.items);
  }

  /**
   * Subscription for the CollectionChanged event of the items source (if exists)
   *
   * @memberof ItemsControlModel
   */
  private collectionChangedSubscription: (e: any, args: any) => void;

  /**
   * Reference to callbar called whenever the ItemsSource have changed.
   *
   * @private
   * @type {INotifyCollectionChanged}
   * @memberof ItemsControlModel
   */
  private valueConvertRef: INotifyCollectionChanged;

  /**
   * Reference to subscription when the model is initilized.
   *
   * @private
   * @type {INotifyCollectionChanged}
   * @memberof ItemsControlModel
   */
  private itemsCollectionListenerRef: INotifyCollectionChanged;

  /**
   * Called when the component is being destroyed, clear some handlers attach to this model.
   *
   * @type {void}
   * @memberof ItemsControlModel
   */
  OnDestroy(): void {
    super.OnDestroy();
    this.valueConvertRef?.CollectionChanged.removeHandler(
      this.collectionChangedSubscription,
      this
    );
    this.itemsCollectionListenerRef?.CollectionChanged.removeHandler(
      this.ValidateInfo,
      this
    );
    this.collectionChangedSubscription = null;
  }

  /**
   * On ItemsSource changed handler
   *
   * @param {DependencyPropertyChangedEventArgs} args
   * @memberof ItemsControlModel
   * @wIgnore
   */
  public OnItemsSourceChanged(args: DependencyPropertyChangedEventArgs) {
    // deregister previous handler
    if (args.OldValue && this.collectionChangedSubscription) {
      this.valueConvertRef = tryToConvertType<INotifyCollectionChanged>(
        args.OldValue,
        ReflectionHelper.getInterfaceRuntimeTypeInfo(
          'System.Collections.Specialized.INotifyCollectionChanged'
        )
      );
      this.valueConvertRef?.CollectionChanged.removeHandler(
        this.collectionChangedSubscription,
        this
      );
      this.collectionChangedSubscription = null;
    }

    // adds new handler
    try {
      this.valueConvertRef = tryToConvertType<INotifyCollectionChanged>(
        args.NewValue,
        ReflectionHelper.getInterfaceRuntimeTypeInfo(
          'System.Collections.Specialized.INotifyCollectionChanged'
        )
      );
      if (this.valueConvertRef) {
        this.collectionChangedSubscription =
          this.valueConvertRef.CollectionChanged.addHandler(
            this.setItemsPropertyEvent,
            this
          );
      }
      this.setItemsProperty(args.NewValue);
    } catch (error) {
      this.setItemsProperty(args.NewValue);
    }
  }

  /**
   * ItemsSource dependency property callback
   *
   * @static
   * @param {DependencyObject} sender
   * @param {DependencyPropertyChangedEventArgs} args
   * @memberof ItemsControlModel
   * @wIgnore
   */
  public static ItemsSourceCallback(
    sender: DependencyObject,
    args: DependencyPropertyChangedEventArgs
  ) {
    if (sender instanceof ItemsControlModel) {
      sender.OnItemsSourceChanged(args);
    }
  }

  /**
   *  Sets the listeners for the items property
   *
   * @param {Iterable<any>} value
   * @memberof ItemsControlModel
   */
  private SetUpHierarchyListeners(value: Iterable<any>) {
    this.itemsCollectionListenerRef =
      tryToConvertType<INotifyCollectionChanged>(
        value,
        ReflectionHelper.getInterfaceRuntimeTypeInfo(
          'System.Collections.Specialized.INotifyCollectionChanged'
        )
      );
    this.itemsCollectionListenerRef?.CollectionChanged.addHandler(
      this.ValidateInfo,
      this
    );
  }

  /**
   *  Validates the listener information for the items modifed
   *
   * @param {CollectionChangeInfo} info
   * @memberof ItemsControlModel
   */
  private ValidateInfo(_: any, info: CollectionChangeInfo) {
    if (info.action == CollectionChangeAction.Add) {
      for (const item of info.NewItems) {
        if (item instanceof FrameworkElement) {
          item.Parent = this;
        }
      }
    } else if (
      (info.action == CollectionChangeAction.Reset && info.OldItems) ||
      info.action == CollectionChangeAction.Remove
    ) {
      for (const item of info.OldItems) {
        if (item instanceof FrameworkElement) {
          item.Parent = null;
        }
      }
    }
  }

  /**
   * Applies the changes in bound collection to this { ItemsConttrolModel } internal
   * collection
   *
   * @private
   * @param {CollectionChangeInfo} info the { @link CollectionChangeInfo } object containing the
   * changes in bound collection
   * @memberof ItemsControlModel
   */
  private propagateCollectionChanges(
    collection: any,
    info: CollectionChangeInfo
  ) {
    switch (info.action) {
      case CollectionChangeAction.Add:
      case CollectionChangeAction.Replace:
        let i = info.NewStartingIndex;
        for (const e of info.NewItems) {
          if (info.action === CollectionChangeAction.Add) {
            this.items.insert(i++, e);
          } else {
            this.items.setItem(i++, e);
            this.items.fire(info);
          }
        }
        break;
      case CollectionChangeAction.Remove:
        for (let index = 0; index < info.OldItems.count; index++) {
          this.items.removeAt(info.OldStartingIndex);
        }
        break;
      case CollectionChangeAction.Reset:
        this.rebuildCollection(collection, info);
        break;
    }
  }
}

export abstract class PresentationFrameworkCollectionModel<T>
  extends DependencyObject
  implements IList<T>
{
  private innerCollection = new SimpleList<T>();
  getItem(index: number): T {
    return this.innerCollection.getItem(index);
  }

  setItem(index: number, value: T) {
    this.innerCollection.setItem(index, value);
    this.change.fire(['Count']);
  }

  indexOf(value: T): number {
    return this.innerCollection.indexOf(value);
  }

  insert(index: number, value: T) {
    this.innerCollection.insert(index, value);
    this.change.fire(['Count']);
  }

  removeAt(index: number) {
    this.innerCollection.removeAt(index);
    this.change.fire(['Count']);
  }

  get count(): number {
    return this.innerCollection.count;
  }
  sort();

  sort(comparer: IComparer<T>);

  sort(comparer?) {
    this.innerCollection.sort(comparer);
  }

  add(value: T): void {
    this.internalAdd(value);
    this.change.fire(['Count']);
  }

  /**
   * Add used for internal processing of added elements to collection without trigger change
   *
   * @param {T} value
   * @memberof PresentationFrameworkCollectionModel
   */
  internalAdd(value: T): void {
    if (
      value &&
      value instanceof Object &&
      value.hasOwnProperty &&
      value.hasOwnProperty('Parent')
    ) {
      // eslint-disable-next-line @typescript-eslint/dot-notation
      value['Parent'] = this;
    }
    this.innerCollection.add(value);
  }

  clear(): void {
    const count = this.innerCollection.count;
    this.innerCollection.clear();
    if (count !== this.innerCollection.count) {
      this.change.fire(['Count']);
    }
  }

  contains(value: T): boolean {
    return this.innerCollection.contains(value);
  }

  remove(value: T): boolean {
    const result = this.innerCollection.remove(value);
    this.change.fire(['Count']);
    return result;
  }

  copyTo(target: T[], index: number): void {
    this.innerCollection.copyTo(target, index);
  }
  [Symbol.iterator](): Iterator<T, any, undefined> {
    return this.innerCollection[Symbol.iterator]();
  }
  get internalArray(): T[] {
    return this.innerCollection.internalArray;
  }
}

@ClassInfo({
  classId: 'ItemsCollectionModel',
  implements: ['System.Collections.Specialized.INotifyCollectionChanged'],
})
/**
 * Items Collection Model
 *
 * @export
 * @class ItemsCollectionModel
 * @extends {PresentationFrameworkCollectionModel<any>}
 */
export class ItemsCollectionModel extends PresentationFrameworkCollectionModel<any> {
  /**
   * Function that allows to do sync of the items collection in the model
   *
   * @memberof ItemsCollectionModel
   */
  syncItems: Function;

  /**
   * Collection changed subsccription event
   *
   * @memberof ItemsCollectionModel
   */
  #CollectionChanged: SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  > = new SubscriptionEvent();

  /**
   * Internal access for explicit implemented property
   *
   * @readonly
   * @memberof ItemsCollectionModel
   */
  get CollectionChanged_INotifyCollectionChanged(): SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  > {
    return this.#CollectionChanged;
  }

  /**
   * Insert item at postion
   *
   * @param {number} index
   * @param {*} value
   * @memberof ItemsCollectionModel
   */
  insert(index: number, value: any) {
    if (index <= this.internalArray.length && index >= 0) {
      super.insert(index, value);
      const info = new CollectionChangeInfo(CollectionChangeAction.Add);
      const newItems = new SimpleList();
      newItems.add(value);
      info.NewItems = newItems;
      info.NewStartingIndex = index;
      this.#CollectionChanged.fire([this, info]);
      this.syncItems?.();
    } else {
      throw new Error(
        'Specified argument was out of the range of valid values.'
      );
    }
  }

  /**
   * Removes item in the given index
   *
   * @param {number} index
   * @memberof ItemsCollectionModel
   */
  removeAt(index: number) {
    if (index < this.internalArray.length && index >= 0) {
      const itemToRemove = super.getItem(index);
      super.removeAt(index);
      const info = new CollectionChangeInfo(CollectionChangeAction.Remove);
      const oldItems = new SimpleList();
      oldItems.add(itemToRemove);
      info.OldItems = oldItems;
      info.OldStartingIndex = index;
      this.#CollectionChanged.fire([this, info]);
      this.syncItems?.();
    } else {
      throw new Error(
        'Specified argument was out of the range of valid values.'
      );
    }
  }

  /**
   * Adds item to the collection
   *
   * @param {*} value
   * @memberof ItemsCollectionModel
   */
  add(value: any): void {
    super.add(value);
    const info = new CollectionChangeInfo(CollectionChangeAction.Add);
    const newItems = new SimpleList();
    newItems.add(value);
    info.NewItems = newItems;
    info.NewStartingIndex = this.internalArray.length - 1;
    this.#CollectionChanged.fire([this, info]);
    this.syncItems?.();
  }

  /**
   * Adds a new collections to the items collection.
   * Clear the items collection before adding the new values.
   *
   * @param {*} collection
   * @memberof ItemsCollectionModel
   */
  setWithCollection(collection: any): void {
    for (const item of collection) {
      super.internalAdd(item);
    }
    this.change.fire(['Count']);
    this.syncItems?.();
  }

  /**
   * Clears the collection without firing the CollectionChange event.
   *
   * @memberof ItemsCollectionModel
   */
  clearWithoutReset() {
    super.clear();
    this.syncItems?.();
  }

  /**
   * Clears the collection
   *
   * @memberof ItemsCollectionModel
   */
  clear(): void {
    const currentValues = this.internalArray.map((e) => e);
    super.clear();
    const info = new CollectionChangeInfo(CollectionChangeAction.Reset);
    info.OldItems = new SimpleList(currentValues);
    this.#CollectionChanged.fire([this, info]);
    this.syncItems?.();
  }

  /**
   * Fire event to external handler
   *
   * @param {CollectionChangeInfo} info
   * @memberof ItemsCollectionModel
   */
  fire(info: CollectionChangeInfo): void {
    this.#CollectionChanged.fire([this, info]);
  }

  /**
   * Removes the first element from the collection which match with the value
   *
   * @param {*} value
   * @returns {boolean}
   * @memberof ItemsCollectionModel
   */
  remove(value: any): boolean {
    const removeItemIndex = super.indexOf(value);
    if (removeItemIndex > -1) {
      super.removeAt(removeItemIndex);
      const info = new CollectionChangeInfo(CollectionChangeAction.Remove);
      const oldItems = new SimpleList();
      oldItems.add(value);
      info.OldItems = oldItems;
      info.OldStartingIndex = removeItemIndex;
      this.#CollectionChanged.fire([this, info]);
      this.syncItems?.();
      return true;
    }
    return false;
  }

  @asExplicitImplementation(
    'System.Collections.Specialized.INotifyCollectionChanged'
  )
  asINotifyCollectionChanged(): INotifyCollectionChanged {
    return new ItemsCollectionModel_INotifyCollectionChangedWrapper(this);
  }
}

class ItemsCollectionModel_INotifyCollectionChangedWrapper
  implements INotifyCollectionChanged
{
  /**
   * Collection changed subsccription event
   *
   * @memberof ItemsCollectionModel
   */
  get CollectionChanged(): SubscriptionEvent<
    (e: any, args: CollectionChangeInfo) => void
  > {
    return this.instance.CollectionChanged_INotifyCollectionChanged;
  }

  /**
   * Used to recognize if the class is an explicit implementation
   *
   * @memberof ItemsCollectionModel_INotifyCollectionChangedWrapper
   */
  isExplicitInterfaceImplementationWrapper = true;

  /**
   *Creates an instance of ItemsCollectionModel_INotifyCollectionChangedWrapper.
   *
   * @param {ItemsCollectionModel} instance
   * @memberof ItemsCollectionModel_INotifyCollectionChangedWrapper
   */
  constructor(public instance: ItemsCollectionModel) {}
}

result-matching ""

    No results matching ""