-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added readonly form section collection back for consistency
- Loading branch information
1 parent
5034f30
commit 5d3ba69
Showing
6 changed files
with
269 additions
and
120 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,178 +1,148 @@ | ||
import type { IPropertiesChangedEventHandler } from '../viewModels'; | ||
import type { Form } from './Form'; | ||
import type { FormSectionSetupCallback } from './IConfigurableFormSectionCollection'; | ||
import type { IFormSectionCollection } from './IFormSectionCollection'; | ||
import type { IReadOnlyFormSectionCollection } from './IReadOnlyFormSectionCollection'; | ||
import { ObjectValidator, type IObjectValidator, type IValidatable } from '../validation'; | ||
import { ObservableCollection } from '../collections'; | ||
import { ReadOnlyFormSectionCollection } from './ReadOnlyFormSectionCollection'; | ||
|
||
/** | ||
* Represents a configurable read-only observable collection of form sections. Callbacks can be configured for setting | ||
* up individual form sections for cases where validation and other aspects are based on the state of an entity or the | ||
* form itself. | ||
* Represents a configurable observable collection of form sections. Callbacks can be configured for setting up individual | ||
* form sections for cases where validation and other aspects are based on the state of an entity or the form itself. | ||
* | ||
* @template TSection the concrete type of the form section. | ||
* @template TValidationError the concrete type for representing validaiton errors (strings, enums, numbers etc.). | ||
*/ | ||
export class FormSectionCollection<TSection extends Form<TValidationError>, TValidationError = string> extends ObservableCollection<TSection> implements IReadOnlyFormSectionCollection<TSection, TValidationError>, IFormSectionCollection<TSection, TValidationError>, IValidatable<TValidationError> { | ||
private _error: TValidationError | null; | ||
private readonly _setupCallbacks: FormSectionSetupCallback<TSection, TValidationError>[]; | ||
|
||
export class FormSectionCollection<TSection extends Form<TValidationError>, TValidationError = string> extends ReadOnlyFormSectionCollection<TSection, TValidationError> implements IFormSectionCollection<TSection, TValidationError> { | ||
/** | ||
* Initializes a new instance of the {@link FormSectionCollection} class. | ||
* @param sections The sections to initialize the collection with. | ||
*/ | ||
public constructor(sections?: Iterable<TSection>) { | ||
super(sections); | ||
|
||
this._setupCallbacks = []; | ||
this.validation = new ObjectValidator<this, TValidationError>({ | ||
target: this, | ||
shouldTargetTriggerValidation: (_, changedProperties) => { | ||
return this.onShouldTriggerValidation(changedProperties); | ||
} | ||
}); | ||
|
||
const sectionChangedEventHandler: IPropertiesChangedEventHandler<Form<TValidationError>> = { | ||
handle: this.onSectionChanged.bind(this) | ||
}; | ||
this.forEach(section => { | ||
section.propertiesChanged.subscribe(sectionChangedEventHandler); | ||
}); | ||
this.collectionChanged.subscribe({ | ||
handle: (_, { addedItems: addedSections, removedItems: removedSections }) => { | ||
removedSections.forEach(removedSection => { | ||
removedSection.propertiesChanged.unsubscribe(sectionChangedEventHandler); | ||
removedSection.reset(); | ||
}); | ||
|
||
addedSections.forEach(addedSection => { | ||
addedSection.propertiesChanged.subscribe(sectionChangedEventHandler); | ||
this._setupCallbacks.forEach(setupCallback => { | ||
setupCallback(addedSection); | ||
}); | ||
}); | ||
|
||
this.notifyPropertiesChanged('isValid', 'isInvalid'); | ||
} | ||
}); | ||
this._setupSections(); | ||
} | ||
|
||
/** | ||
* Gets the validation configuration for the form. Fields have their own individual validation config as well. | ||
* Gets or sets the number of items in the collection. | ||
* @see [Array.length](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/length) | ||
*/ | ||
readonly validation: IObjectValidator<this, TValidationError>; | ||
public get length(): number { | ||
return super.length; | ||
} | ||
|
||
/** | ||
* A flag indicating whether the section collection is valid. | ||
* | ||
* A section collection is valid only when itself is valid and all contained sections are valid. | ||
* Gets or sets the number of items in the collection. | ||
* @see [Array.length](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/length) | ||
*/ | ||
public get isValid(): boolean { | ||
return this._error === null && this.every(section => section.isValid); | ||
public set length(value: number) { | ||
super.length = value; | ||
} | ||
|
||
/** | ||
* A flag indicating whether the section collection is invalid. | ||
* | ||
* A section collection is invalid when itself is invalid or any contained sections is invalid. | ||
* Appends new elements to the end of the collection, and returns the new length of the collection. | ||
* @param items New elements to add at the end of the collection. | ||
* @returns The new length of the collection. | ||
* @see [Array.push](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/push) | ||
*/ | ||
public get isInvalid(): boolean { | ||
return this._error !== null || this.some(section => section.isInvalid); | ||
public push(...items: readonly TSection[]): number { | ||
return super.push.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Gets or sets the error message when the section collection is invalid. | ||
* Removes the last element from the collection and returns it. If the collection is empty, `undefined` is returned. | ||
* @returns The last element in the collection that was removed. | ||
* @see [Array.pop](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/pop) | ||
*/ | ||
public get error(): TValidationError | null { | ||
return this._error; | ||
public pop(): TSection | undefined { | ||
return super.pop.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Gets or sets the error message when the section collection is invalid. | ||
* Inserts new elements at the start of the collection, and returns the new length of the collection. | ||
* @param items Elements to insert at the start of the collection. | ||
* @returns The new length of the collection. | ||
* @see [Array.unshift](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) | ||
*/ | ||
public set error(value: TValidationError | false | null | undefined) { | ||
const normalizedError = (value === false || value === null || value === undefined) ? null : value; | ||
|
||
if (this._error !== normalizedError) { | ||
this._error = normalizedError; | ||
this.notifyPropertiesChanged('error', 'isValid', 'isInvalid'); | ||
} | ||
public unshift(...items: readonly TSection[]): number { | ||
return super.unshift.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Configures the provided `setupCallback` and applies it on all existing form sections within the collection | ||
* and to any form section that is added. | ||
* @param setupCallback The callback performing the setup. | ||
* Removes the first element from the collection and returns it. If the collection is empty, `undefined` is returned. | ||
* @returns The first element in the collection that was removed. | ||
* @see [Array.shift](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/shift) | ||
*/ | ||
public withItemSetup(setupCallback: FormSectionSetupCallback<TSection, TValidationError>): this { | ||
if (typeof setupCallback === 'function') { | ||
this._setupCallbacks.push(setupCallback); | ||
this.forEach(section => { | ||
setupCallback(section); | ||
}); | ||
} | ||
|
||
return this; | ||
public shift(): TSection | undefined { | ||
return super.shift.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Removes the provided `setupCallback` and no longer applies it to form sections that are added, all existing | ||
* form sections are reset and re-configured using the remaining setup callbacks. | ||
* @param setupCallback The callback performing the setup. | ||
* Gets the item at the provided index. | ||
* @param index The index from which to retrieve an item. | ||
* @returns The item at the provided index. | ||
* @see [Array.at](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/at) | ||
*/ | ||
public withoutItemSetup(setupCallback: FormSectionSetupCallback<TSection, TValidationError>): this { | ||
if (typeof setupCallback === 'function') { | ||
const setupCallbackIndex = this._setupCallbacks.indexOf(setupCallback); | ||
if (setupCallbackIndex > 0) { | ||
this._setupCallbacks.splice(setupCallbackIndex, 1); | ||
this.forEach(section => section.reset()); | ||
this._setupSections(); | ||
} | ||
} | ||
public get(index: number): TSection { | ||
return super.get.apply(this, arguments); | ||
} | ||
|
||
return this; | ||
/** | ||
* Sets the provided item at the provided index. | ||
* @param index The index to which to set the item. | ||
* @param item The item to set. | ||
* @returns The length of the collection. | ||
*/ | ||
public set(index: number, item: TSection): number { | ||
return super.set.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Clears all setup callbacks and resets all existing form sections. | ||
* Removes and/or adds elements to the collection and returns the deleted elements. | ||
* @param start The zero-based location in the collection from which to start removing elements. | ||
* @param deleteCount The number of elements to remove. | ||
* @param items The items to insert at the given start location. | ||
* @returns An array containing the elements that were deleted. | ||
* @see [Array.splice](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) | ||
*/ | ||
public clearItemSetups(): void { | ||
this._setupCallbacks.splice(0, Number.POSITIVE_INFINITY); | ||
this.forEach(section => section.reset()); | ||
public splice(start: number, deleteCount?: number, ...items: readonly TSection[]): TSection[] { | ||
return super.splice.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Resets the form, contained fields and sections to their initial configuration. | ||
* | ||
* Validation and other flags are reset, fields retain their current values. | ||
* Reverses the items in the collections and returns the observable collection. | ||
* @param compareCallback Optional, a callback used to determine the sort order between two items. | ||
* @returns The observable collection on which the operation is performed. | ||
* @see [Array.sort](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) | ||
*/ | ||
public reset(): void { | ||
this._setupCallbacks.splice(0, Number.POSITIVE_INFINITY); | ||
this.forEach(section => section.reset()); | ||
this.validation.reset(); | ||
public sort(compareCallback?: (left: Exclude<TSection, undefined>, right: Exclude<TSection, undefined>) => number): this { | ||
return super.sort.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Invoked when a section's properies change, this is a plugin method through which notification propagation can be made with ease. | ||
* Reverses the items in the collections and returns the observable collection.. | ||
* @returns The observable collection on which the operation is performed. | ||
* @see [Array.reverse](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) | ||
*/ | ||
protected onSectionChanged(section: Form<TValidationError>, changedProperties: readonly (keyof Form<TValidationError>)[]) { | ||
if (changedProperties.some(changedProperty => changedProperty === 'isValid' || changedProperty === 'isInvalid')) | ||
this.notifyPropertiesChanged('isValid', 'isInvalid'); | ||
public reverse(): this { | ||
return super.reverse.apply(this, arguments); | ||
} | ||
|
||
/** | ||
* Invoked when the current instance's properties change, this is a plugin method to help reduce validations when changes do not | ||
* have an effect on validation. | ||
* Copies items inside the collection overwriting existing ones. | ||
* @param target The index at which to start copying items, accepts both positive and negative values. | ||
* @param start The index from which to start copying items, accepts both positive and negative values. | ||
* @param end The index until where to copy items, accepts both positive and negative values. | ||
* @see [Array.copyWithin](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin) | ||
*/ | ||
protected onShouldTriggerValidation(changedProperties: readonly (keyof this)[]): boolean { | ||
return changedProperties.some(changedProperty => changedProperty !== 'error' && changedProperty !== 'isValid' && changedProperty !== 'isInvalid'); | ||
public copyWithin(target: number, start: number, end?: number): this { | ||
return super.copyWithin.apply(this, arguments); | ||
} | ||
|
||
private _setupSections(): void { | ||
this.forEach(section => { | ||
this._setupCallbacks.forEach(setupCallback => setupCallback(section)); | ||
}); | ||
/** | ||
* Fills the collection with the provided `item`. | ||
* @param item The item to fill the collection with. | ||
* @param start The index from which to start filling the collection, accepts both positive and negative values. | ||
* @param end The index until which to fill the collection, accepts both positive and negative values. | ||
* @returns The observable collection on which the operation is performed. | ||
* @see [Array.fill](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) | ||
*/ | ||
public fill(item: TSection, start?: number, end?: number): this { | ||
return super.fill.apply(this, arguments); | ||
} | ||
} |
Oops, something went wrong.