From 3251c29abf89fe951e35257544d0f0f20655c6c8 Mon Sep 17 00:00:00 2001 From: Andrei Fangli Date: Tue, 22 Oct 2024 18:25:36 +0300 Subject: [PATCH] Added documentation for fields, sections and section collections --- docs.ts | 19 ++++-- src/forms/Form.ts | 165 +++++++++++++++++++++++++++++++++------------- 2 files changed, 135 insertions(+), 49 deletions(-) diff --git a/docs.ts b/docs.ts index cdf88a8..bf048ea 100644 --- a/docs.ts +++ b/docs.ts @@ -1609,7 +1609,7 @@ ${getReferences(functionSignature)} } function getFlagSummary(declaration: DeclarationReflection): string { - return [ + let flagsSummary = [ declaration.kind !== ReflectionKind.Constructor && declaration.parent?.kind !== ReflectionKind.Interface && declaration.overwrites && '`override`', declaration.flags.isInherited && '`inherited`', declaration.flags.isStatic && '`static`', @@ -1620,7 +1620,12 @@ ${getReferences(functionSignature)} declaration.flags.isOptional && '`optional`' ] .filter(value => !!value) - .join(' ') + ' '; + .join(' '); + + if (flagsSummary.length > 0) + flagsSummary += ' '; + + return flagsSummary; } function sortCompareDeclarations(left: DeclarationReflection, right: DeclarationReflection): number { @@ -1660,8 +1665,14 @@ ${getReferences(functionSignature)} if (references.length > 0) return '### See also\n\n' + references - .map(reference => '* ' + getBlock(reference.content).replace(/^[ \t]-/gm, '')) - .join('\n'); + .map( + reference => getBlock(reference.content) + .split(/^[ \t]*-[ \t]*/gm) + .filter(reference => reference) + .map(reference => '* ' + reference) + .join('') + ) + .join(''); else return ''; } diff --git a/src/forms/Form.ts b/src/forms/Form.ts index 40f441c..5f594b3 100644 --- a/src/forms/Form.ts +++ b/src/forms/Form.ts @@ -1,5 +1,6 @@ import type { IPropertiesChangedEventHandler } from '../viewModels'; import type { IReadOnlyFormCollection } from './IReadOnlyFormCollection'; +import type { ReadOnlyFormCollection } from './ReadOnlyFormCollection'; import { type IReadOnlyObservableCollection, type IObservableCollection, type ICollectionChangedEventHandler, type ICollectionReorderedEventHandler, ObservableCollection, ReadOnlyObservableCollection } from '../collections'; import { type IValidatable, type IObjectValidator, Validatable, ObjectValidator } from '../validation'; import { FormField } from './FormField'; @@ -56,18 +57,18 @@ import { FormCollection } from './FormCollection'; * the same, but the way fields behave is different. Some fields become required * or have different validation rules while other can become locked and are no * longer editable. - * + * * #### Form Structure and Change Propagation - * + * * Forms have a hierarchical structure comprising of fields and sections which are * forms themselves. This allows for both simple and complex form definitions * through the same model. - * + * * Any form contrains a collection of fields and a collection of sections, however * propagation has an additional level of sections collections. Any form is a parent * node while fields are leaves in the tree structure with propagation generally * going bottom-up. - * + * * * {@linkcode Form} - root or parent node * * {@linkcode FormField} - leaf nodes, any changes to a field are propagated to the parent node, a {@linkcode Form}. * * {@linkcode FormCollection} - a collection of {@linkcode Form} instances, any change to a form collection is propagated to the parent node, a {@linkcode Form}. @@ -75,25 +76,25 @@ import { FormCollection } from './FormCollection'; * Any changes to a {@linkcode Form} is propagated to the {@linkcode FormCollection} * to which it was added. With this, extensions and validation can be added at any level, * from fields, to forms and form collections themselves. - * + * * For simple cases, defining a form is done by extending a {@linkcode Form} and * adding fields using {@linkcode withFields}. - * + * * In case of large forms it can be beneficial to group fields into sections, * which are just different {@linkcode Form} composing a larger one. This can be * done using {@linkcode withSections}. - * + * * For more complex cases where there are collections of forms where items can * be added and removed, and each item has its own set of editable fields, a * {@linkcode FormCollection} must be used to allow for items to be added and * removed. To conrol the interface for mutating the collection consider * extending {@link ReadOnlyFormCollection} instead. - * + * * To add your own form collections to a form use {@linkcode withSectionsCollection} * as this will perform the same operation as {@linkcode withSections} only that * you have control over the underlying form collection. Any changes to the * collection are reflected on the form as well. - * + * * All fields and sections that are added with any of the mentioned methods are * available through the {@linkcode fields} and {@linkcode sections} properties. * @@ -102,34 +103,35 @@ import { FormCollection } from './FormCollection'; * Validation is one of the best examples for change propagation and is offered * out of the box. Whenever a field becomes invalid, the entire form becomes * invalid. - * + * * This applies to form sections as well, whenever a section collection is * invalid, the form (parent node) becomes invalid, and finally, when a form * becomes invalid, the form collection it was added to also becomes invalid. - * + * * With this, the propagation can be seen clearly as validity is determined * completely by the status of each component of the entire form, from all levels. * Any change in one of the nodes goes all the way up to the root node making it * very easy to check if the entire form is valid or not, and later on checking * which sections or fields are invalid. - * + * * Multiple validators can be added and upon any change that is notified by the * target invokes them until the first validator returns an error message. E.g.: * if a field is required and has 2nd validator for checking the length of the * content, the 2nd validator will only be invoked when the 1st one passes, when * the field has an actual value. - * + * * This allows for granular validation messages as well as reusing them across * {@linkcode IValidatable} objects. - * + * * For more complex cases when the validity of one field is dependent on the * value of another field, such as the start date/end date pair, then validation * triggers can be configured so that when either field changes the validators * are invoked. This is similar in a way to how dependencies work on a ReactJS * hook. - * + * * All form components have a `validation` property where configuraiton can be * made, check {@linkcode validation} for more information. + * * ---- * * @guidance Define a Form @@ -341,6 +343,10 @@ import { FormCollection } from './FormCollection'; * .validation * .add(field => field.value <= 0 ? 'GreaterThanZero' : null); * ``` + * + * @see {@linkcode FormField} + * @see {@linkcode ReadOnlyFormCollection} + * @see {@linkcode FormCollection} */ export class Form extends Validatable { private readonly _fields: AggregateObservableCollection>; @@ -474,6 +480,32 @@ export class Form extends Validatable; + * } + * ``` + * + * @see {@linkcode withSections} + * @see {@linkcode withSectionsCollection} */ protected withFields(...fields: readonly FormField[]): IObservableCollection> { const fieldsCollection = new ObservableCollection>(fields); @@ -490,6 +522,45 @@ export class Form extends Validatable[]): FormCollection, TValidationError> { const sectionsCollection = new FormCollection, TValidationError>(sections); @@ -509,11 +580,17 @@ export class Form extends Validatable extends Validatable({ name: 'name', initialValue: '' }) - * ); + * this.name = new FormField({ + * name: 'name', + * initialValue: '' + * }) + * ) + * * this.withSectionsCollection( - * this.items = new ToDoListItemsCollection() + * this.items = new ToDoItemsCollection() * ); * } * * public readonly name: FormField; * - * public readonly items: ToDoListItemsCollection; + * public readonly items: ToDoItemsCollection * } * - * class ToDoListItemsCollection extends ReadOnlyFormCollection { - * public add(): ToDoItemForm { - * const toDoItem = new ToDoItemForm(); - * this.push(toDoItem); + * class ToDoItemsCollection extends ReadOnlyFormCollection { + * public add(): ToDoItem { + * const item = new ToDoItem(); + * this.push(item); * - * return toDoItem; + * return item; * } * - * public remove(toDoItem: ToDoItemForm): void { - * const toDoItemIndex = this.indexOf(toDoItem); - * if (toDoItemIndex >= 0) - * this.splice(toDoItemIndex, 1); + * public remove(item: ToDoItem): void { + * const itemIndex = this.indexOf(item); + * if (itemIndex >= 0) + * this.splice(itemIndex, 1); * } - * * } * - * class ToDoItemForm extends Form { + * class ToDoItem extends Form { * public constructor() { * super(); * * this.withFields( - * this.description = new FormField({ name: 'description', initialValue: '' }) + * this.description = new FormField({ + * name: 'name', + * initialValue: '' + * }) * ); * } * @@ -562,16 +645,8 @@ export class Form extends Validatable, TValidationError>): IReadOnlyFormCollection, TValidationError> { this._sections.aggregatedCollections.push(sectionsCollection);