UNPKG

@rxap/form-system

Version:

This package provides a set of directives, decorators, mixins, and validators to simplify the creation of dynamic forms in Angular applications. It offers features such as automatic control creation, data source integration, component customization, and h

623 lines (519 loc) 25.3 kB
<!doctype html> <html class="no-js" lang=""> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>angular-form-system</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="../images/favicon.ico"> <link rel="stylesheet" href="../styles/style.css"> <link rel="stylesheet" href="../styles/dark.css"> </head> <body> <script> // Blocking script to avoid flickering dark mode // Dark mode toggle button var useDark = window.matchMedia('(prefers-color-scheme: dark)'); var darkModeState = useDark.matches; var $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input'); var $darkModeToggles = document.querySelectorAll('.dark-mode-switch'); var darkModeStateLocal = localStorage.getItem('compodoc_darkmode-state'); function checkToggle(check) { for (var i = 0; i < $darkModeToggleSwitchers.length; i++) { $darkModeToggleSwitchers[i].checked = check; } } function toggleDarkMode(state) { if (window.localStorage) { localStorage.setItem('compodoc_darkmode-state', state); } checkToggle(state); const hasClass = document.body.classList.contains('dark'); if (state) { for (var i = 0; i < $darkModeToggles.length; i++) { $darkModeToggles[i].classList.add('dark'); } if (!hasClass) { document.body.classList.add('dark'); } } else { for (var i = 0; i < $darkModeToggles.length; i++) { $darkModeToggles[i].classList.remove('dark'); } if (hasClass) { document.body.classList.remove('dark'); } } } useDark.addEventListener('change', function (evt) { toggleDarkMode(evt.matches); }); if (darkModeStateLocal) { darkModeState = darkModeStateLocal === 'true'; } toggleDarkMode(darkModeState); </script> <div class="navbar navbar-default navbar-fixed-top d-md-none p-0"> <div class="d-flex"> <a href="../" class="navbar-brand">angular-form-system</a> <button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button> </div> </div> <div class="xs-menu menu" id="mobile-menu"> <div id="book-search-input" role="search"><input type="text" placeholder="Type to search"></div> <compodoc-menu></compodoc-menu> </div> <div class="container-fluid main"> <div class="row main"> <div class="d-none d-md-block menu"> <compodoc-menu mode="normal"></compodoc-menu> </div> <!-- START CONTENT --> <div class="content injectable"> <div class="content-data"> <ol class="breadcrumb"> <li class="breadcrumb-item">Injectables</li> <li class="breadcrumb-item" >NoopResolveMethod</li> </ol> <ul class="nav nav-tabs" role="tablist"> <li class="nav-item"> <a href="#info" class="nav-link" class="nav-link active" role="tab" id="info-tab" data-bs-toggle="tab" data-link="info">Info</a> </li> <li class="nav-item"> <a href="#readme" class="nav-link" role="tab" id="readme-tab" data-bs-toggle="tab" data-link="readme">README</a> </li> <li class="nav-item"> <a href="#source" class="nav-link" role="tab" id="source-tab" data-bs-toggle="tab" data-link="source">Source</a> </li> </ul> <div class="tab-content"> <div class="tab-pane fade active in" id="info"> <p class="comment"> <h3>File</h3> </p> <p class="comment"> <code>src/lib/directives/autocomplete-options-from-method.directive.ts</code> </p> <section data-compodoc="block-index"> <h3 id="index">Index</h3> <table class="table table-sm table-bordered index-table"> <tbody> <tr> <td class="col-md-4"> <h6><b>Methods</b></h6> </td> </tr> <tr> <td class="col-md-4"> <ul class="index-list"> <li> <a href="#call" >call</a> </li> </ul> </td> </tr> </tbody> </table> </section> <section data-compodoc="block-methods"> <h3 id="methods"> Methods </h3> <table class="table table-sm table-bordered"> <tbody> <tr> <td class="col-md-4"> <a name="call"></a> <span class="name"> <span ><b>call</b></span> <a href="#call"><span class="icon ion-ios-link"></span></a> </span> </td> </tr> <tr> <td class="col-md-4"> <code>call(undefined: literal type)</code> </td> </tr> <tr> <td class="col-md-4"> <div class="io-line">Defined in <a href="" data-line="103" class="link-to-prism">src/lib/directives/autocomplete-options-from-method.directive.ts:103</a></div> </td> </tr> <tr> <td class="col-md-4"> <div class="io-description"> <b>Parameters :</b> <table class="params"> <thead> <tr> <td>Name</td> <td>Type</td> <td>Optional</td> </tr> </thead> <tbody> <tr> <td> <code>literal type</code> </td> <td> No </td> </tr> </tbody> </table> </div> <div class="io-description"> <b>Returns : </b> <code>Promise&lt;ControlOption&gt;</code> </div> <div class="io-description"> </div> </td> </tr> </tbody> </table> </section> </div> <div class="tab-pane fade " id="readme"> <p><h1>Autocomplete Table Select</h1> <h1>With Rxap form System</h1> <b>Example :</b><div><pre class="line-numbers"><code class="language-angular2html"> &lt;mat-form-field&gt; &lt;mat-label&gt;Company&lt;/mat-label&gt; &lt;input type=&quot;text&quot; placeholder=&quot;Enter Company Name&quot; matInput formControlName=&quot;company&quot; [matAutocomplete]=&quot;auto&quot;&gt; &lt;!-- The autocomplete input --&gt; &lt;mat-autocomplete #auto=&quot;matAutocomplete&quot;&gt; &lt;mat-option *rxapAutocompleteOptionsFromMethod=&quot;let option; matAutocomplete: auto&quot; [value]=&quot;option.value&quot;&gt;{{ option.display }} &lt;/mat-option&gt; &lt;/mat-autocomplete&gt; &lt;button mat-icon-button rxapInputClearButton matSuffix&gt; &lt;mat-icon&gt;clear&lt;/mat-icon&gt; &lt;/button&gt; &lt;/mat-form-field&gt;</code></pre></div><b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">&#64;Injectable() &#64;RxapForm(&#39;form&#39;) export class Form { // ensure the table data source is provided - in root or in the form component &#64;UseAutocompleteOptionsMethod(SearchCompanyMethod) // ensure the table data source is provided - in root or in the form component &#64;UseAutocompleteResolveMethod(GetCompanyMethod) &#64;UseFormControl() company!: RxapFromControl&lt;Company&gt;; }</code></pre></div><p>The DataSource <code>CompanyGuiTableDataSource</code> is used to populate the table in the selection window. Ensure that this DataSource is of the type <code>AbstractTableDataSource</code>.</p> <p>The Method <code>SearchCompanyMethod</code> is used by the autocomplete control to search for a list of matching selection options. The Method must accept a string as the first argument and return a list of <code>ControlOption</code> objects.</p> <p>The Method <code>GetCompanyMethod</code> is used to resolve the selected value. The Method must accept a the value of the form control as the first argument and return a <code>ControlOption</code> object.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">export class SearchCompanyMethod implements Method&lt;ControlOptions, { parameters: { search?: string | null } }&gt; { ... } export class GetCompanyMethod implements Method&lt;ControlOption, { parameters: { value: string } }&gt; { ... }</code></pre></div><h1>Method Parameter Adopter</h1> <p>By default the following adopter function is used by the <code>@UseAutocompleteOptionsMethod</code> and <code>@UseAutocompleteResolveMethod</code> decorators.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">(parameters) =&gt; ({parameters})</code></pre></div><p>To override this default adopter function use the second argument of the decorator.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">&#64;UseAutocompleteOptionsMethod(SearchCompanyMethod, { adapter: { parameter: (parameters) =&gt; ({ search: parameters.search }) } })</code></pre></div><blockquote> <p>The adapter function are called in an injection context, so it is possible to use the <code>inject</code> function</p> </blockquote> </p> </div> <div class="tab-pane fade tab-source-code" id="source"> <pre class="line-numbers compodoc-sourcecode"><code class="language-typescript">import { AfterViewInit, Directive, inject, Injectable, Injector, INJECTOR, Input, isDevMode, OnDestroy, ProviderToken, } from &#x27;@angular/core&#x27;; import { MatAutocomplete } from &#x27;@angular/material/autocomplete&#x27;; import { ControlOption, ControlOptions } from &#x27;@rxap/utilities&#x27;; import { distinctUntilChanged, Subscription, tap } from &#x27;rxjs&#x27;; import { isUUID } from &#x27;@rxap/validator&#x27;; import { Method, MethodWithParameters } from &#x27;@rxap/pattern&#x27;; import { Mixin } from &#x27;@rxap/mixin&#x27;; import { NgControl } from &#x27;@angular/forms&#x27;; import { MatFormField } from &#x27;@angular/material/form-field&#x27;; import { isDefined } from &#x27;@rxap/rxjs&#x27;; import { ExtractControlMixin } from &#x27;../mixins/extract-control.mixin&#x27;; import { ExtractFormDefinitionMixin } from &#x27;../mixins/extract-form-definition.mixin&#x27;; import { ExtractIsValueFunctionMixin, UseIsValueFunction, } from &#x27;../mixins/extract-is-value-function.mixin&#x27;; import { ExtractMethodMixin } from &#x27;../mixins/extract-method.mixin&#x27;; import { UseMethodConfig } from &#x27;../mixins/extract-methods.mixin&#x27;; import { UseOptionsMethod } from &#x27;../mixins/extract-options-method.mixin&#x27;; import { ExtractResolveMethodMixin, UseResolveMethod, } from &#x27;../mixins/extract-resolve-method.mixin&#x27;; import { ExtractToDisplayFunctionMixin, UseToDisplayFunction, } from &#x27;../mixins/extract-to-display-function.mixin&#x27;; import { OptionsFromMethodDirective, OptionsFromMethodDirectiveSettings } from &#x27;./options-from-method.directive&#x27;; import { OpenApiRemoteMethodParameter } from &#x27;@rxap/open-api/remote-method&#x27;; import { controlValueChanges$ } from &#x27;@rxap/forms&#x27;; export function UseAutocompleteOptionsMethod( method: ProviderToken&lt;MethodWithParameters&lt;ControlOptions, AutocompleteOptionsFromMethodDirectiveParameters&gt;&gt;, ): any; export function UseAutocompleteOptionsMethod( method: ProviderToken&lt;MethodWithParameters&lt;ControlOptions, OpenApiRemoteMethodParameter&lt;AutocompleteOptionsFromMethodDirectiveParameters&gt;&gt;&gt;, ): any; export function UseAutocompleteOptionsMethod( method: ProviderToken&lt;MethodWithParameters&gt;, config: UseMethodConfig&lt;ControlOptions, AutocompleteOptionsFromMethodDirectiveParameters&gt;, ): any; export function UseAutocompleteOptionsMethod( method: ProviderToken&lt;MethodWithParameters&lt;ControlOptions, AutocompleteOptionsFromMethodDirectiveParameters | OpenApiRemoteMethodParameter&lt;AutocompleteOptionsFromMethodDirectiveParameters&gt;&gt;&gt;, config: UseMethodConfig &#x3D; {}, ) { config.adapter ??&#x3D; {}; config.adapter.parameter ??&#x3D; (parameters) &#x3D;&gt; ({parameters}); return UseOptionsMethod(method as any, config); } export function UseAutocompleteResolveMethod&lt;Value &#x3D; unknown&gt;( method: ProviderToken&lt;MethodWithParameters&lt;ControlOption&lt;Value&gt;, OpenApiRemoteMethodParameter&lt;{ value: string }&gt;&gt;&gt;, config: UseMethodConfig &#x3D; {}, ) { config.adapter ??&#x3D; {}; config.adapter.parameter ??&#x3D; (parameters) &#x3D;&gt; ({parameters}); return UseResolveMethod(method, config); } export function UseAutocompleteIsValueFunction( isValue: (value: any) &#x3D;&gt; boolean, ): any { return UseIsValueFunction(isValue); } export function UseAutocompleteToDisplayFunction( toDisplay: (value: any) &#x3D;&gt; string, ): any { return UseToDisplayFunction(toDisplay); } export interface AutocompleteOptionsFromRemoteMethodTemplateContext { $implicit: ControlOption; } export interface AutocompleteOptionsFromMethodDirectiveSettings extends OptionsFromMethodDirectiveSettings { filteredOptions?: boolean; } export interface AutocompleteOptionsFromMethodDirectiveParameters&lt;Value &#x3D; any&gt; { search?: Value | null; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface AutocompleteOptionsFromMethodDirective&lt;Value &#x3D; any, Parameters extends AutocompleteOptionsFromMethodDirectiveParameters&lt;Value&gt; &#x3D; AutocompleteOptionsFromMethodDirectiveParameters&lt;Value&gt;&gt; extends ExtractResolveMethodMixin, ExtractIsValueFunctionMixin, ExtractToDisplayFunctionMixin, AfterViewInit, OnDestroy { } @Injectable({ providedIn: &#x27;root&#x27; }) export class NoopResolveMethod&lt;Value&gt; implements Method&lt;ControlOption, { value: Value }&gt; { call({ value } : { value: Value }): Promise&lt;ControlOption&gt; { return Promise.resolve({ value, display: value + &#x27;&#x27; }); } } @Mixin(ExtractResolveMethodMixin, ExtractIsValueFunctionMixin, ExtractToDisplayFunctionMixin) @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector selector: &#x27;[rxapAutocompleteOptionsFromMethod]&#x27;, standalone: true, }) export class AutocompleteOptionsFromMethodDirective&lt;Value &#x3D; any, Parameters extends AutocompleteOptionsFromMethodDirectiveParameters&lt;Value&gt; &#x3D; AutocompleteOptionsFromMethodDirectiveParameters&lt;Value&gt;&gt; extends OptionsFromMethodDirective&lt;Value, Parameters&gt; implements AfterViewInit, OnDestroy { static override ngTemplateContextGuard( dir: OptionsFromMethodDirective, ctx: any, ): ctx is AutocompleteOptionsFromRemoteMethodTemplateContext { return true; } @Input(&#x27;rxapAutocompleteOptionsFromMethodParameters&#x27;) public declare parameters?: Parameters; @Input(&#x27;rxapAutocompleteOptionsFromMethodResetOnChange&#x27;) public declare resetOnChange?: Value; @Input(&#x27;rxapAutocompleteOptionsFromMethodMatAutocomplete&#x27;) public matAutocomplete?: MatAutocomplete; // eslint-disable-next-line @angular-eslint/no-input-rename @Input(&#x27;rxapAutocompleteOptionsFromMethodCall&#x27;) public declare method: Method&lt;ControlOptions, Parameters&gt;; // eslint-disable-next-line @angular-eslint/no-input-rename @Input(&#x27;rxapAutocompleteOptionsFromMethodResolve&#x27;) public resolveMethod?: MethodWithParameters&lt;ControlOption, { value: Value } &amp; Parameters&gt;; @Input(&#x27;rxapAutocompleteOptionsFromMethodIsValue&#x27;) public isValue?: (value: any) &#x3D;&gt; boolean; @Input(&#x27;rxapAutocompleteOptionsFromMethodToDisplay&#x27;) public toDisplay?: (value: any) &#x3D;&gt; string; protected override ngControl: NgControl | null &#x3D; null; protected override matFormField: MatFormField | null &#x3D; null; protected override injector: Injector &#x3D; inject(INJECTOR); protected override settings: AutocompleteOptionsFromMethodDirectiveSettings &#x3D; {}; /** * This flag is used to prevent the setValue is called for each refresh * of the options list. This is needed because the setValue method will * trigger a new refresh of the options list. This results in an endless * call stack. This flag is set to true if the setValue method is called * once. */ private isAutocompleteToDisplayTriggered &#x3D; false; private _subscription?: Subscription; public ngOnDestroy() { this._subscription?.unsubscribe(); } public override async ngAfterViewInit() { if (this.matAutocomplete) { this.settings ??&#x3D; {}; this.settings.filteredOptions ??&#x3D; true; } await super.ngAfterViewInit(); this.resolveMethod ??&#x3D; this.extractResolveMethod(); this.isValue ??&#x3D; this.extractIsValueFunction((value: any) &#x3D;&gt; typeof value &#x3D;&#x3D;&#x3D; &#x27;string&#x27; &amp;&amp; isUUID(value)); this.toDisplay ??&#x3D; this.extractToDisplayFunction((value: any): string &#x3D;&gt; { if (!value) { return &#x27;&#x27;; } const option &#x3D; this.findOptionByValue(value); return option?.display ?? (isDevMode() ? &#x27;to display error&#x27; : &#x27;...&#x27;); }); if (this.matAutocomplete) { this.matAutocomplete.displayWith &#x3D; this.toDisplay.bind(this); } if (!this.control) { throw new Error(&#x27;The control is not yet defined&#x27;); } const value$ &#x3D; controlValueChanges$(this.control); this._subscription &#x3D; value$.pipe( isDefined(), // only trigger the load options or resolve value if the value is changed // this is required because in the resolveValue method the control value is set // to trigger the toDisplay function in the mat-autocomplete distinctUntilChanged(), tap(async value &#x3D;&gt; { this.setOptions(await this.loadOptions(this.parameters)); if (this.isValue?.(value)) { this.triggerAutocompleteToDisplay(); } }), ).subscribe(); } protected override loadOptions(parameters: Parameters &#x3D; {} as Parameters): Promise&lt;ControlOptions | null&gt; { if (!this.control) { throw new Error(&#x27;The control is not yet defined&#x27;); } const value &#x3D; this.control?.value; const c_parameters &#x3D; {...parameters}; if (this.isValue?.(value)) { return this.resolveValue(value, c_parameters); } else { c_parameters.search ??&#x3D; value; if (!c_parameters.search) { return Promise.resolve([]); } return super.loadOptions(c_parameters); } } protected override renderTemplate() { super.renderTemplate(); if (this.matAutocomplete &amp;&amp; !this.isAutocompleteToDisplayTriggered) { this.isAutocompleteToDisplayTriggered &#x3D; true; this.triggerAutocompleteToDisplay(); } } protected async resolveValue(value: Value, parameters: Parameters &#x3D; {} as Parameters) { if (!this.resolveMethod) { if (isDevMode()) { console.warn(&#x27;The resolve method is not yet defined&#x27;); } return null; } // only resolve the value if the option is not already loaded if (!this.findOptionByValue(value)) { const option &#x3D; await this.resolveMethod!.call({...parameters, value}); if (option.value !&#x3D;&#x3D; value) { throw new Error(&#x27;The resolved value is not the same as the input value&#x27;); } return [ option ]; } return this.options; } private triggerAutocompleteToDisplay() { // trigger a change detection after the options are rendered // this is needed to trigger the mat-autocomplete options to display function this.ngControl?.control?.setValue(this.ngControl?.control?.value); } private findOptionByValue(value: Value): ControlOption | null { return this.options?.find((option: ControlOption) &#x3D;&gt; option.value &#x3D;&#x3D;&#x3D; value) ?? null; } } </code></pre> </div> </div> </div><div class="search-results"> <div class="has-results"> <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1> <ul class="search-results-list"></ul> </div> <div class="no-results"> <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1> </div> </div> </div> <!-- END CONTENT --> </div> </div> <label class="dark-mode-switch"> <input type="checkbox"> <span class="slider"> <svg class="slider-icon" viewBox="0 0 24 24" fill="none" height="20" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" width="20" xmlns="http://www.w3.org/2000/svg"> <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path> </svg> </span> </label> <script> var COMPODOC_CURRENT_PAGE_DEPTH = 1; var COMPODOC_CURRENT_PAGE_CONTEXT = 'injectable'; var COMPODOC_CURRENT_PAGE_URL = 'NoopResolveMethod.html'; var MAX_SEARCH_RESULTS = 15; </script> <script> $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input'); checkToggle(darkModeState); if ($darkModeToggleSwitchers.length > 0) { for (var i = 0; i < $darkModeToggleSwitchers.length; i++) { $darkModeToggleSwitchers[i].addEventListener('change', function (event) { darkModeState = !darkModeState; toggleDarkMode(darkModeState); }); } } </script> <script src="../js/libs/custom-elements.min.js"></script> <script src="../js/libs/lit-html.js"></script> <script src="../js/menu-wc.js" defer></script> <script nomodule src="../js/menu-wc_es5.js" defer></script> <script src="../js/libs/bootstrap-native.js"></script> <script src="../js/libs/es6-shim.min.js"></script> <script src="../js/libs/EventDispatcher.js"></script> <script src="../js/libs/promise.min.js"></script> <script src="../js/libs/zepto.min.js"></script> <script src="../js/compodoc.js"></script> <script src="../js/tabs.js"></script> <script src="../js/menu.js"></script> <script src="../js/libs/clipboard.min.js"></script> <script src="../js/libs/prism.js"></script> <script src="../js/sourceCode.js"></script> <script src="../js/search/search.js"></script> <script src="../js/search/lunr.min.js"></script> <script src="../js/search/search-lunr.js"></script> <script src="../js/search/search_index.js"></script> <script src="../js/lazy-load-graphs.js"></script> </body> </html>