@wertzui/ngx-restworld-client 17.1.1 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, input, computed, Component, Pipe, forwardRef, model, numberAttribute, booleanAttribute, output, viewChild, contentChild, contentChildren, inject, ViewEncapsulation, ChangeDetectionStrategy, linkedSignal, signal, viewChildren, effect, Self, Directive, Optional, Host, SkipSelf, Inject, NgModule, ContentChild, untracked, resource, provideAppInitializer } from '@angular/core';
2
+ import { Injectable, input, computed, Component, Pipe, forwardRef, model, numberAttribute, booleanAttribute, output, viewChild, contentChild, contentChildren, inject, ViewEncapsulation, ChangeDetectionStrategy, signal, resource, viewChildren, effect, Self, Directive, Optional, Host, SkipSelf, Inject, NgModule, untracked, ContentChild, linkedSignal, provideAppInitializer } from '@angular/core';
3
3
  import * as i2 from 'primeng/avatar';
4
4
  import { AvatarModule } from 'primeng/avatar';
5
5
  import * as i3 from 'primeng/tooltip';
@@ -9,7 +9,7 @@ import { AsyncPipe, CommonModule, NgTemplateOutlet } from '@angular/common';
9
9
  import * as i1$1 from '@wertzui/ngx-hal-client';
10
10
  import { Property, PropertyType, ProblemDetails, ResourceFactory, Link, NumberTemplate, Template, PagedListResource } from '@wertzui/ngx-hal-client';
11
11
  import * as i2$3 from '@angular/forms';
12
- import { FormGroupDirective, ControlContainer, NG_VALUE_ACCESSOR, NgControl, UntypedFormGroup, UntypedFormArray, FormsModule, FormControlName, NG_VALIDATORS, NG_ASYNC_VALIDATORS, DefaultValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, RangeValueAccessor, NumberValueAccessor, NgControlStatus, ReactiveFormsModule, FormGroup, FormControl, Validators, FormArrayName } from '@angular/forms';
12
+ import { FormGroupDirective, ControlContainer, NG_VALUE_ACCESSOR, NgControl, UntypedFormGroup, UntypedFormArray, FormsModule, FormControlName, NG_VALIDATORS, NG_ASYNC_VALIDATORS, DefaultValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, RangeValueAccessor, NumberValueAccessor, NgControlStatus, ReactiveFormsModule, FormArrayName, FormGroup, FormControl, Validators } from '@angular/forms';
13
13
  import * as clrFormat from 'clr-format';
14
14
  import * as i2$1 from 'primeng/api';
15
15
  import { PrimeTemplate, SharedModule, FilterMatchMode, TranslationKeys } from 'primeng/api';
@@ -17,17 +17,16 @@ import { BaseComponent } from 'primeng/basecomponent';
17
17
  import { CheckboxStyle } from 'primeng/checkbox';
18
18
  import { HttpHeaders, HttpEventType, HTTP_INTERCEPTORS } from '@angular/common/http';
19
19
  import { NgHttpCachingHeaders, withNgHttpCachingLocalStorage, NgHttpCachingStrategy, provideNgHttpCaching } from 'ng-http-caching';
20
- import { lastValueFrom, merge, map, mergeMap, tap, finalize } from 'rxjs';
20
+ import { lastValueFrom, merge, map, mergeMap, distinctUntilChanged, switchMap, EMPTY, debounceTime, startWith, tap, finalize } from 'rxjs';
21
21
  import * as i1$3 from 'primeng/button';
22
22
  import { ButtonModule, ButtonDirective } from 'primeng/button';
23
23
  import * as i2$2 from 'primeng/fileupload';
24
24
  import { FileUploadModule, FileUpload } from 'primeng/fileupload';
25
25
  import * as i1$2 from '@angular/platform-browser';
26
- import * as i4$3 from 'ngx-valdemort';
26
+ import * as i5 from 'ngx-valdemort';
27
27
  import { ValidationErrorsComponent, ValidationErrorDirective } from 'ngx-valdemort';
28
28
  import * as i1$4 from 'primeng/message';
29
29
  import { MessageModule } from 'primeng/message';
30
- import * as i3$3 from '@angular/cdk/drag-drop';
31
30
  import { DragDropModule } from '@angular/cdk/drag-drop';
32
31
  import * as i3$2 from 'primeng/multiselect';
33
32
  import { MultiSelect } from 'primeng/multiselect';
@@ -45,21 +44,21 @@ import { ColorPickerModule } from 'primeng/colorpicker';
45
44
  import { InputText } from 'primeng/inputtext';
46
45
  import { SIGNAL, signalSetFn } from '@angular/core/primitives/signals';
47
46
  import { toSignal, toObservable } from '@angular/core/rxjs-interop';
48
- import * as i6 from 'primeng/progressspinner';
49
- import { ProgressSpinnerModule } from 'primeng/progressspinner';
50
- import * as i8 from 'primeng/ripple';
51
- import { RippleModule } from 'primeng/ripple';
52
- import * as i3$4 from '@angular/router';
53
- import { NavigationEnd } from '@angular/router';
54
- import * as i4$1 from 'primeng/splitbutton';
55
- import { SplitButtonModule } from 'primeng/splitbutton';
56
47
  import { createTraverser, TokenType, defaultParser } from '@odata/parser';
57
48
  import { PrimitiveTypeEnum } from '@odata/metadata';
58
49
  import * as i7 from 'primeng/contextmenu';
59
50
  import { ContextMenuModule } from 'primeng/contextmenu';
60
- import * as i5 from 'primeng/table';
51
+ import * as i6 from 'primeng/table';
61
52
  import { TableModule } from 'primeng/table';
53
+ import * as i4$1 from 'primeng/splitbutton';
54
+ import { SplitButtonModule } from 'primeng/splitbutton';
55
+ import * as i3$3 from '@angular/router';
56
+ import { NavigationEnd } from '@angular/router';
62
57
  import * as i4$2 from 'primeng/config';
58
+ import * as i6$1 from 'primeng/progressspinner';
59
+ import { ProgressSpinnerModule } from 'primeng/progressspinner';
60
+ import * as i8 from 'primeng/ripple';
61
+ import { RippleModule } from 'primeng/ripple';
63
62
  import { WebTracerProvider, SimpleSpanProcessor, ConsoleSpanExporter, BatchSpanProcessor, AlwaysOnSampler, ParentBasedSampler, TraceIdRatioBasedSampler, AlwaysOffSampler } from '@opentelemetry/sdk-trace-web';
64
63
  import { ZoneContextManager } from '@opentelemetry/context-zone-peer-dep';
65
64
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
@@ -67,11 +66,11 @@ import { resourceFromAttributes } from '@opentelemetry/resources';
67
66
  import { trace, context, SpanKind, propagation, SpanStatusCode } from '@opentelemetry/api';
68
67
  import { filter } from 'rxjs/operators';
69
68
  import { ATTR_SERVICE_NAME, ATTR_HTTP_REQUEST_HEADER, ATTR_CLIENT_PORT, ATTR_CLIENT_ADDRESS, ATTR_URL_SCHEME, ATTR_URL_QUERY, ATTR_URL_PATH, ATTR_URL_FULL, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_RESPONSE_HEADER } from '@opentelemetry/semantic-conventions';
70
- import * as i7$1 from 'primeng/confirmdialog';
69
+ import * as i8$1 from 'primeng/confirmdialog';
71
70
  import { ConfirmDialogModule } from 'primeng/confirmdialog';
72
- import * as i5$1 from 'primeng/skeleton';
71
+ import * as i6$2 from 'primeng/skeleton';
73
72
  import { SkeletonModule } from 'primeng/skeleton';
74
- import * as i6$1 from 'primeng/tabs';
73
+ import * as i7$1 from 'primeng/tabs';
75
74
  import { TabsModule } from 'primeng/tabs';
76
75
 
77
76
  /**
@@ -807,8 +806,8 @@ class ProblemService {
807
806
  * @param defaultTitle The default title to display if the problem details do not contain a title.
808
807
  */
809
808
  displayToast(problemDetails, defaultDetail = "Error", defaultTitle = "Error") {
810
- const summary = problemDetails.title ?? defaultTitle;
811
- const detail = problemDetails.detail ?? defaultDetail;
809
+ const summary = problemDetails?.title ?? defaultTitle;
810
+ const detail = problemDetails?.detail ?? defaultDetail;
812
811
  this._messageService.add({ severity: "error", summary: summary, detail: detail, sticky: true });
813
812
  }
814
813
  /**
@@ -1559,24 +1558,68 @@ class OptionsManager {
1559
1558
  _clients;
1560
1559
  apiName;
1561
1560
  property;
1562
- selectedValues;
1561
+ _selectedValues;
1563
1562
  _getLabel;
1564
1563
  _getTooltip;
1565
1564
  getLabel = computed(() => this._getLabel() ?? ((itemOrValue) => this.getDefaultLabel(itemOrValue)), ...(ngDevMode ? [{ debugName: "getLabel" }] : []));
1566
1565
  getTooltip = computed(() => this._getTooltip() ?? ((itemOrValue) => this.getDefaultTooltip(itemOrValue)), ...(ngDevMode ? [{ debugName: "getTooltip" }] : []));
1567
- items = linkedSignal(() => this.options()?.inline ?? []);
1568
- loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
1566
+ // public readonly items = linkedSignal(() => this.options()?.inline as TOptionsItem[] ?? []);
1567
+ loading = computed(() => this.items.isLoading(), ...(ngDevMode ? [{ debugName: "loading" }] : []));
1569
1568
  options = computed(() => this.property().options, ...(ngDevMode ? [{ debugName: "options" }] : []));
1570
1569
  promptField = computed(() => this.options()?.promptField ?? "prompt", ...(ngDevMode ? [{ debugName: "promptField" }] : []));
1571
1570
  valueField = computed(() => this.options()?.valueField ?? "value", ...(ngDevMode ? [{ debugName: "valueField" }] : []));
1572
1571
  _client = computed(() => this._clients.getClient(this.apiName()), ...(ngDevMode ? [{ debugName: "_client" }] : []));
1573
- selectedItems = computed(() => this.selectedValues()?.map(value => this.getItemByValue(this.items(), value) ?? []), ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
1574
- constructor(_problemService, _clients, apiName, property, selectedValues, _getLabel, _getTooltip) {
1572
+ selectedItems = computed(() => this.selectedValues()?.map(value => this.getItemByValue(this.items.value(), value) ?? []), ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
1573
+ selectedValues = computed(() => {
1574
+ const values = this._selectedValues();
1575
+ if (values === null || values === undefined)
1576
+ return [];
1577
+ return (Array.isArray(values) ? values : [values]);
1578
+ }, ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
1579
+ filter = signal(undefined, ...(ngDevMode ? [{ debugName: "filter" }] : []));
1580
+ _lastFilter = undefined;
1581
+ items = resource({
1582
+ params: () => ({ filter: this.filter(), options: this.options(), selectedValues: this.selectedValues() }),
1583
+ loader: async ({ params }) => {
1584
+ const options = params.options;
1585
+ let filter = params.filter;
1586
+ const selectedValues = params.selectedValues;
1587
+ // We have inline options, so we don't need to load them from the server.
1588
+ if (Array.isArray(options?.inline) && options.inline.length > 0)
1589
+ return options.inline;
1590
+ // First load: there might be selected items, but the user has not typed in a filter yet.
1591
+ // In this case, we want to load the selected items to be able to show them in the dropdown.
1592
+ if (filter === undefined && selectedValues.length > 0)
1593
+ filter = `${options.valueField} in (${selectedValues})`;
1594
+ // Only make the call if the filter has changed.
1595
+ // This prevents unnecessary calls when the selected values change but the filter is the same.
1596
+ if (filter === this._lastFilter) {
1597
+ const currentItems = this.items.value();
1598
+ if (currentItems === undefined)
1599
+ return [];
1600
+ return currentItems;
1601
+ }
1602
+ this._lastFilter = filter;
1603
+ const templatedUri = options.link?.href;
1604
+ if (!templatedUri) {
1605
+ this._problemService.displayToast(undefined, `The property ${this.property().name} does not have options with a link href.`);
1606
+ return [];
1607
+ }
1608
+ const headersWithCaching = new HttpHeaders({ [NgHttpCachingHeaders.ALLOW_CACHE]: "1", [NgHttpCachingHeaders.LIFETIME]: "3000" });
1609
+ const response = await this._client().getListByUri(templatedUri, { $filter: filter, $top: 10 }, headersWithCaching);
1610
+ this._problemService.checkResponseDisplayErrorsAndThrow(response, undefined, `An error occurred while getting the selected items for the property ${this.property().name}.`);
1611
+ const oldItems = this.items.value();
1612
+ const newItems = response.body._embedded.items;
1613
+ const combinedItems = this.combineCurrentItemsWithSelected(oldItems, selectedValues, newItems);
1614
+ return combinedItems;
1615
+ }
1616
+ });
1617
+ constructor(_problemService, _clients, apiName, property, _selectedValues, _getLabel, _getTooltip) {
1575
1618
  this._problemService = _problemService;
1576
1619
  this._clients = _clients;
1577
1620
  this.apiName = apiName;
1578
1621
  this.property = property;
1579
- this.selectedValues = selectedValues;
1622
+ this._selectedValues = _selectedValues;
1580
1623
  this._getLabel = _getLabel;
1581
1624
  this._getTooltip = _getTooltip;
1582
1625
  }
@@ -1589,7 +1632,7 @@ class OptionsManager {
1589
1632
  return shortened;
1590
1633
  }
1591
1634
  getItemByValue(items, value) {
1592
- const foundItem = items.find(item => this.getValue(item) === value);
1635
+ const foundItem = items?.find(item => this.getValue(item) === value);
1593
1636
  if (foundItem)
1594
1637
  return foundItem;
1595
1638
  if (value === null || value === undefined)
@@ -1606,7 +1649,7 @@ class OptionsManager {
1606
1649
  let item = itemOrValue;
1607
1650
  if (itemOrValue === undefined || itemOrValue === null || !itemOrValue.hasOwnProperty(promptField)) {
1608
1651
  const value = itemOrValue;
1609
- item = this.getItemByValue(this.items(), value);
1652
+ item = this.getItemByValue(this.items.value(), value);
1610
1653
  }
1611
1654
  if (item === undefined || item === null || !item.hasOwnProperty(promptField))
1612
1655
  return "";
@@ -1626,53 +1669,21 @@ class OptionsManager {
1626
1669
  const item = itemOrValue;
1627
1670
  return item[valueField];
1628
1671
  }
1629
- /**
1630
- * Initializes the options manager.
1631
- * This will load the initial options for the property.
1632
- * Call it once in the ngOnInit method of your component.
1633
- */
1634
- async initialize() {
1635
- const options = this.options();
1636
- if (!options.link?.href)
1637
- return;
1638
- const selectedValues = this.selectedValues() ?? options.selectedValues;
1639
- const filter = selectedValues === null || selectedValues === undefined ?
1640
- undefined :
1641
- `${options.valueField} in (${selectedValues})`;
1642
- await this.updateItemsFromFilter(filter);
1643
- }
1644
1672
  /**
1645
1673
  * Updates the items based on the given filter.
1646
1674
  * @param filter The filter to use when getting the items.
1647
1675
  */
1648
1676
  async updateItemsFromFilter(filter) {
1649
- this.loading.set(true);
1650
- try {
1651
- const options = this.options();
1652
- const templatedUri = options.link?.href;
1653
- if (!templatedUri)
1654
- this._problemService.displayToastAndThrow(undefined, `The property ${this.property().name} does not have options with a link href.`);
1655
- const headersWithCaching = new HttpHeaders({ [NgHttpCachingHeaders.ALLOW_CACHE]: "1", [NgHttpCachingHeaders.LIFETIME]: "3000" });
1656
- const response = await this._client().getListByUri(templatedUri, { $filter: filter, $top: 10 }, headersWithCaching);
1657
- this._problemService.checkResponseDisplayErrorsAndThrow(response, undefined, `An error occurred while getting the selected items for the property ${this.property().name}.`);
1658
- const items = response.body._embedded.items;
1659
- const newItems = this.combineCurrentItemsWithSelected(items);
1660
- this.items.set(newItems);
1661
- }
1662
- finally {
1663
- this.loading.set(false);
1664
- }
1677
+ this.filter.set(filter);
1665
1678
  }
1666
- combineCurrentItemsWithSelected(items) {
1667
- const oldInline = this.items();
1668
- if (!oldInline)
1669
- return items;
1670
- const selectedValues = this.selectedValues();
1671
- const selectedValuesAsArray = (Array.isArray(selectedValues) ? selectedValues : [selectedValues]);
1672
- const itemsToKeep = oldInline.filter(i => selectedValuesAsArray.includes(this.getValue(i)));
1673
- const newValues = items.map(i => this.getValue(i));
1674
- const newItems = items.concat(itemsToKeep.filter(i => !newValues.includes(this.getValue(i))));
1675
- return newItems;
1679
+ combineCurrentItemsWithSelected(oldItems, selectedValues, newItems) {
1680
+ if (!oldItems)
1681
+ return newItems;
1682
+ // const selectedValuesAsArray = (Array.isArray(selectedValues) ? selectedValues : [selectedValues]) as ExtractGenericOptionsSelectedValuesType<TProperty>[];
1683
+ const itemsToKeep = oldItems.filter(i => selectedValues.includes(this.getValue(i)));
1684
+ const newValues = newItems.map(i => this.getValue(i));
1685
+ const combinedItems = newItems.concat(itemsToKeep.filter(i => !newValues.includes(this.getValue(i))));
1686
+ return combinedItems;
1676
1687
  }
1677
1688
  getDefaultLabel(itemOrValue) {
1678
1689
  if (itemOrValue === undefined || itemOrValue === null)
@@ -1864,9 +1875,6 @@ class RestWorldDisplayDropdownComponent {
1864
1875
  constructor(optionsService) {
1865
1876
  this.optionsManager = optionsService.getManager(this.apiName, this.property, this.selectedValues, this.getLabel, this.getTooltip);
1866
1877
  }
1867
- async ngOnInit() {
1868
- await this.optionsManager.initialize();
1869
- }
1870
1878
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldDisplayDropdownComponent, deps: [{ token: OptionsService }], target: i0.ɵɵFactoryTarget.Component });
1871
1879
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldDisplayDropdownComponent, isStandalone: true, selector: "rw-display-dropdown", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, getLabel: { classPropertyName: "getLabel", publicName: "getLabel", isSignal: true, isRequired: false, transformFunction: null }, getTooltip: { classPropertyName: "getTooltip", publicName: "getTooltip", isSignal: true, isRequired: false, transformFunction: null }, property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, selectedValues: { classPropertyName: "selectedValues", publicName: "selectedValues", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@for (item of optionsManager.selectedItems(); track item; let last = $last) {\r\n <span [pTooltip]=\"optionsManager.getTooltip()($any(item))\">{{optionsManager.getLabel()($any(item))}}</span>{{last ? \"\" : \", \"}}\r\n}\r\n", styles: [""], dependencies: [{ kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }] });
1872
1880
  }
@@ -2721,1657 +2729,1715 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
2721
2729
  }] });
2722
2730
 
2723
2731
  /**
2724
- * A base class for all input components..
2732
+ * This visitor turns a parsed OData $filter query string represented as a {@link Token} into a map of property names to filter metadata.
2733
+ * The filter metadata is used by the PrimeNG table component to filter the rows of
2734
+ * a table based on the values of the columns.
2725
2735
  */
2726
- class RestWorldInputBaseComponent {
2727
- /**
2728
- * The property to display.
2729
- * @required
2730
- */
2731
- property = model.required(...(ngDevMode ? [{ debugName: "property" }] : []));
2732
- /**
2733
- * Set this to true if the input should use template driven forms instead of the default reactive forms.
2736
+ class OdataVisitor {
2737
+ _filters = new Map();
2738
+ operator;
2739
+ propertyName;
2740
+ value;
2741
+ static correctFilterMatchMode(matchMode, literalValue) {
2742
+ if (literalValue instanceof Date) {
2743
+ switch (matchMode) {
2744
+ case FilterMatchMode.EQUALS:
2745
+ return FilterMatchMode.DATE_IS;
2746
+ case FilterMatchMode.NOT_EQUALS:
2747
+ return FilterMatchMode.DATE_IS_NOT;
2748
+ case FilterMatchMode.LESS_THAN:
2749
+ return FilterMatchMode.DATE_BEFORE;
2750
+ case FilterMatchMode.LESS_THAN_OR_EQUAL_TO:
2751
+ return FilterMatchMode.DATE_BEFORE;
2752
+ case FilterMatchMode.GREATER_THAN:
2753
+ return FilterMatchMode.DATE_AFTER;
2754
+ case FilterMatchMode.GREATER_THAN_OR_EQUAL_TO:
2755
+ return FilterMatchMode.DATE_AFTER;
2756
+ }
2757
+ }
2758
+ return matchMode;
2759
+ }
2760
+ static parseLiteralValue(node) {
2761
+ const type = node.value;
2762
+ switch (type) {
2763
+ case PrimitiveTypeEnum.Binary:
2764
+ case PrimitiveTypeEnum.Boolean:
2765
+ return node.raw === "true";
2766
+ case PrimitiveTypeEnum.Byte:
2767
+ case PrimitiveTypeEnum.Int16:
2768
+ case PrimitiveTypeEnum.Int32:
2769
+ case PrimitiveTypeEnum.Int64:
2770
+ case PrimitiveTypeEnum.SByte:
2771
+ return Number.parseInt(node.raw);
2772
+ case PrimitiveTypeEnum.Date:
2773
+ case PrimitiveTypeEnum.DateTime:
2774
+ case PrimitiveTypeEnum.DateTimeOffset:
2775
+ case PrimitiveTypeEnum.Duration:
2776
+ case PrimitiveTypeEnum.TimeOfDay:
2777
+ return new Date(node.raw);
2778
+ case PrimitiveTypeEnum.Decimal:
2779
+ case PrimitiveTypeEnum.Double:
2780
+ case PrimitiveTypeEnum.Single:
2781
+ return Number.parseFloat(node.raw);
2782
+ default:
2783
+ switch (node.raw) {
2784
+ case "null":
2785
+ return null;
2786
+ default:
2787
+ return node.raw.startsWith("'") ? node.raw.substring(1, node.raw.length - 1) : node.raw;
2788
+ }
2789
+ }
2790
+ }
2791
+ static parseMethodCallExpression(node) {
2792
+ switch (node.value.method) {
2793
+ case 'startswith':
2794
+ return FilterMatchMode.STARTS_WITH;
2795
+ case 'contains':
2796
+ return FilterMatchMode.CONTAINS;
2797
+ case 'not contains':
2798
+ return FilterMatchMode.NOT_CONTAINS;
2799
+ case 'endswith':
2800
+ return FilterMatchMode.ENDS_WITH;
2801
+ default:
2802
+ throw Error(`Unknown method call ${node.value.method}`);
2803
+ }
2804
+ }
2805
+ /***
2806
+ * Parses an OData $filter query string and returns a map of property names to filter metadata.
2807
+ * @param node The root node of the OData $filter query string.
2808
+ * @param logToConsole If true, logs the type and raw value of each token to the console.
2809
+ * @returns A map of property names to filter metadata.
2734
2810
  */
2735
- useTemplateDrivenForms = input(false, ...(ngDevMode ? [{ debugName: "useTemplateDrivenForms" }] : []));
2736
- model = model(...(ngDevMode ? [undefined, { debugName: "model" }] : []));
2737
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2738
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldInputBaseComponent, isStandalone: true, inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, useTemplateDrivenForms: { classPropertyName: "useTemplateDrivenForms", publicName: "useTemplateDrivenForms", isSignal: true, isRequired: false, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { property: "propertyChange", model: "modelChange" }, ngImport: i0 });
2811
+ parse(node, logToConsole) {
2812
+ if (logToConsole) {
2813
+ const visitLogAll = createTraverser(Object.fromEntries(Object.entries(TokenType)
2814
+ .map(([key, value]) => [key, (node) => console.log(`${node.type}: ${node.raw}`)])), true);
2815
+ visitLogAll(node);
2816
+ }
2817
+ const visit = createTraverser({
2818
+ EqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.EQUALS),
2819
+ NotEqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.NOT_EQUALS),
2820
+ GreaterThanExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.GREATER_THAN),
2821
+ GreaterOrEqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.GREATER_THAN_OR_EQUAL_TO),
2822
+ LesserThanExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.LESS_THAN),
2823
+ LesserOrEqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.LESS_THAN_OR_EQUAL_TO),
2824
+ HasExpression: (node) => this.CreateFilter(this.propertyName, [this.value], FilterMatchMode.EQUALS),
2825
+ ODataIdentifier: (node) => this.propertyName = node.value.name,
2826
+ Literal: (node) => this.value = OdataVisitor.parseLiteralValue(node),
2827
+ AndExpression: (node) => this.operator = "and",
2828
+ OrExpression: (node) => this.operator = "or",
2829
+ BoolParenExpression: (node) => this.SetOperatorForFilters(),
2830
+ MethodCallExpression: (node) => this.visitMethodCallExpression(node, visit),
2831
+ }, true);
2832
+ visit(node);
2833
+ return this._filters;
2834
+ }
2835
+ CreateFilter(propertyName, value, matchMode) {
2836
+ if (propertyName === undefined)
2837
+ throw Error(`Cannot add a filter for the value ${value} and the match mode ${matchMode} without a property name`);
2838
+ matchMode = OdataVisitor.correctFilterMatchMode(matchMode, value);
2839
+ const filter = this._filters.get(propertyName);
2840
+ if (filter === undefined) {
2841
+ this._filters.set(propertyName, [{ value, operator: "and", matchMode: matchMode }]);
2842
+ }
2843
+ else if (filter.length === 1 && Array.isArray(filter[0].value) && Array.isArray(value)) {
2844
+ filter[0].value.push(...value); // Flags enum
2845
+ }
2846
+ else {
2847
+ filter.push({ value, operator: "and", matchMode: matchMode });
2848
+ }
2849
+ }
2850
+ SetOperatorForFilters() {
2851
+ // In this case we are in the outermost expression which is just parantheses around the complete $filter value
2852
+ if (this.propertyName === undefined)
2853
+ return;
2854
+ // If we have not found an operator, or it is "and", we can just stay with the default created with the filter.
2855
+ if (this.operator === undefined || this.operator === "and")
2856
+ return;
2857
+ const filter = this._filters.get(this.propertyName);
2858
+ if (filter === undefined)
2859
+ return;
2860
+ // The operator is "or", so we need to set the operator for all filters to "or"
2861
+ for (const f of filter)
2862
+ f.operator = this.operator;
2863
+ }
2864
+ // The visitor does not correctly implement the MethodCallExpressionToken, so we need to do this manually
2865
+ visitMethodCallExpression(node, visit) {
2866
+ for (const parameter of node.value.parameters)
2867
+ visit(parameter);
2868
+ const matchMode = OdataVisitor.parseMethodCallExpression(node);
2869
+ this.CreateFilter(this.propertyName, this.value, matchMode);
2870
+ }
2739
2871
  }
2740
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputBaseComponent, decorators: [{
2741
- type: Directive
2742
- }] });
2872
+
2743
2873
  /**
2744
- * A base class for all input components which also feature lazy loading, like dropdowns.
2874
+ * This class is used to parse an OData $filter string into a Record of filter constraints.
2745
2875
  */
2746
- class RestWorldInputLazyLoadBaseComponent extends RestWorldInputBaseComponent {
2747
- /**
2748
- * The name of the API to use for the property.
2749
- * @required
2750
- * @remarks This is the name of the API as defined in the `RestWorldClientCollection`.
2751
- */
2752
- apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
2753
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputLazyLoadBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
2754
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldInputLazyLoadBaseComponent, isStandalone: true, inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
2876
+ class ODataFilterParser {
2877
+ static parseFilter(filter, properties) {
2878
+ if (!filter)
2879
+ return {};
2880
+ // The parser has a bug where it does not correctly parse cast expressions, so we need to replace them with the first argument
2881
+ // See https://github.com/Soontao/odata-v4-parser/issues/283
2882
+ filter = ODataFilterParser.replaceCastExpressions(filter);
2883
+ const ast = defaultParser.filter(filter);
2884
+ const visitor = new OdataVisitor();
2885
+ const filters = visitor.parse(ast);
2886
+ /// OData needs enum values in pascal case, but JSON and therefor HAL-Forms needs them in camel case
2887
+ ODataFilterParser.MakeEnumValuesCamelCase(filters, properties);
2888
+ return Object.fromEntries(filters);
2889
+ }
2890
+ static MakeEnumValuesCamelCase(filters, properties) {
2891
+ for (const [key, value] of filters) {
2892
+ for (const filter of value) {
2893
+ const options = properties[key]?.options;
2894
+ if (options && !options.link && typeof filter.value === "string") {
2895
+ filter.value = filter.value.charAt(0).toLowerCase() + filter.value.slice(1);
2896
+ }
2897
+ }
2898
+ }
2899
+ }
2900
+ static replaceCastExpressions(input) {
2901
+ // Regular expression to match cast(x, y) and extract x
2902
+ const regex = /cast\(([^,]+),\s*[^)]+\)/g;
2903
+ // Replace matches with the first captured group (x)
2904
+ const result = input.replace(regex, (match, x) => x.trim());
2905
+ return result;
2906
+ }
2755
2907
  }
2756
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputLazyLoadBaseComponent, decorators: [{
2757
- type: Directive
2758
- }] });
2908
+
2759
2909
  /**
2760
- * A form element with a label that is automatically created from a property in a form template.
2761
- * This may also be a complex object or a collection in which case multiple and nested input elements may be rendered.
2762
- * If you want a form element without a label, use {@link RestWorldFormInput} `<rw-input>`.
2763
- * @example
2764
- * <rw-form-element [property]="property" [apiName]="apiName"></rw-form-element>
2910
+ * A static class that provides utility methods for creating OData filters, order by clauses, and parameters from {@link TableLazyLoadEvent} objects.
2765
2911
  */
2766
- class RestWorldFormElementComponent extends RestWorldInputLazyLoadBaseComponent {
2767
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormElementComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2768
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.1", type: RestWorldFormElementComponent, isStandalone: true, selector: "rw-form-element", usesInheritance: true, ngImport: i0, template: "<div class=\"grid field\">\r\n <rw-label [property]=\"property()\" class=\"col-12 md:col-2 flex align-items-center\"></rw-label>\r\n <rw-input [apiName]=\"apiName()\" [property]=\"property()\" class=\"col-12 md:col-10\"></rw-input>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputComponent), selector: "rw-input" }, { kind: "component", type: i0.forwardRef(() => RestWorldLabelComponent), selector: "rw-label", inputs: ["property"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
2769
- }
2770
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormElementComponent, decorators: [{
2771
- type: Component,
2772
- args: [{ selector: 'rw-form-element', viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputComponent), RestWorldLabelComponent], template: "<div class=\"grid field\">\r\n <rw-label [property]=\"property()\" class=\"col-12 md:col-2 flex align-items-center\"></rw-label>\r\n <rw-input [apiName]=\"apiName()\" [property]=\"property()\" class=\"col-12 md:col-10\"></rw-input>\r\n</div>\r\n" }]
2773
- }] });
2774
- /**
2775
- * A collection that is automatically created from the given property.
2776
- * The collection supports drag & drop to re order the elements and can also be nested.
2777
- * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
2778
- * instead of using this component directly.
2779
- * @example
2780
- * <rw-input-collection [property]="property" [apiName]="apiName"></rw-input-collection>
2781
- */
2782
- class RestWorldInputCollectionComponent extends RestWorldInputLazyLoadBaseComponent {
2783
- _formService;
2784
- _controlContainer;
2785
- defaultTemplate = computed(() => this.property()._templates.default, ...(ngDevMode ? [{ debugName: "defaultTemplate" }] : []));
2786
- innerFormArray = computed(() => this._controlContainer.control?.controls[this.property().name], ...(ngDevMode ? [{ debugName: "innerFormArray" }] : []));
2787
- templates = computed(() => this.getCollectionEntryTemplates(this.property()), ...(ngDevMode ? [{ debugName: "templates" }] : []));
2788
- inputCollectionRef;
2789
- constructor(_formService, _controlContainer) {
2790
- super();
2791
- this._formService = _formService;
2792
- this._controlContainer = _controlContainer;
2793
- }
2794
- getCollectionEntryTemplates(property) {
2795
- if (!property)
2796
- return [];
2797
- return Object.entries(property._templates)
2798
- .filter(([key, value]) => Number.isInteger(Number.parseInt(key)) && Number.isInteger(Number.parseInt(value?.title ?? "")))
2799
- .map(([, value]) => new NumberTemplate(value));
2800
- }
2801
- addNewItemToCollection() {
2802
- const templates = this.templates();
2803
- const defaultTemplate = this.defaultTemplate();
2804
- const maxIndex = Math.max(...Object.keys(templates)
2805
- .map(key => Number.parseInt(key))
2806
- .filter(key => Number.isSafeInteger(key)));
2807
- const nextIndex = maxIndex < 0 ? 0 : maxIndex + 1;
2808
- const copiedTemplateDto = JSON.parse(JSON.stringify(defaultTemplate));
2809
- copiedTemplateDto.title = nextIndex.toString();
2810
- const copiedTemplate = new Template(copiedTemplateDto);
2811
- this.property.update((property) => { property._templates[nextIndex] = copiedTemplate; return { ...property }; });
2812
- this.innerFormArray().push(this._formService.createFormGroupFromTemplate(this.defaultTemplate()));
2813
- }
2814
- collectionItemDropped($event) {
2815
- const previousIndex = $event.previousIndex;
2816
- const currentIndex = $event.currentIndex;
2817
- const movementDirection = currentIndex > previousIndex ? 1 : -1;
2818
- // Move in FormArray
2819
- // We do not need to move the item in the _templates object
2820
- const innerFormArray = this.innerFormArray();
2821
- const movedControl = innerFormArray.at(previousIndex);
2822
- for (let i = previousIndex; i * movementDirection < currentIndex * movementDirection; i = i + movementDirection) {
2823
- innerFormArray.setControl(i, innerFormArray.at(i + movementDirection));
2912
+ class ODataService {
2913
+ /**
2914
+ * Creates an OData filter string for a given property and filter metadata.
2915
+ * @param property - The property to filter on.
2916
+ * @param filter - The filter metadata to use.
2917
+ * @returns The OData filter string, or undefined if the filter value is falsy.
2918
+ */
2919
+ static createFilterForProperty(property, filter) {
2920
+ if (filter.matchMode == TranslationKeys.NO_FILTER)
2921
+ return undefined;
2922
+ // Enums are handled differently
2923
+ if (property.options && !property.options.link)
2924
+ return ODataService.createFilterForEnum(property, filter);
2925
+ const oDataOperator = ODataService.createODataOperator(filter.matchMode);
2926
+ const comparisonValue = ODataService.createComparisonValue(property, filter.value);
2927
+ switch (oDataOperator) {
2928
+ case 'contains':
2929
+ case 'not contains':
2930
+ case 'startswith':
2931
+ case 'endswith':
2932
+ return `${oDataOperator}(${property.name}, ${comparisonValue})`;
2933
+ default:
2934
+ return `${property.name} ${oDataOperator} ${comparisonValue}`;
2824
2935
  }
2825
- innerFormArray.setControl(currentIndex, movedControl);
2826
- }
2827
- deleteItemFromCollection(template) {
2828
- const title = template.title;
2829
- if (title === undefined)
2830
- throw new Error("Cannot delete a template without a title.");
2831
- this.property.update((property) => { delete property._templates[title]; return { ...property }; });
2832
- this.innerFormArray().removeAt(title);
2833
2936
  }
2834
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputCollectionComponent, deps: [{ token: i1$1.FormService }, { token: i2$3.ControlContainer }], target: i0.ɵɵFactoryTarget.Component });
2835
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputCollectionComponent, isStandalone: true, selector: "rw-input-collection", queries: [{ propertyName: "inputCollectionRef", first: true, predicate: ["inputCollection"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"flex align-items-center\">\n <div class=\"brace\">\n </div>\n <div class=\"w-full\" cdkDropList (cdkDropListDropped)=\"collectionItemDropped($event)\">\n @for (template of templates(); track template) {\n <div class=\"flex align-items-center\" cdkDrag>\n <i class=\"fas fa-grip-lines\" cdkDragHandle></i>\n <div class=\"brace\">\n </div>\n <div class=\"w-full flex justify-content-end\">\n <rw-input-template [formGroup]=\"innerFormArray().controls[template.title!]\" [template]=\"template\" [apiName]=\"apiName()\" class=\"w-full\"></rw-input-template>\n <button pButton pRipple type=\"button\" icon=\"fas fa-trash\" class=\"p-button-outlined p-button-danger ml-2 mb-3\" (click)=\"deleteItemFromCollection(template)\"></button>\n </div>\n </div>\n }\n <div class=\"flex justify-content-end w-full\">\n <button pButton pRipple type=\"button\" icon=\"fas fa-plus\" class=\"p-button-outlined p-button-info\" (click)=\"addNewItemToCollection()\"></button>\n </div>\n </div>\n</div>\n", styles: [".cdk-drag-handle{cursor:move}.cdk-drag-preview{background-color:#ffffffd0;border:2px dashed rgb(206,212,218);cursor:move}.cdk-drag-placeholder{border:2px dashed rgb(206,212,218);margin:-2px}.brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputTemplateComponent), selector: "rw-input-template", inputs: ["apiName", "template"] }, { kind: "ngmodule", type: i0.forwardRef(() => DragDropModule) }, { kind: "directive", type: i0.forwardRef(() => i3$3.CdkDropList), selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i0.forwardRef(() => i3$3.CdkDrag), selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i0.forwardRef(() => i3$3.CdkDragHandle), selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i0.forwardRef(() => ButtonDirective), selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "directive", type: i0.forwardRef(() => i2$3.NgControlStatusGroup), selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i0.forwardRef(() => i2$3.FormGroupDirective), selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
2836
- }
2837
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputCollectionComponent, decorators: [{
2838
- type: Component,
2839
- args: [{ selector: 'rw-input-collection', viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputTemplateComponent), DragDropModule, ButtonDirective, ReactiveFormsModule], template: "<div class=\"flex align-items-center\">\n <div class=\"brace\">\n </div>\n <div class=\"w-full\" cdkDropList (cdkDropListDropped)=\"collectionItemDropped($event)\">\n @for (template of templates(); track template) {\n <div class=\"flex align-items-center\" cdkDrag>\n <i class=\"fas fa-grip-lines\" cdkDragHandle></i>\n <div class=\"brace\">\n </div>\n <div class=\"w-full flex justify-content-end\">\n <rw-input-template [formGroup]=\"innerFormArray().controls[template.title!]\" [template]=\"template\" [apiName]=\"apiName()\" class=\"w-full\"></rw-input-template>\n <button pButton pRipple type=\"button\" icon=\"fas fa-trash\" class=\"p-button-outlined p-button-danger ml-2 mb-3\" (click)=\"deleteItemFromCollection(template)\"></button>\n </div>\n </div>\n }\n <div class=\"flex justify-content-end w-full\">\n <button pButton pRipple type=\"button\" icon=\"fas fa-plus\" class=\"p-button-outlined p-button-info\" (click)=\"addNewItemToCollection()\"></button>\n </div>\n </div>\n</div>\n", styles: [".cdk-drag-handle{cursor:move}.cdk-drag-preview{background-color:#ffffffd0;border:2px dashed rgb(206,212,218);cursor:move}.cdk-drag-placeholder{border:2px dashed rgb(206,212,218);margin:-2px}.brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"] }]
2840
- }], ctorParameters: () => [{ type: i1$1.FormService }, { type: i2$3.ControlContainer }], propDecorators: { inputCollectionRef: [{
2841
- type: ContentChild,
2842
- args: ['inputCollection', { static: false }]
2843
- }] } });
2844
- /**
2845
- * A form input element that is automatically created from a property in a form template.
2846
- * This may also be a complex object or a collection in which case multiple and nested input elements may be rendered.
2847
- * If you also want a label, use {@link RestWorldFormElement} `<rw-form-element>`.
2848
- * You can also use one of the different RestWorldInput... `<rw-input-...>` elements to render a specific input,
2849
- * but it is advised to control the rendered input through the passed in property.
2850
- * @example
2851
- * <rw-input [property]="property" [apiName]="apiName"></rw-input>
2852
- */
2853
- class RestWorldInputComponent extends RestWorldInputLazyLoadBaseComponent {
2854
- get PropertyType() {
2855
- return PropertyType;
2937
+ /**
2938
+ * Creates an OData `$filter` value for an array of {@link FilterMetadata} objects and a given {@link Property}.
2939
+ * @param property - The {@link Property} to filter on.
2940
+ * @param filters - An array of {@link FilterMetadata} objects.
2941
+ * @returns The OData `$filter` value, or undefined if no filters were provided.
2942
+ */
2943
+ static createFilterForPropertyArray(property, filters) {
2944
+ const filter = filters
2945
+ .map(f => ODataService.createFilterForProperty(property, f))
2946
+ .filter(f => !!f)
2947
+ .join(` ${filters[0].operator} `);
2948
+ if (filter === '')
2949
+ return undefined;
2950
+ return `(${filter})`;
2856
2951
  }
2857
- get PropertyWithOptions() {
2858
- return PropertyWithOptions;
2952
+ /**
2953
+ * Creates an OData `$filter` value from a {@link TableLazyLoadEvent} and an array of {@link Property Properties}.
2954
+ * @param event The {@link TableLazyLoadEvent} containing the filter data.
2955
+ * @param properties An optional array of {@link Property Properties} to filter on.
2956
+ * @returns The OData $`$filter` value, or undefined if no filters were applied or no properties were provided.
2957
+ */
2958
+ static createFilterFromTableLoadEvent(event, properties) {
2959
+ const eventFilters = event.filters;
2960
+ if (eventFilters === undefined || properties === undefined)
2961
+ return undefined;
2962
+ const filter = properties
2963
+ .map(property => ({ property, filters: eventFilters[property.name] }))
2964
+ .filter(f => f.filters !== undefined)
2965
+ .map(f => ODataService.createFilterForPropertyArray(f.property, f.filters))
2966
+ .filter(f => !!f)
2967
+ .join(' and ');
2968
+ if (filter === '')
2969
+ return undefined;
2970
+ return `(${filter})`;
2859
2971
  }
2860
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2861
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputComponent, isStandalone: true, selector: "rw-input", usesInheritance: true, ngImport: i0, template: "@if (property()) {\r\n @if (property().options) {\r\n <rw-input-dropdown [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-dropdown>\r\n }\r\n @else {\r\n @switch (property().type) {\r\n @case (PropertyType.Object) {\r\n <rw-input-object [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-object>\r\n }\r\n @case (PropertyType.Collection) {\r\n <rw-input-collection [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-collection>\r\n }\r\n @default {\r\n <rw-input-simple [property]=\"property()\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-simple>\r\n }\r\n }\r\n }\r\n\r\n <rw-validation-errors [property]=\"property()\"></rw-validation-errors>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputDropdownComponent), selector: "rw-input-dropdown", inputs: ["caseSensitive", "getLabel", "getTooltip"], outputs: ["onChange"] }, { kind: "component", type: i0.forwardRef(() => RestWorldInputObjectComponent), selector: "rw-input-object" }, { kind: "component", type: i0.forwardRef(() => RestWorldInputSimpleComponent), selector: "rw-input-simple" }, { kind: "component", type: i0.forwardRef(() => RestWorldInputCollectionComponent), selector: "rw-input-collection" }, { kind: "component", type: i0.forwardRef(() => RestWorldValidationErrorsComponent), selector: "rw-validation-errors", inputs: ["form", "property"] }, { kind: "ngmodule", type: i0.forwardRef(() => FormsModule) }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
2862
- }
2863
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputComponent, decorators: [{
2864
- type: Component,
2865
- args: [{ selector: 'rw-input', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputDropdownComponent), forwardRef(() => RestWorldInputObjectComponent), forwardRef(() => RestWorldInputSimpleComponent), RestWorldInputCollectionComponent, RestWorldValidationErrorsComponent, FormsModule], template: "@if (property()) {\r\n @if (property().options) {\r\n <rw-input-dropdown [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-dropdown>\r\n }\r\n @else {\r\n @switch (property().type) {\r\n @case (PropertyType.Object) {\r\n <rw-input-object [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-object>\r\n }\r\n @case (PropertyType.Collection) {\r\n <rw-input-collection [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-collection>\r\n }\r\n @default {\r\n <rw-input-simple [property]=\"property()\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-simple>\r\n }\r\n }\r\n }\r\n\r\n <rw-validation-errors [property]=\"property()\"></rw-validation-errors>\r\n}\r\n" }]
2866
- }] });
2867
- /**
2868
- * A dropdown that is automatically created from the given property.
2869
- * The dropdown supports searching through a RESTWorld list endpoint on the backend if the `link` of the options is set.
2870
- * Otherwise the dropdown will use the `inline` of the options.
2871
- * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
2872
- * instead of using this component directly.
2873
- * @example
2874
- * <rw-input-dropdown [property]="property" [apiName]="apiName"></rw-input-dropdown>
2875
- */
2876
- class RestWorldInputDropdownComponent extends RestWorldInputLazyLoadBaseComponent {
2877
- _controlContainer;
2878
2972
  /**
2879
- * A flag that indicates if the search should be case sensitive.
2880
- * The default is false.
2973
+ * Creates a map of property names to filter metadata from an OData $filter string.
2974
+ * @param filter The OData filter string to parse.
2975
+ * @returns A record of property names to filter metadata.
2881
2976
  */
2882
- caseSensitive = input(false, ...(ngDevMode ? [{ debugName: "caseSensitive" }] : []));
2977
+ static createFilterMetadataFromODataFilter(filter, properties) {
2978
+ const filters = ODataFilterParser.parseFilter(filter, properties);
2979
+ return filters;
2980
+ }
2883
2981
  /**
2884
- * A function that returns the label for the given item.
2885
- * The default returns the prompt and optionally the value in brackets.
2886
- * The value in brackets will only be displayed if the `cols` field of the property is undefined or greater than 1.
2887
- * Overwrite this function to change the label.
2888
- * @param item The item to get the label for.
2982
+ * Creates a OData `$orderby` value from a {@link TableLazyLoadEvent}.
2983
+ * @param event The {@link TableLazyLoadEvent} to create the `$orderby` value from.
2984
+ * @returns The `$orderby` value created from the {@link TableLazyLoadEvent}.
2889
2985
  */
2890
- getLabel = input(...(ngDevMode ? [undefined, { debugName: "getLabel" }] : []));
2986
+ static createOrderByFromTableLoadEvent(event) {
2987
+ if (event.multiSortMeta && event.multiSortMeta.length > 0) {
2988
+ return event.multiSortMeta
2989
+ .map(m => `${m.field} ${m.order > 0 ? 'asc' : 'desc'}`)
2990
+ .join(', ');
2991
+ }
2992
+ if (event.sortField) {
2993
+ const order = !event.sortOrder || event.sortOrder > 0 ? 'asc' : 'desc';
2994
+ return `${event.sortField} ${order}`;
2995
+ }
2996
+ return undefined;
2997
+ }
2891
2998
  /**
2892
- * A function that returns the tooltip for the given item.
2893
- * The default returns all properties of the item except the ones that start with an underscore or the ones that are in the list of default properties to exclude.
2894
- * The default properties to exclude are: createdAt, createdBy, lastChangedAt, lastChangedBy, timestamp, promptField, valueField.
2895
- * Overwrite this function to change the tooltip.
2896
- * @param item The item to get the label for.
2999
+ * Creates an {@link ODataParameters} object from a given {@link ActivatedRoute}.
3000
+ * @param route The {@link ActivatedRoute} to create the {@link ODataParameters} from.
3001
+ * @param prefix An optional prefix to use for the query parameter keys.
3002
+ * @returns The {@link ODataParameters} object created from the {@link ActivatedRoute}.
2897
3003
  */
2898
- getTooltip = input(...(ngDevMode ? [undefined, { debugName: "getTooltip" }] : []));
2899
- inputOptionsMultipleRef = contentChild("inputOptionsMultiple", ...(ngDevMode ? [{ debugName: "inputOptionsMultipleRef" }] : []));
2900
- inputOptionsSingleRef = contentChild("inputOptionsSingle", ...(ngDevMode ? [{ debugName: "inputOptionsSingleRef" }] : []));
2901
- multiSelect = viewChild(MultiSelect, ...(ngDevMode ? [{ debugName: "multiSelect" }] : []));
3004
+ static createParametersFromRoute(route, prefix) {
3005
+ const snapshot = route.snapshot;
3006
+ let oDataParameters = {};
3007
+ const filter = snapshot.queryParamMap.get(`${prefix}$filter`);
3008
+ const orderBy = snapshot.queryParamMap.get(`${prefix}$orderby`);
3009
+ const top = snapshot.queryParamMap.get(`${prefix}$top`);
3010
+ const skip = snapshot.queryParamMap.get(`${prefix}$skip`);
3011
+ if (filter)
3012
+ oDataParameters.$filter = filter;
3013
+ if (orderBy)
3014
+ oDataParameters.$orderby = orderBy;
3015
+ if (top) {
3016
+ const topNumber = Number.parseInt(top);
3017
+ if (Number.isInteger(topNumber))
3018
+ oDataParameters.$top = topNumber;
3019
+ }
3020
+ if (skip) {
3021
+ const skipNumber = Number.parseInt(skip);
3022
+ if (Number.isInteger(skipNumber))
3023
+ oDataParameters.$skip = skipNumber;
3024
+ }
3025
+ return oDataParameters;
3026
+ }
2902
3027
  /**
2903
- * An event that is emitted when the selected value changes.
3028
+ * Creates {@link ODataParameters} from a {@link TableLazyLoadEvent} and an optional {@link Template}.
3029
+ * @param event The {@link TableLazyLoadEvent} to create OData parameters from.
3030
+ * @param template An optional {@link Template} to use for creating the OData parameters.
3031
+ * @returns An {@link ODataParameters} object containing the `$filter`, `$orderby`, `$top`, and `$skip` parameters.
2904
3032
  */
2905
- onChange = output();
2906
- onOptionsFiltered = debounce(this.onOptionsFilteredInternal, 500);
2907
- optionsManager;
2908
- _formControl = computed(() => {
2909
- const formGroup = this._controlContainer.control;
2910
- return formGroup.controls[this.property().name];
2911
- }, ...(ngDevMode ? [{ debugName: "_formControl" }] : []));
2912
- _valueChangesSignal = toSignal(merge(
2913
- // Get the initial value when 'control' changes
2914
- toObservable(this._formControl).pipe(map((ctl) => ctl.value)),
2915
- // Get the new value when 'control.value' changes
2916
- toObservable(this._formControl).pipe(mergeMap((ctl) => ctl.valueChanges))));
2917
- constructor(_controlContainer, optionsService) {
2918
- super();
2919
- this._controlContainer = _controlContainer;
2920
- //const valueChangesSignal = toSignal(this._formControl.valueChanges);
2921
- this.optionsManager = optionsService.getManager(this.apiName, this.property, this._valueChangesSignal, this.getLabel, this.getTooltip);
3033
+ static createParametersFromTableLoadEvent(event, template) {
3034
+ const oDataParameters = {
3035
+ $filter: ODataService.createFilterFromTableLoadEvent(event, template?.properties),
3036
+ $orderby: ODataService.createOrderByFromTableLoadEvent(event),
3037
+ $top: ODataService.createTopFromTableLoadEvent(event),
3038
+ $skip: ODataService.createSkipFromTableLoadEvent(event)
3039
+ };
3040
+ return oDataParameters;
2922
3041
  }
2923
- async ngOnInit() {
2924
- await this.optionsManager.initialize();
3042
+ /**
3043
+ * Creates a OData `$skip` value from a {@link TableLazyLoadEvent}.
3044
+ * @param event The {@link TableLazyLoadEvent} to create the `$skip` value from.
3045
+ * @returns The `$skip` value created from the {@link TableLazyLoadEvent}.
3046
+ */
3047
+ static createSkipFromTableLoadEvent(event) {
3048
+ return event.first;
2925
3049
  }
2926
- onOptionsChanged(event) {
2927
- this.onChange.emit(event);
3050
+ /**
3051
+ * Creates a OData `$top` value from a {@link TableLazyLoadEvent}.
3052
+ * @param event The {@link TableLazyLoadEvent} to create the `$top` value from.
3053
+ * @returns The `$top` value created from the {@link TableLazyLoadEvent}.
3054
+ */
3055
+ static createTopFromTableLoadEvent(event) {
3056
+ return event.rows === null ? undefined : event.rows;
2928
3057
  }
2929
- async onOptionsFilteredInternal(event) {
2930
- const options = this.optionsManager.options();
2931
- const currentItems = this.optionsManager.items();
2932
- if (!(event.filter) || event.filter === '')
2933
- return;
2934
- if (event.originalEvent.type === "input") {
2935
- const inputEvent = event.originalEvent;
2936
- if (inputEvent.inputType === "insertFromPaste") {
2937
- // If the user pasted in multiple ids as comma separated list, we want to get them all and set them as the selected value.
2938
- var values = event.filter
2939
- .split(",")
2940
- .filter(v => v !== '')
2941
- .map(v => v.trim())
2942
- .map(v => {
2943
- const n = Number.parseFloat(v);
2944
- return Number.isNaN(n) ? this.makeUpperIfCaseInsensitive(v.toUpperCase(), false) : n;
3058
+ static createComparisonValue(property, value, isEnum) {
3059
+ if (value === null || value === undefined)
3060
+ return 'null';
3061
+ const type = ODataService.getPropertyType(property, value);
3062
+ switch (type) {
3063
+ case PropertyType.Date:
3064
+ return `cast(${value.toISOString()}, Edm.DateOnly)`;
3065
+ case PropertyType.DatetimeLocal:
3066
+ return `cast(${value.toISOString()}, Edm.DateTime)`;
3067
+ case PropertyType.DatetimeOffset:
3068
+ return `cast(${value.toISOString()}, Edm.DateTimeOffset)`;
3069
+ case PropertyType.Time:
3070
+ return `cast(${value.toISOString()}, Edm.TimeOnly)`;
3071
+ case PropertyType.Duration:
3072
+ return `cast(${value.toISOString()}, Edm.TimeSpan)`;
3073
+ case PropertyType.Bool:
3074
+ case PropertyType.Number:
3075
+ case PropertyType.Currency:
3076
+ case PropertyType.Month:
3077
+ return '' + value;
3078
+ case PropertyType.Percent:
3079
+ return '' + (value / 100);
3080
+ default:
3081
+ return `'${isEnum && typeof value === "string" ? value.charAt(0).toUpperCase() + value.slice(1) : value}'`;
3082
+ }
3083
+ }
3084
+ static createFilterForEnum(property, filter) {
3085
+ if (filter.matchMode == TranslationKeys.NO_FILTER)
3086
+ return undefined;
3087
+ const options = property.options;
3088
+ if (options === undefined)
3089
+ throw Error(`Property ${property.name} has no options`);
3090
+ const maxItems = options.maxItems ?? Number.MAX_SAFE_INTEGER;
3091
+ const oDataOperator = ODataService.createODataOperator(filter.matchMode);
3092
+ // Normal enum
3093
+ if (maxItems === 1) {
3094
+ const comparisonValue = ODataService.createComparisonValue(property, filter.value, true);
3095
+ return `${property.name} ${oDataOperator} ${comparisonValue}`;
3096
+ }
3097
+ // Flags enum
3098
+ if (filter.value === null || filter.value === undefined)
3099
+ return undefined;
3100
+ const values = Array.isArray(filter.value) ? filter.value : [filter.value];
3101
+ const comparisonValues = values.map(v => ODataService.createComparisonValue(property, v, true));
3102
+ const filters = comparisonValues.map(v => `${property.name} has ${v}`);
3103
+ const concatenatedFilters = filters.join(' and ');
3104
+ return `(${concatenatedFilters})`;
3105
+ }
3106
+ static createODataOperator(matchMode) {
3107
+ switch (matchMode) {
3108
+ case FilterMatchMode.STARTS_WITH:
3109
+ return 'startswith';
3110
+ case FilterMatchMode.CONTAINS:
3111
+ return 'contains';
3112
+ case FilterMatchMode.NOT_CONTAINS:
3113
+ return 'not contains';
3114
+ case FilterMatchMode.ENDS_WITH:
3115
+ return 'endswith';
3116
+ case FilterMatchMode.EQUALS:
3117
+ return 'eq';
3118
+ case FilterMatchMode.NOT_EQUALS:
3119
+ return 'ne';
3120
+ case FilterMatchMode.IN:
3121
+ return 'in';
3122
+ case FilterMatchMode.LESS_THAN:
3123
+ return 'lt';
3124
+ case FilterMatchMode.LESS_THAN_OR_EQUAL_TO:
3125
+ return 'le';
3126
+ case FilterMatchMode.GREATER_THAN:
3127
+ return 'gt';
3128
+ case FilterMatchMode.GREATER_THAN_OR_EQUAL_TO:
3129
+ return 'ge';
3130
+ case FilterMatchMode.IS:
3131
+ return 'eq';
3132
+ case FilterMatchMode.IS_NOT:
3133
+ return 'ne';
3134
+ case FilterMatchMode.BEFORE:
3135
+ return 'lt';
3136
+ case FilterMatchMode.AFTER:
3137
+ return 'gt';
3138
+ case FilterMatchMode.DATE_AFTER:
3139
+ return 'ge';
3140
+ case FilterMatchMode.DATE_BEFORE:
3141
+ return 'lt';
3142
+ case FilterMatchMode.DATE_IS:
3143
+ return 'eq';
3144
+ case FilterMatchMode.DATE_IS_NOT:
3145
+ return 'ne';
3146
+ default:
3147
+ throw Error(`Unknown matchMode ${matchMode}`);
3148
+ }
3149
+ }
3150
+ static getPropertyType(property, value) {
3151
+ if (property.options) {
3152
+ if (typeof value === "string" ||
3153
+ Array.isArray(value) && value.every(v => typeof v === "string") ||
3154
+ property.options.inline?.some(o => property.options.valueField !== undefined && typeof o[property.options.valueField] === "string"))
3155
+ return PropertyType.Text;
3156
+ return PropertyType.Number;
3157
+ }
3158
+ return property.type;
3159
+ }
3160
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ODataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3161
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ODataService, providedIn: 'root' });
3162
+ }
3163
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ODataService, decorators: [{
3164
+ type: Injectable,
3165
+ args: [{
3166
+ providedIn: 'root',
3167
+ }]
3168
+ }] });
3169
+
3170
+ /**
3171
+ * Displays one button for every `MenuItem` given in the items array.
3172
+ * If a `MenuItem` has nested items, a button with a dropdown menu will be displayed.
3173
+ * @example
3174
+ * <rw-menu-button [items]="items"></rw-menu-button>
3175
+ */
3176
+ class RestWorldMenuButtonComponent {
3177
+ _router;
3178
+ /**
3179
+ * An array of menu items to be displayed.
3180
+ */
3181
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
3182
+ constructor(_router) {
3183
+ this._router = _router;
3184
+ }
3185
+ onClick(event, item) {
3186
+ const openInNewWindow = event.ctrlKey || event.button === 1 || event.metaKey;
3187
+ if (item.url) {
3188
+ window.open(item.url, openInNewWindow ? '_blank' : '_self');
3189
+ }
3190
+ else if (item.routerLink) {
3191
+ if (openInNewWindow) {
3192
+ const tree = this._router.createUrlTree(item.routerLink, {
3193
+ queryParams: item.queryParams,
3194
+ fragment: item.fragment,
3195
+ queryParamsHandling: item.queryParamsHandling,
3196
+ preserveFragment: item.preserveFragment,
2945
3197
  });
2946
- if (!values || values.length === 0)
2947
- return;
2948
- const allAreNumbers = values.every(v => typeof v === "number" && !isNaN(v));
2949
- const filter = allAreNumbers
2950
- ? `${options.valueField} in (${values.join(',')})`
2951
- : `contains(${this.makeUpperIfCaseInsensitive(options.promptField, true)}, '${values.join("', '")}')`;
2952
- if ((options?.link?.href))
2953
- await this.optionsManager.updateItemsFromFilter(filter);
2954
- if (currentItems) {
2955
- const selectedValues = currentItems
2956
- .map(i => this.optionsManager.getValue(i))
2957
- .filter(v => values.includes(v));
2958
- this._formControl().setValue(selectedValues);
2959
- this.multiSelect()?.resetFilter();
2960
- }
3198
+ const url = this._router.serializeUrl(tree);
3199
+ window.open(url, '_blank');
2961
3200
  }
2962
3201
  else {
2963
- // This is the normal case where the user types in a filter.
2964
- let filter = `contains(${this.makeUpperIfCaseInsensitive(options.promptField, true)}, '${this.makeUpperIfCaseInsensitive(event.filter, false)}')`;
2965
- if (options.valueField?.toLowerCase() === 'id' && !Number.isNaN(Number.parseInt(event.filter)))
2966
- filter = `(${options.valueField} eq ${event.filter}) or (${filter})`;
2967
- if ((options?.link?.href))
2968
- await this.optionsManager.updateItemsFromFilter(filter);
3202
+ this._router.navigate(item.routerLink, {
3203
+ queryParams: item.queryParams,
3204
+ fragment: item.fragment,
3205
+ queryParamsHandling: item.queryParamsHandling,
3206
+ preserveFragment: item.preserveFragment,
3207
+ skipLocationChange: item.skipLocationChange,
3208
+ replaceUrl: item.replaceUrl,
3209
+ });
2969
3210
  }
2970
3211
  }
3212
+ else if (item.command) {
3213
+ item.command({ item: item });
3214
+ }
2971
3215
  }
2972
- makeUpperIfCaseInsensitive(filter, isOData) {
2973
- if (this.caseSensitive() || typeof filter !== "string")
2974
- return filter;
2975
- if (isOData)
2976
- return `toupper(${filter})`;
2977
- return filter.toUpperCase();
2978
- }
2979
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputDropdownComponent, deps: [{ token: i2$3.ControlContainer }, { token: OptionsService }], target: i0.ɵɵFactoryTarget.Component });
2980
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputDropdownComponent, isStandalone: true, selector: "rw-input-dropdown", inputs: { caseSensitive: { classPropertyName: "caseSensitive", publicName: "caseSensitive", isSignal: true, isRequired: false, transformFunction: null }, getLabel: { classPropertyName: "getLabel", publicName: "getLabel", isSignal: true, isRequired: false, transformFunction: null }, getTooltip: { classPropertyName: "getTooltip", publicName: "getTooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChange: "onChange" }, queries: [{ propertyName: "inputOptionsMultipleRef", first: true, predicate: ["inputOptionsMultiple"], descendants: true, isSignal: true }, { propertyName: "inputOptionsSingleRef", first: true, predicate: ["inputOptionsSingle"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "multiSelect", first: true, predicate: MultiSelect, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-template #defaultInputOptionsSingle let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-select\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n @else {\n <p-select\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n</ng-template>\n\n<ng-template #defaultInputOptionsMultiple let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-multiSelect\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n @else {\n <p-multiSelect\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n</ng-template>\n\n@if(!optionsManager.options().maxItems || optionsManager.options().maxItems == 1) {\n <ng-container *ngTemplateOutlet=\"inputOptionsSingleRef() ?? defaultInputOptionsSingle; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n@else {\n <ng-container *ngTemplateOutlet=\"inputOptionsMultipleRef() ?? defaultInputOptionsMultiple; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n", styles: ["::ng-deep .p-multiselect-label{display:inline-flex!important}::ng-deep .p-multiselect-label-empty{height:36px}::ng-deep .p-chip{background-color:#eff6ff;color:#1d4ed8}::ng-deep .pi-chip-remove-icon:hover{filter:brightness(.6)}\n"], dependencies: [{ kind: "component", type: Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "component", type: MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "component", type: Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: HalFormsModule }, { kind: "directive", type: FormControlProperty, selector: "[formControlProperty]:not([useTemplateDrivenForms=true])", inputs: ["formControlProperty"] }, { kind: "directive", type: PropertyControlStatus, selector: "[formControlProperty]" }, { kind: "directive", type: PropertySelectAttributes, selector: "p-select[formControlProperty], p-select[propertyAttributes], p-multiSelect[formControlProperty], p-multiSelect[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "directive", type: PropertyAttributes, selector: "[formControlProperty],[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3216
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldMenuButtonComponent, deps: [{ token: i3$3.Router }], target: i0.ɵɵFactoryTarget.Component });
3217
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldMenuButtonComponent, isStandalone: true, selector: "rw-menu-button", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@for (item of items(); track item; let i = $index) {\n @if (!item.items) {\n <p-button\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-button>\n } \n @else {\n <p-splitButton\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [model]=\"item.items\"\n appendTo=\"body\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-splitButton>\n }\n}\n", styles: [":host{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: SplitButtonModule }, { kind: "component", type: i4$1.SplitButton, selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }] });
2981
3218
  }
2982
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputDropdownComponent, decorators: [{
3219
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldMenuButtonComponent, decorators: [{
2983
3220
  type: Component,
2984
- args: [{ selector: 'rw-input-dropdown', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [Select, ReactiveFormsModule, MultiSelect, Tooltip, Chip, NgTemplateOutlet, HalFormsModule, FormsModule], template: "<ng-template #defaultInputOptionsSingle let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-select\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n @else {\n <p-select\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n</ng-template>\n\n<ng-template #defaultInputOptionsMultiple let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-multiSelect\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n @else {\n <p-multiSelect\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n</ng-template>\n\n@if(!optionsManager.options().maxItems || optionsManager.options().maxItems == 1) {\n <ng-container *ngTemplateOutlet=\"inputOptionsSingleRef() ?? defaultInputOptionsSingle; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n@else {\n <ng-container *ngTemplateOutlet=\"inputOptionsMultipleRef() ?? defaultInputOptionsMultiple; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n", styles: ["::ng-deep .p-multiselect-label{display:inline-flex!important}::ng-deep .p-multiselect-label-empty{height:36px}::ng-deep .p-chip{background-color:#eff6ff;color:#1d4ed8}::ng-deep .pi-chip-remove-icon:hover{filter:brightness(.6)}\n"] }]
2985
- }], ctorParameters: () => [{ type: i2$3.ControlContainer }, { type: OptionsService }] });
3221
+ args: [{ selector: 'rw-menu-button', standalone: true, imports: [ButtonModule, TooltipModule, SplitButtonModule], template: "@for (item of items(); track item; let i = $index) {\n @if (!item.items) {\n <p-button\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-button>\n } \n @else {\n <p-splitButton\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [model]=\"item.items\"\n appendTo=\"body\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-splitButton>\n }\n}\n", styles: [":host{display:flex;justify-content:flex-end}\n"] }]
3222
+ }], ctorParameters: () => [{ type: i3$3.Router }] });
3223
+
2986
3224
  /**
2987
- * A complex object with multiple properties that is automatically created from the given property.
2988
- * The object can also be nested.
2989
- * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
2990
- * instead of using this component directly.
2991
- * @example
2992
- * <rw-input-object [property]="property" [apiName]="apiName"></rw-input-object>
3225
+ * This component is used to display a filter element for a table column.
3226
+ * It is used internally by the <rw-table> component, but can also be used in the #filter template of a column in the header of a <p-table>.
2993
3227
  */
2994
- class RestWorldInputObjectComponent extends RestWorldInputLazyLoadBaseComponent {
2995
- _controlContainer;
2996
- inputObjectRef = contentChild("inputObject", ...(ngDevMode ? [{ debugName: "inputObjectRef" }] : []));
2997
- constructor(_controlContainer) {
2998
- super();
2999
- this._controlContainer = _controlContainer;
3228
+ class RestWorldTableColumnFilterElementComponent {
3229
+ _formService;
3230
+ /**
3231
+ * The filter constraint to update when the value changes.
3232
+ * This is coming from the $context of the #filter template
3233
+ */
3234
+ filterConstraint = input.required(...(ngDevMode ? [{ debugName: "filterConstraint" }] : []));
3235
+ /**
3236
+ * The HAL-Forms property to filter by.
3237
+ * This is normally the column.
3238
+ */
3239
+ property = input.required(...(ngDevMode ? [{ debugName: "property" }] : []));
3240
+ /**
3241
+ * The name of the API to use when generating dropdowns.
3242
+ */
3243
+ apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
3244
+ /**
3245
+ * The initial value of the filter.
3246
+ */
3247
+ value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
3248
+ form = computed(() => this._formService.createFormGroupFromTemplate(this.template()), ...(ngDevMode ? [{ debugName: "form" }] : []));
3249
+ template = computed(() => new Template({
3250
+ properties: [this.property()],
3251
+ }), ...(ngDevMode ? [{ debugName: "template" }] : []));
3252
+ _formValueChangesSubscription;
3253
+ constructor(_formService) {
3254
+ this._formService = _formService;
3255
+ effect(() => {
3256
+ this._formValueChangesSubscription?.unsubscribe();
3257
+ const form = this.form();
3258
+ const property = this.property();
3259
+ const value = untracked(() => this.value());
3260
+ const formControl = form.controls[property.name];
3261
+ this._formValueChangesSubscription = formControl.valueChanges.subscribe(this.setFilterValue.bind(this));
3262
+ formControl.setValue(value);
3263
+ });
3000
3264
  }
3001
- innerFormGroup = computed(() => {
3002
- const formGroup = this._controlContainer.control;
3003
- return formGroup.controls[this.property().name];
3004
- }, ...(ngDevMode ? [{ debugName: "innerFormGroup" }] : []));
3005
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputObjectComponent, deps: [{ token: i2$3.ControlContainer }], target: i0.ɵɵFactoryTarget.Component });
3006
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.1", type: RestWorldInputObjectComponent, isStandalone: true, selector: "rw-input-object", queries: [{ propertyName: "inputObjectRef", first: true, predicate: ["inputObject"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-template #defaultInputObject let-property=\"property\" let-innerFormGroup=\"innerFormGroup\", let-apiName=\"apiName\">\n <div class=\"flex align-items-center\">\n <div class=\"brace\">\n </div>\n <div class=\"w-full\">\n <rw-input-template [formGroup]=\"innerFormGroup()\" [template]=\"property()._templates.default\" [apiName]=\"apiName()\"></rw-input-template>\n </div>\n </div>\n</ng-template>\n\n<ng-container>\n <ng-container *ngTemplateOutlet=\"inputObjectRef() || defaultInputObject; context: { property: property, innerFormGroup: innerFormGroup, apiName: apiName }\"></ng-container>\n</ng-container>\n", styles: [".brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputTemplateComponent), selector: "rw-input-template", inputs: ["apiName", "template"] }, { kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "directive", type: i0.forwardRef(() => i2$3.NgControlStatusGroup), selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i0.forwardRef(() => i2$3.FormGroupDirective), selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i0.forwardRef(() => NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3265
+ setFilterValue(value) {
3266
+ this.filterConstraint().value = value;
3267
+ }
3268
+ ngOnDestroy() {
3269
+ this._formValueChangesSubscription?.unsubscribe();
3270
+ }
3271
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableColumnFilterElementComponent, deps: [{ token: i1$1.FormService }], target: i0.ɵɵFactoryTarget.Component });
3272
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldTableColumnFilterElementComponent, isStandalone: true, selector: "rw-table-column-filter-element", inputs: { filterConstraint: { classPropertyName: "filterConstraint", publicName: "filterConstraint", isSignal: true, isRequired: true, transformFunction: null }, property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<form [formGroup]=\"form()\">\r\n <rw-input [property]=\"property()\" [apiName]=\"apiName()\"></rw-input>\r\n</form>\r\n", styles: [""], dependencies: [{ kind: "component", type: RestWorldInputComponent, selector: "rw-input" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3007
3273
  }
3008
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputObjectComponent, decorators: [{
3274
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableColumnFilterElementComponent, decorators: [{
3009
3275
  type: Component,
3010
- args: [{ selector: 'rw-input-object', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputTemplateComponent), ReactiveFormsModule, NgTemplateOutlet], template: "<ng-template #defaultInputObject let-property=\"property\" let-innerFormGroup=\"innerFormGroup\", let-apiName=\"apiName\">\n <div class=\"flex align-items-center\">\n <div class=\"brace\">\n </div>\n <div class=\"w-full\">\n <rw-input-template [formGroup]=\"innerFormGroup()\" [template]=\"property()._templates.default\" [apiName]=\"apiName()\"></rw-input-template>\n </div>\n </div>\n</ng-template>\n\n<ng-container>\n <ng-container *ngTemplateOutlet=\"inputObjectRef() || defaultInputObject; context: { property: property, innerFormGroup: innerFormGroup, apiName: apiName }\"></ng-container>\n</ng-container>\n", styles: [".brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"] }]
3011
- }], ctorParameters: () => [{ type: i2$3.ControlContainer }] });
3012
- /**
3013
- * A simple input element, like a string, a number or a Date that is automatically created from the given property.
3014
- * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
3015
- * instead of using this component directly.
3016
- * @example
3017
- * <rw-input-simple [property]="property" [apiName]="apiName"></rw-input-simple>
3018
- */
3019
- class RestWorldInputSimpleComponent extends RestWorldInputBaseComponent {
3020
- static _dateFormat = new Date(3333, 10, 22) // months start at 0 in JS
3021
- .toLocaleDateString()
3022
- .replace("22", "dd")
3023
- .replace("11", "mm")
3024
- .replace("3333", "yy")
3025
- .replace("33", "y");
3026
- static _timeFormat = new Date(1, 1, 1, 22, 33, 44)
3027
- .toLocaleTimeString()
3028
- .replace("22", "hh")
3029
- .replace("33", "mm")
3030
- .replace("44", "ss");
3031
- _inputChild = viewChild("inputElement", ...(ngDevMode ? [{ debugName: "_inputChild" }] : []));
3032
- _controlChild = viewChild(NG_VALUE_ACCESSOR, ...(ngDevMode ? [{ debugName: "_controlChild" }] : []));
3033
- constructor() {
3034
- super();
3035
- }
3036
- writeValue(obj) {
3037
- const controlChild = this._controlChild();
3038
- const inputChild = this._inputChild();
3039
- if (controlChild !== undefined)
3040
- controlChild.writeValue(obj);
3041
- else if (inputChild !== undefined)
3042
- inputChild.nativeElement.value = obj;
3043
- }
3044
- registerOnChange(fn) {
3045
- const controlChild = this._controlChild();
3046
- const inputChild = this._inputChild();
3047
- if (controlChild !== undefined)
3048
- controlChild.registerOnChange(fn);
3049
- else if (inputChild !== undefined)
3050
- inputChild.nativeElement.oninput = (event) => fn(event.target.value);
3051
- }
3052
- registerOnTouched(fn) {
3053
- const controlChild = this._controlChild();
3054
- const inputChild = this._inputChild();
3055
- if (controlChild !== undefined)
3056
- controlChild.registerOnTouched(fn);
3057
- else if (inputChild !== undefined)
3058
- inputChild.nativeElement.onblur = (event) => fn();
3059
- }
3060
- setDisabledState(isDisabled) {
3061
- const controlChild = this._controlChild();
3062
- const inputChild = this._inputChild();
3063
- if (controlChild !== undefined && controlChild.setDisabledState !== undefined)
3064
- controlChild.setDisabledState(isDisabled);
3065
- else if (inputChild !== undefined) {
3066
- if (isDisabled)
3067
- inputChild.nativeElement.setAttribute("disabled", "disabled");
3068
- else
3069
- inputChild.nativeElement.removeAttribute("disabled");
3070
- }
3071
- }
3072
- get PropertyType() {
3073
- return PropertyType;
3074
- }
3075
- get PropertyWithImage() {
3076
- return PropertyWithImage;
3077
- }
3078
- get dateFormat() {
3079
- return RestWorldInputSimpleComponent._dateFormat;
3080
- }
3081
- // public readonly formControl = computed(() => {
3082
- // const formGroup = this._controlContainer.control as FormGroup<any>;
3083
- // return formGroup.controls[this.property().name] as FormControl<SimpleValue | SimpleValue[]>;
3084
- // });
3085
- get timeFormat() {
3086
- return RestWorldInputSimpleComponent._timeFormat;
3087
- }
3088
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputSimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3089
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputSimpleComponent, isStandalone: true, selector: "rw-input-simple", providers: [{
3090
- provide: NG_VALUE_ACCESSOR,
3091
- useExisting: forwardRef(() => RestWorldInputSimpleComponent),
3092
- multi: true
3093
- }], viewQueries: [{ propertyName: "_inputChild", first: true, predicate: ["inputElement"], descendants: true, isSignal: true }, { propertyName: "_controlChild", first: true, predicate: NG_VALUE_ACCESSOR, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@switch(property().type) {\r\n @case (PropertyType.Textarea) {\r\n @if (useTemplateDrivenForms()){\r\n <textarea #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n @else {\r\n <textarea #inputElement [formControlProperty]=\"property()\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n }\r\n @case (PropertyType.Date) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Month) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Time) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.DatetimeLocal) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Number) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-inputNumber [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n @else {\r\n <p-inputNumber inputMode=\"decimal\" [formControlProperty]=\"property()\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n }\r\n @case (PropertyType.Bool) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-tri-state-checkbox [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [required]=\"property().required ?? false\"></rw-tri-state-checkbox>\r\n }\r\n @else {\r\n <rw-tri-state-checkbox [formControlProperty]=\"property()\" [required]=\"property().required\"></rw-tri-state-checkbox>\r\n }\r\n }\r\n @case (PropertyType.DatetimeOffset) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Duration) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Image) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-image [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n @else {\r\n <rw-image [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n }\r\n @case (PropertyType.File) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-file [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n @else {\r\n <rw-file [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n }\r\n @default {\r\n <!-- <input [formControlName]=\"property().name\" [id]=\"property().name\" type=\"text\" pInputText class=\"w-full\" [class.p-disabled]=\"property().readOnly\" /> -->\r\n @if (useTemplateDrivenForms()) {\r\n <input #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputText class=\"w-full\" />\r\n }\r\n @else {\r\n <input #inputElement [formControlProperty]=\"property()\" pInputText class=\"w-full\" />\r\n }\r\n }\r\n}\r\n", styles: [".p-inputtext.ng-touched.ng-invalid{border-color:#e24c4c}\n"], dependencies: [{ kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "component", type: TriStateCheckbox, selector: "p-tri-state-checkbox, p-tri-state-checkBox, p-tri-state-check-box, rw-tri-state-checkbox, rw-tri-state-checkBox, rw-tri-state-check-box", inputs: ["value", "name", "disabled", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "model", "variant"], outputs: ["disabledChange", "modelChange", "onChange", "onFocus", "onBlur"] }, { kind: "component", type: RestWorldImageComponent, selector: "rw-image", inputs: ["property", "backgroundColor", "displayCropDialog"], outputs: ["backgroundColorChange", "displayCropDialogChange"] }, { kind: "component", type: RestWorldFileComponent, selector: "rw-file", inputs: ["accept", "fileName"] }, { kind: "directive", type: InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: HalFormsModule }, { kind: "directive", type: FormControlProperty, selector: "[formControlProperty]:not([useTemplateDrivenForms=true])", inputs: ["formControlProperty"] }, { kind: "directive", type: DefaultPropertyValueAccessor, selector: "input:not([type=checkbox])[formControlProperty], textarea[formControlProperty], select[formControlProperty]" }, { kind: "directive", type: PropertyControlStatus, selector: "[formControlProperty]" }, { kind: "directive", type: PropertyAttributes, selector: "[formControlProperty],[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "directive", type: PropertyInputNumberAttributes, selector: "p-inputNumber[formControlProperty], p-inputNumber[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "directive", type: i2$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3094
- }
3095
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputSimpleComponent, decorators: [{
3096
- type: Component,
3097
- args: [{ selector: 'rw-input-simple', standalone: true, providers: [{
3098
- provide: NG_VALUE_ACCESSOR,
3099
- useExisting: forwardRef(() => RestWorldInputSimpleComponent),
3100
- multi: true
3101
- }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [DatePicker, InputNumber, TriStateCheckbox, RestWorldImageComponent, RestWorldFileComponent, InputText, HalFormsModule, FormsModule], template: "@switch(property().type) {\r\n @case (PropertyType.Textarea) {\r\n @if (useTemplateDrivenForms()){\r\n <textarea #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n @else {\r\n <textarea #inputElement [formControlProperty]=\"property()\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n }\r\n @case (PropertyType.Date) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Month) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Time) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.DatetimeLocal) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Number) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-inputNumber [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n @else {\r\n <p-inputNumber inputMode=\"decimal\" [formControlProperty]=\"property()\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n }\r\n @case (PropertyType.Bool) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-tri-state-checkbox [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [required]=\"property().required ?? false\"></rw-tri-state-checkbox>\r\n }\r\n @else {\r\n <rw-tri-state-checkbox [formControlProperty]=\"property()\" [required]=\"property().required\"></rw-tri-state-checkbox>\r\n }\r\n }\r\n @case (PropertyType.DatetimeOffset) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Duration) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Image) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-image [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n @else {\r\n <rw-image [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n }\r\n @case (PropertyType.File) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-file [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n @else {\r\n <rw-file [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n }\r\n @default {\r\n <!-- <input [formControlName]=\"property().name\" [id]=\"property().name\" type=\"text\" pInputText class=\"w-full\" [class.p-disabled]=\"property().readOnly\" /> -->\r\n @if (useTemplateDrivenForms()) {\r\n <input #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputText class=\"w-full\" />\r\n }\r\n @else {\r\n <input #inputElement [formControlProperty]=\"property()\" pInputText class=\"w-full\" />\r\n }\r\n }\r\n}\r\n", styles: [".p-inputtext.ng-touched.ng-invalid{border-color:#e24c4c}\n"] }]
3102
- }], ctorParameters: () => [] });
3276
+ args: [{ selector: 'rw-table-column-filter-element', imports: [RestWorldInputComponent, ReactiveFormsModule], template: "<form [formGroup]=\"form()\">\r\n <rw-input [property]=\"property()\" [apiName]=\"apiName()\"></rw-input>\r\n</form>\r\n" }]
3277
+ }], ctorParameters: () => [{ type: i1$1.FormService }] });
3278
+
3103
3279
  /**
3104
- * A collection of `<rw-form>` elements automatically created from a template.
3105
- * Does not have any buttons on its own.
3106
- * If you want buttons, use {@link RestWorldForm} `<rw-form>`.
3280
+ * Displays a table based on a search-, an edit-template and a list of items.
3281
+ * The search-template is required and used to display the table columns and to filter and sort the items.
3282
+ * The edit-template is optional and used to edit the items. For the edit capability, the table must be part of a reactive form.
3283
+ * The items are displayed as table rows.
3284
+ * The table supports lazy loading, row selection, row menus, and context menus.
3285
+ *
3107
3286
  * @example
3108
- * <rw-form-collection [template]="template" [apiName]="apiName"></rw-form-collection>
3287
+ * <rw-table
3288
+ * [apiName]="apiName"
3289
+ * [searchTemplate]="searchTemplate"
3290
+ * [editTemplate]="editTemplate"
3291
+ * [rows]="rows"
3292
+ * [rowsPerPageOptions]="[10, 25, 50]"
3293
+ * [headerMenu]="headerMenu"
3294
+ * [rowMenu]="rowMenu"
3295
+ * [rowStyleClass]="rowStyleClass"
3296
+ * [cellStyleClass]="cellStyleClass"
3297
+ * [totalRecords]="totalRecords"
3298
+ * [multiSortMeta]="multiSortMeta"
3299
+ * [styleClass]="styleClass"
3300
+ * [tableStyle]="tableStyle"
3301
+ * [scrollable]="scrollable"
3302
+ * [scrollHeight]="scrollHeight"
3303
+ * [selectionMode]="selectionMode"
3304
+ * [rowHover]="rowHover"
3305
+ * [selection]="selection"
3306
+ * [contextMenuItems]="contextMenuItems"
3307
+ * [isLoading]="isLoading"
3308
+ * [(selectedRows)]="selectedRows"
3309
+ * [(oDataParameters)]="oDataParameters"
3310
+ * </rw-table>
3311
+ *
3109
3312
  */
3110
- class RestWorldInputTemplateComponent {
3313
+ class RestWorldTableComponent {
3314
+ _controlContainer;
3315
+ _formService;
3316
+ onSort($event) {
3317
+ if (this.lazy())
3318
+ return;
3319
+ console.log("Sort event:", $event);
3320
+ // sort the form array for the indexes to line up with the sorted rows
3321
+ const formArray = this.formArray();
3322
+ const editTemplate = this.editTemplate();
3323
+ if (!formArray || !editTemplate)
3324
+ return;
3325
+ formArray.controls.sort((a, b) => {
3326
+ for (const sortMeta of $event.multisortmeta) {
3327
+ const field = sortMeta.field;
3328
+ const valueA = a.get(field)?.value;
3329
+ const valueB = b.get(field)?.value;
3330
+ const order = sortMeta.order ?? 1;
3331
+ if (valueA === valueB)
3332
+ continue;
3333
+ if (valueA === undefined || valueA === null)
3334
+ return -1 * order;
3335
+ if (valueB === undefined || valueB === null)
3336
+ return 1 * order;
3337
+ if (valueA < valueB)
3338
+ return -1 * order;
3339
+ if (valueA > valueB)
3340
+ return 1 * order;
3341
+ }
3342
+ return 0;
3343
+ });
3344
+ }
3345
+ PropertyType = PropertyType;
3111
3346
  /**
3112
- * The name of the API to use for the property.
3113
- * @required
3114
- * @remarks This is the name of the API as defined in the `RestWorldClientCollection`.
3347
+ * The name of the api.
3348
+ * For the editing capability, you must also set the editTemplate and the formArray.
3115
3349
  */
3116
3350
  apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
3117
3351
  /**
3118
- * The template to display.
3119
- * @required
3120
- * @remarks This is the template that defines the properties to display.
3352
+ * A function that returns the style class for a cell.
3353
+ * @param row The row for which to return the style class.
3354
+ * @param column The column for which to return the style class.
3355
+ * @param rowIndex The index of the row on the currently displayed page.
3356
+ * @param columnIndex The index of the column.
3357
+ * @returns The style class for the cell.
3121
3358
  */
3122
- template = input.required(...(ngDevMode ? [{ debugName: "template" }] : []));
3123
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputTemplateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3124
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputTemplateComponent, isStandalone: true, selector: "rw-input-template", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@for (property of template().properties; track property) {\r\n <rw-form-element [property]=\"property\" [apiName]=\"apiName()\"></rw-form-element>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: RestWorldFormElementComponent, selector: "rw-form-element" }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3125
- }
3126
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputTemplateComponent, decorators: [{
3127
- type: Component,
3128
- args: [{ selector: 'rw-input-template', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [RestWorldFormElementComponent], template: "@for (property of template().properties; track property) {\r\n <rw-form-element [property]=\"property\" [apiName]=\"apiName()\"></rw-form-element>\r\n}\r\n" }]
3129
- }] });
3130
-
3131
- /**
3132
- * A form with Save, Reload and Delete buttons.
3133
- * If you do not want buttons, use RestWorldFormTemplateComponent <rw-form-template>.
3134
- * You can also provide your own buttons by passing in a template.
3135
- * @example
3136
- * <rw-form
3137
- * [template]="template"
3138
- * apiName="apiName"
3139
- * rel="rel"
3140
- * [allowSubmit]="true"
3141
- * [allowDelete]="true"
3142
- * [allowReload]="true"
3143
- * [showSubmit]="true"
3144
- * [showDelete]="true"
3145
- * [showReload]="true">
3146
- * <ng-template #content let-form="form" let-template="template" let-apiName="apiName">
3147
- * <!-- Custom form content here -->
3148
- * <!-- This is optional and will replace the default which renders labels and inputs if present -->
3149
- * </ng-template>
3150
- * <ng-template #buttons let-form="form" let-template="template" let-apiName="apiName">
3151
- * <!-- Custom buttons here -->
3152
- * <!-- This is optional and will replace the default which renders the Save, Reload and Delete buttons if present -->
3153
- * </ng-template>
3154
- * </rw-form>
3155
- */
3156
- class RestWorldFormComponent {
3157
- _clients;
3158
- _confirmationService;
3159
- _messageService;
3160
- _formService;
3161
- _elementRef;
3162
- _problemService;
3359
+ cellStyleClass = input(() => "", ...(ngDevMode ? [{ debugName: "cellStyleClass" }] : []));
3360
+ cellStyleClasses = computed(() => this.rows().map((r, ri) => Object.fromEntries(this.columns().map((c, ci) => [c.name, this.cellStyleClass()(r, c, ri, ci)]))), ...(ngDevMode ? [{ debugName: "cellStyleClasses" }] : []));
3361
+ columns = computed(() => this.searchTemplate()?.properties.filter(p => p.type !== PropertyType.Hidden) ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : []));
3362
+ contextMenu = viewChild("contextMenu", ...(ngDevMode ? [{ debugName: "contextMenu" }] : []));
3363
+ primeNgTable = viewChild.required("table");
3364
+ contextMenuItems = signal([], ...(ngDevMode ? [{ debugName: "contextMenuItems" }] : []));
3365
+ dateFormat = new Date(3333, 10, 22) // months start at 0 in JS
3366
+ .toLocaleDateString()
3367
+ .replace("22", "dd")
3368
+ .replace("11", "MM")
3369
+ .replace("3333", "yy")
3370
+ .replace("33", "y");
3371
+ editProperties = computed(() => this.editTemplate()?.propertiesRecord ?? {}, ...(ngDevMode ? [{ debugName: "editProperties" }] : []));
3163
3372
  /**
3164
- * Emitted after the resource has been deleted.
3373
+ * The template that is used to edit the items.
3374
+ * Bind this to the template that is used to edit the items.
3375
+ * Normally this is returned from the backend as part of the hal-forms resource from a list endpoint.
3376
+ * For the editing capability, you must also set the apiName and the formArray.
3165
3377
  */
3166
- afterDelete = output();
3378
+ editTemplate = input(...(ngDevMode ? [undefined, { debugName: "editTemplate" }] : []));
3379
+ filters = computed(() => {
3380
+ const filter = this.oDataParameters().$filter;
3381
+ const properties = this.searchTemplate()?.propertiesRecord;
3382
+ if (filter === null || filter === undefined || typeof filter !== "string" || properties === undefined)
3383
+ return {};
3384
+ return ODataService.createFilterMetadataFromODataFilter(filter, properties);
3385
+ }, ...(ngDevMode ? [{ debugName: "filters" }] : []));
3167
3386
  /**
3168
- * Emitted after the form has been submitted.
3387
+ * The form array that contains the form groups for the items.
3388
+ * Bind this to the form array that contains the form groups for the items.
3389
+ * Each entry in the array represents one row in the currently displayed page of the table.
3390
+ * For the editing capability, you must also set the apiName and the editTemplate.
3169
3391
  */
3170
- afterSubmit = output();
3171
- /**
3172
- * Determines whether to enable the delete button.
3173
- */
3174
- allowDelete = input(true, ...(ngDevMode ? [{ debugName: "allowDelete" }] : []));
3175
- /**
3176
- * Determines whether to enable the reload button.
3177
- */
3178
- allowReload = input(true, ...(ngDevMode ? [{ debugName: "allowReload" }] : []));
3179
- /**
3180
- * Determines whether to enable the submit button.
3181
- */
3182
- allowSubmit = input(true, ...(ngDevMode ? [{ debugName: "allowSubmit" }] : []));
3392
+ formArray = computed(() => this._controlContainer?.control, ...(ngDevMode ? [{ debugName: "formArray" }] : []));
3183
3393
  /**
3184
- * The name of the API to use.
3394
+ * An optional menu that is displayed at the top right of the table.
3395
+ * @see RestWorldMenuButtonComponent
3185
3396
  */
3186
- apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
3397
+ headerMenu = input([], ...(ngDevMode ? [{ debugName: "headerMenu" }] : []));
3398
+ isEditable = computed(() => this.editTemplate() !== undefined && this.formArray() !== undefined && this.apiName() !== undefined, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
3187
3399
  /**
3188
- * Determines whether the resource can be deleted.
3400
+ * Indicates whether the table is currently loading.
3401
+ * Set this to true while loading new items from the backend when reacting to the `onFilterOrSortChanged` event.
3189
3402
  */
3190
- canDelete = computed(() => this.allowDelete() &&
3191
- this.template() !== undefined &&
3192
- this.template().target !== undefined &&
3193
- this.template().method == "PUT" &&
3194
- this.formGroup() !== undefined &&
3195
- this.formGroup()?.value.id !== undefined &&
3196
- this.formGroup()?.value.timestamp !== undefined &&
3197
- !this.isLoading(), ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
3403
+ isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
3198
3404
  /**
3199
- * Determines whether the form can be reloaded.
3405
+ * Indicates whether the table is lazy loaded.
3406
+ * If set to true, sorting and filtering needs to be handled by the `load` event.
3407
+ * If set to false, sorting and filtering is handled by the table component itself.
3408
+ * The default is `true`.
3409
+ * @see load
3200
3410
  */
3201
- canReload = computed(() => this.allowReload() &&
3202
- this.template() !== undefined &&
3203
- this.template().target !== undefined &&
3204
- this.template().title !== undefined &&
3205
- this.template().properties.some(p => p.name === "id" && p.value !== undefined && p.value !== null && p.value !== 0) &&
3206
- !this.isLoading(), ...(ngDevMode ? [{ debugName: "canReload" }] : []));
3411
+ lazy = input(true, ...(ngDevMode ? [{ debugName: "lazy" }] : []));
3207
3412
  /**
3208
- * Determines whether the form can be submitted.
3413
+ * Indicates whether the table has a paginator.
3414
+ * If set to true, the table will display a paginator at the bottom.
3415
+ * If set to false, the table will not display a paginator and all rows will be displayed at once.
3416
+ * The default is `true`.
3417
+ * In order to customize the number of rows per page, you can set the `rowsPerPageOptions` property.
3418
+ * @see rowsPerPageOptions
3209
3419
  */
3210
- canSubmit = computed(() => this.allowSubmit() &&
3211
- this.template() !== undefined &&
3212
- this.template().target !== undefined &&
3213
- !this.isLoading() &&
3214
- this.formGroup() !== undefined, ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
3420
+ paginator = input(true, ...(ngDevMode ? [{ debugName: "paginator" }] : []));
3421
+ multiSortMeta = computed(() => {
3422
+ const orderBy = this.oDataParameters().$orderby;
3423
+ if (orderBy === null || orderBy === undefined || typeof orderBy !== "string")
3424
+ return undefined;
3425
+ return orderBy
3426
+ .split(",")
3427
+ .map(o => o.trim())
3428
+ .filter(o => o !== "")
3429
+ .map(o => {
3430
+ const [field, order] = o.split(" ");
3431
+ const orderAsNumber = order?.toLowerCase() === "desc" ? -1 : 1;
3432
+ return { field: field, order: orderAsNumber };
3433
+ });
3434
+ }, ...(ngDevMode ? [{ debugName: "multiSortMeta" }] : []));
3435
+ oDataParameters = model({}, ...(ngDevMode ? [{ debugName: "oDataParameters" }] : []));
3436
+ reflectParametersInUrl = input(true, ...(ngDevMode ? [{ debugName: "reflectParametersInUrl" }] : []));
3215
3437
  /**
3216
- * The form group that represents the form.
3438
+ * Indicates whether the table rows are highlighted when the mouse hovers over them.
3217
3439
  */
3218
- formGroup = computed(() => this._formService.createFormGroupFromTemplate(this.template()), ...(ngDevMode ? [{ debugName: "formGroup" }] : []));
3219
- isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
3440
+ rowHover = input(false, ...(ngDevMode ? [{ debugName: "rowHover" }] : []));
3220
3441
  /**
3221
- * The rel of the form.
3442
+ * A function that returns the menu for a row.
3443
+ * Based on the openedByRightClick parameter, the function can return different menus.
3444
+ * The menu when it has not been opened by a right click is displayed in an extra column to the right of the table if `showRowMenuAsColumn` is `true`.
3445
+ * The menu when it has been opened by a right click is displayed as a context menu if `showRowMenuOnRightClick` is `true`.
3446
+ * @param row The row for which to return the menu.
3447
+ * @param openedByRightClick Indicates whether the menu was opened by a right click.
3448
+ * @returns The menu for the row.
3449
+ * @see showRowMenuAsColumn
3450
+ * @see showRowMenuOnRightClick
3451
+ */
3452
+ rowMenu = input(() => [], ...(ngDevMode ? [{ debugName: "rowMenu" }] : []));
3453
+ rowMenus = computed(() => {
3454
+ return this.showRowMenuAsColumn() ? this.rows().map(r => this.rowMenu()(r, false)) : [];
3455
+ }, ...(ngDevMode ? [{ debugName: "rowMenus" }] : []));
3456
+ /**
3457
+ * A function that returns the style class for a row.
3458
+ * @param row The row for which to return the style class.
3459
+ * @param rowIndex The index of the row on the currently displayed page.
3460
+ * @returns The style class for the row.
3222
3461
  */
3223
- rel = input.required(...(ngDevMode ? [{ debugName: "rel" }] : []));
3462
+ rowStyleClass = input(() => "", ...(ngDevMode ? [{ debugName: "rowStyleClass" }] : []));
3463
+ rowStyleClasses = computed(() => this.rows().map((r, i) => this.rowStyleClass()(r, i)), ...(ngDevMode ? [{ debugName: "rowStyleClasses" }] : []));
3224
3464
  /**
3225
- * Determines whether to show the delete button.
3226
- */
3227
- showDelete = input(true, ...(ngDevMode ? [{ debugName: "showDelete" }] : []));
3465
+ * The items that are displayed as table rows.
3466
+ * Bind this to the items that are displayed as table rows.
3467
+ * Normally this is returned from the backend as part of the hal-forms resource from a list endpoint.
3468
+ */
3469
+ rows = input.required(...(ngDevMode ? [{ debugName: "rows" }] : []));
3470
+ rowsBeforeCurrentPage = computed(() => this.oDataParameters().$skip ?? 0, ...(ngDevMode ? [{ debugName: "rowsBeforeCurrentPage" }] : []));
3228
3471
  /**
3229
- * Determines whether to show the reload button.
3230
- */
3231
- showReload = input(true, ...(ngDevMode ? [{ debugName: "showReload" }] : []));
3472
+ * The number of rows per page.
3473
+ * The default is the first element of rowsPerPageOptions.
3474
+ */
3475
+ rowsPerPage = computed(() => this.oDataParameters().$top ?? this.rowsPerPageOptions()[0], ...(ngDevMode ? [{ debugName: "rowsPerPage" }] : []));
3232
3476
  /**
3233
- * Determines whether to show the submit button.
3234
- */
3235
- showSubmit = input(true, ...(ngDevMode ? [{ debugName: "showSubmit" }] : []));
3477
+ * The possible values for the number of rows per page.
3478
+ * The default is [10, 25, 50].
3479
+ */
3480
+ rowsPerPageOptions = input([10, 25, 50], ...(ngDevMode ? [{ debugName: "rowsPerPageOptions" }] : []));
3236
3481
  /**
3237
- * The template used to render the form.
3482
+ * The height of the scrollable table.
3483
+ * The default is "flex".
3238
3484
  */
3239
- template = model.required(...(ngDevMode ? [{ debugName: "template" }] : []));
3485
+ scrollHeight = input("flex", ...(ngDevMode ? [{ debugName: "scrollHeight" }] : []));
3240
3486
  /**
3241
- * Emitted when the form value changes.
3487
+ * Indicates whether the table is scrollable.
3488
+ * The default is `true`.
3242
3489
  */
3243
- valueChanges = output();
3490
+ scrollable = input(true, ...(ngDevMode ? [{ debugName: "scrollable" }] : []));
3244
3491
  /**
3245
- * A reference to a template that can be used to render custom buttons for the form.
3246
- */
3247
- buttonsRef = contentChild('buttons', ...(ngDevMode ? [{ debugName: "buttonsRef" }] : []));
3492
+ * The template that is used to display the table columns and to filter and sort the items.
3493
+ * Bind this to the template that is used to display the table columns and to filter and sort the items.
3494
+ * Normally this is returned from the backend as part of the hal-forms resource from a list endpoint.
3495
+ */
3496
+ searchTemplate = input.required(...(ngDevMode ? [{ debugName: "searchTemplate" }] : []));
3248
3497
  /**
3249
- * A reference to a template that can be used to render content before the default buttons.
3250
- */
3251
- beforeButtonsRef = contentChild('beforeButtons', ...(ngDevMode ? [{ debugName: "beforeButtonsRef" }] : []));
3498
+ * The currently selected rows.
3499
+ */
3500
+ selectedRows = model([], ...(ngDevMode ? [{ debugName: "selectedRows" }] : []));
3252
3501
  /**
3253
- * A reference to a template that can be used to render content after the default buttons.
3254
- */
3255
- afterButtonsRef = contentChild('afterButtons', ...(ngDevMode ? [{ debugName: "afterButtonsRef" }] : []));
3502
+ * The mode how rows can be selected.
3503
+ * The default is `null` which means rows cannot be selected.
3504
+ */
3505
+ selectionMode = input(null, ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
3506
+ showMenuColumn = computed(() => this.headerMenu().length > 0 || (this.showRowMenuAsColumn() && this.rowMenus().some(m => m.length > 0)), ...(ngDevMode ? [{ debugName: "showMenuColumn" }] : []));
3256
3507
  /**
3257
- * A reference to a template that can be used to render custom content inside the <form> element instead of the default form.
3258
- */
3259
- contentRef = contentChild('content', ...(ngDevMode ? [{ debugName: "contentRef" }] : []));
3260
- _client = computed(() => this._clients.getClient(this.apiName()), ...(ngDevMode ? [{ debugName: "_client" }] : []));
3261
- _formValueChangesSubscription;
3262
- constructor(_clients, _confirmationService, _messageService, _formService, _elementRef, _problemService) {
3263
- this._clients = _clients;
3264
- this._confirmationService = _confirmationService;
3265
- this._messageService = _messageService;
3508
+ * Indicates whether the row menu is displayed as a column to the right of the table.
3509
+ */
3510
+ showRowMenuAsColumn = input(true, ...(ngDevMode ? [{ debugName: "showRowMenuAsColumn" }] : []));
3511
+ /**
3512
+ * Indicates whether the row menu is displayed as a context menu when the user right clicks on a row.
3513
+ */
3514
+ showRowMenuOnRightClick = input(true, ...(ngDevMode ? [{ debugName: "showRowMenuOnRightClick" }] : []));
3515
+ /**
3516
+ * The style class for the table.
3517
+ * The default is "".
3518
+ */
3519
+ styleClass = input("", ...(ngDevMode ? [{ debugName: "styleClass" }] : []));
3520
+ /**
3521
+ * The inline style for the table.
3522
+ */
3523
+ tableStyle = input(...(ngDevMode ? [undefined, { debugName: "tableStyle" }] : []));
3524
+ totalRecords = input(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
3525
+ urlParameterPrefix = input("", ...(ngDevMode ? [{ debugName: "urlParameterPrefix" }] : []));
3526
+ // private _formArray?: FormArray<FormGroup<{ [K in keyof TListItem]: AbstractControl<unknown> }>>;
3527
+ _filterMatchModeOptions;
3528
+ timeFormat = new Date(1, 1, 1, 22, 33, 44)
3529
+ .toLocaleTimeString()
3530
+ .replace("22", "hh")
3531
+ .replace("33", "mm")
3532
+ .replace("44", "ss");
3533
+ _initialQueryParamsSet = false;
3534
+ _lastUsedFilters = {};
3535
+ constructor(_controlContainer, _formService, router, activatedRoute, primeNGConfig, filterService) {
3536
+ this._controlContainer = _controlContainer;
3266
3537
  this._formService = _formService;
3267
- this._elementRef = _elementRef;
3268
- this._problemService = _problemService;
3269
- // Update the form value changes subscription to always track the current form group.
3538
+ this._filterMatchModeOptions = {
3539
+ text: [TranslationKeys.NO_FILTER, ...primeNGConfig.filterMatchModeOptions.text].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
3540
+ numeric: [TranslationKeys.NO_FILTER, ...primeNGConfig.filterMatchModeOptions.numeric].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
3541
+ date: [TranslationKeys.NO_FILTER, ...primeNGConfig.filterMatchModeOptions.date].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
3542
+ boolean: [TranslationKeys.NO_FILTER, TranslationKeys.EQUALS, TranslationKeys.NOT_EQUALS].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
3543
+ enum: [TranslationKeys.NO_FILTER, TranslationKeys.EQUALS, TranslationKeys.NOT_EQUALS].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
3544
+ };
3545
+ filterService.register(TranslationKeys.NO_FILTER, () => true);
3546
+ // Update the form array on changes
3270
3547
  effect(() => {
3271
- this._formValueChangesSubscription?.unsubscribe();
3272
- const formGroup = this.formGroup();
3273
- this._formValueChangesSubscription = formGroup?.valueChanges.subscribe(newValue => this.valueChanges.emit(newValue));
3274
- this.valueChanges.emit(formGroup?.value);
3548
+ const formArray = this.formArray();
3549
+ const editTemplate = this.editTemplate();
3550
+ const rows = this.rows();
3551
+ const lazy = this.lazy();
3552
+ if (!this.isEditable() || !formArray || !editTemplate || !lazy)
3553
+ return;
3554
+ formArray.clear();
3555
+ const newControls = rows
3556
+ .map(r => {
3557
+ const formGroup = this._formService.createFormGroupFromTemplate(editTemplate);
3558
+ formGroup.patchValue(r);
3559
+ return formGroup;
3560
+ });
3561
+ for (const control of newControls)
3562
+ formArray.push(control);
3563
+ });
3564
+ // update the url when the oDataParameters change
3565
+ effect(async () => {
3566
+ if (!this.reflectParametersInUrl())
3567
+ return;
3568
+ const urlParameterPrefix = this.urlParameterPrefix();
3569
+ const oDataParameters = this.oDataParameters();
3570
+ // Set the initial query parameters on the first change
3571
+ if (!this._initialQueryParamsSet) {
3572
+ this._initialQueryParamsSet = true;
3573
+ const oDataParametersFromUrl = ODataService.createParametersFromRoute(activatedRoute, urlParameterPrefix);
3574
+ const mergedParameters = { ...oDataParameters, ...oDataParametersFromUrl };
3575
+ this.oDataParameters.set(mergedParameters);
3576
+ return;
3577
+ }
3578
+ // Update the query parameters in the url after the first change
3579
+ const parameters = this.prefixObjectProperties(oDataParameters, urlParameterPrefix);
3580
+ await router.navigate([], { queryParams: parameters, queryParamsHandling: 'merge' });
3275
3581
  });
3276
3582
  }
3277
- async delete() {
3278
- if (!this.canDelete())
3583
+ getFormGroupAtIndex(indexOnCurrentPage) {
3584
+ const formArray = this.formArray();
3585
+ if (!formArray)
3586
+ return undefined;
3587
+ const finalIndex = this.getAbsoluteIndex(indexOnCurrentPage);
3588
+ return formArray.at(finalIndex);
3589
+ }
3590
+ getAbsoluteIndex(indexOnCurrentPage) {
3591
+ const lazy = this.lazy();
3592
+ if (!lazy)
3593
+ return indexOnCurrentPage;
3594
+ const primeNgTable = this.primeNgTable();
3595
+ if (!primeNgTable)
3596
+ return indexOnCurrentPage;
3597
+ return indexOnCurrentPage - (primeNgTable.first ?? 0);
3598
+ }
3599
+ load(event) {
3600
+ this.fixUserFilterErrors(event.filters);
3601
+ // this.multiSortMeta = event.multiSortMeta;
3602
+ const currentParameters = this.oDataParameters();
3603
+ const searchTemplate = this.searchTemplate();
3604
+ if (!searchTemplate || searchTemplate.properties.length === 0)
3279
3605
  return;
3280
- const formGroup = this.formGroup();
3281
- if (formGroup === undefined)
3282
- throw new Error("formGroup cannot be undefined.");
3283
- const template = this.template();
3284
- if (template === undefined)
3285
- throw new Error("template cannot be undefined.");
3286
- // canDelete already checks that the timestamp is present, so the cast is safe.
3287
- const result = await this._client().deleteByTemplateAndForm(template, formGroup);
3288
- if (this._problemService.checkResponseAndDisplayErrors(result, formGroup)) {
3289
- this._messageService.add({ severity: 'success', summary: 'Deleted', detail: 'The resource has been deleted.' });
3290
- this.afterDelete.emit();
3291
- }
3606
+ const parameters = ODataService.createParametersFromTableLoadEvent(event, searchTemplate);
3607
+ ODataService.createFilterMetadataFromODataFilter(parameters.$filter, searchTemplate.propertiesRecord);
3608
+ if (currentParameters.$filter !== parameters.$filter || currentParameters.$orderby !== parameters.$orderby || currentParameters.$top !== parameters.$top || currentParameters.$skip !== parameters.$skip)
3609
+ this.oDataParameters.set(parameters);
3292
3610
  }
3293
- async reload() {
3294
- const canReload = this.canReload();
3295
- const template = this.template();
3296
- if (!canReload || template === undefined)
3611
+ openContextMenu(event, row) {
3612
+ const contextMenu = this.contextMenu();
3613
+ if (!this.showRowMenuOnRightClick() || contextMenu === undefined)
3297
3614
  return;
3298
- this.isLoading.set(true);
3299
- try {
3300
- const response = await this._client().getForm(template.target);
3301
- if (this._problemService.checkResponseAndDisplayErrors(response, this.formGroup())) {
3302
- this.template.set(response.body.getTemplateByTitle(template.title));
3303
- }
3304
- }
3305
- catch (e) {
3306
- this._messageService.add({ severity: 'error', summary: 'Error', detail: `An unknown error occurred. ${JSON.stringify(e)}`, sticky: true });
3307
- console.log(e);
3308
- }
3309
- this.isLoading.set(false);
3615
+ this.contextMenuItems.set(this.rowMenu()(row, true));
3616
+ contextMenu.show(event);
3617
+ event.stopPropagation();
3310
3618
  }
3311
- showDeleteConfirmatioModal() {
3312
- this._confirmationService.confirm({
3313
- message: 'Do you really want to delete this resource?',
3314
- header: 'Confirm delete',
3315
- icon: 'far fa-trash-alt',
3316
- accept: () => this.delete()
3317
- });
3619
+ showInputField(column) {
3620
+ if (!this.isEditable())
3621
+ return false;
3622
+ const editProperty = this.editProperties()[column.name];
3623
+ return editProperty !== undefined && editProperty.type !== PropertyType.Hidden && !editProperty.readOnly;
3318
3624
  }
3319
- async submit() {
3320
- const formGroup = this.formGroup();
3321
- const template = this.template();
3322
- if (formGroup !== undefined) {
3323
- formGroup.markAllAsTouched();
3324
- if (!formGroup.valid) {
3325
- this._messageService.add({
3326
- severity: 'error',
3327
- summary: 'Error',
3328
- detail: 'Please correct the errors before submitting.',
3329
- });
3330
- ProblemService.scrollToFirstValidationError(this._elementRef.nativeElement);
3331
- return;
3332
- }
3625
+ toColumnFilterType(property) {
3626
+ if (!property)
3627
+ return ColumnFilterType.text;
3628
+ const propertyType = property.type;
3629
+ switch (propertyType) {
3630
+ case PropertyType.Number:
3631
+ case PropertyType.Percent:
3632
+ case PropertyType.Currency:
3633
+ case PropertyType.Month:
3634
+ return ColumnFilterType.numeric;
3635
+ case PropertyType.Bool:
3636
+ return ColumnFilterType.boolean;
3637
+ case PropertyType.Date:
3638
+ case PropertyType.DatetimeLocal:
3639
+ case PropertyType.DatetimeOffset:
3640
+ return ColumnFilterType.date;
3641
+ default:
3642
+ return property.options ? property.options.link ? ColumnFilterType.numeric : ColumnFilterType.enum : ColumnFilterType.text;
3333
3643
  }
3334
- if (!this.canSubmit() || formGroup === undefined || template === undefined)
3644
+ }
3645
+ toMatchModeOptions(property) {
3646
+ const columnFilterType = this.toColumnFilterType(property);
3647
+ return this._filterMatchModeOptions[columnFilterType];
3648
+ }
3649
+ toMaxFractionDigits(property) {
3650
+ switch (property.type) {
3651
+ case PropertyType.Number:
3652
+ case PropertyType.Percent:
3653
+ case PropertyType.Currency:
3654
+ return property.step?.toString().split(".")[1]?.length ?? 2;
3655
+ case PropertyType.Month:
3656
+ return 0;
3657
+ default:
3658
+ return undefined;
3659
+ }
3660
+ }
3661
+ fixUserFilterError(filterEntry, lastFilterEntry, propertyName) {
3662
+ if (!filterEntry)
3335
3663
  return;
3336
- this.isLoading.set(true);
3337
- try {
3338
- const response = await this._client().submit(template, formGroup.value);
3339
- if (!this._problemService.checkResponseAndDisplayErrors(response, formGroup, "Error while saving the resource")) {
3340
- }
3341
- else if (response.status == 201) {
3342
- if (!response.headers.has('Location')) {
3343
- this._messageService.add({ severity: 'error', summary: 'Error', detail: 'The server returned a 201 Created response, but did not return a Location header.', data: response, sticky: true });
3344
- return;
3345
- }
3346
- this._messageService.add({ severity: 'success', summary: 'Created', detail: 'The resource has been created.' });
3347
- var createdAtUri = response.headers.get('Location');
3348
- this.afterSubmit.emit({ location: createdAtUri, status: 201 });
3664
+ if (lastFilterEntry !== undefined &&
3665
+ lastFilterEntry.matchMode !== TranslationKeys.NO_FILTER &&
3666
+ filterEntry.matchMode === TranslationKeys.NO_FILTER) {
3667
+ // The user changed the mode from something to no filter
3668
+ // => We reset the value
3669
+ filterEntry.value = null;
3670
+ }
3671
+ else if (filterEntry.matchMode === TranslationKeys.NO_FILTER &&
3672
+ (lastFilterEntry === undefined || lastFilterEntry.value === null) &&
3673
+ filterEntry.value !== null) {
3674
+ // The user entered a value into the filter, but forgot to change the mode
3675
+ // => We set the match mode to the default for the type that is not no filter
3676
+ filterEntry.matchMode = this._filterMatchModeOptions[this.toColumnFilterType(this.searchTemplate().propertiesRecord[propertyName])][1].value;
3677
+ }
3678
+ }
3679
+ fixUserFilterErrors(filters) {
3680
+ if (!filters)
3681
+ return;
3682
+ Object.entries(filters).forEach(([propertyName, filter]) => {
3683
+ const lastFilter = this._lastUsedFilters[propertyName];
3684
+ if (Array.isArray(filter)) {
3685
+ filter.forEach((filterEntry, index) => this.fixUserFilterError(filterEntry, Array.isArray(lastFilter) ? lastFilter[index] : lastFilter, propertyName));
3349
3686
  }
3350
3687
  else {
3351
- const responseResource = response.body;
3352
- const newTemplate = responseResource.getTemplateByTitle(template.title);
3353
- this.template.set(newTemplate);
3354
- this._messageService.add({ severity: 'success', summary: 'Saved', detail: 'The resource has been saved.' });
3355
- this.afterSubmit.emit({ old: template, new: newTemplate, status: 200 });
3688
+ this.fixUserFilterError(filter, Array.isArray(lastFilter) ? lastFilter[0] : lastFilter, propertyName);
3356
3689
  }
3357
- }
3358
- catch (e) {
3359
- this._messageService.add({ severity: 'error', summary: 'Error', detail: `An unknown error occurred. ${JSON.stringify(e)}`, sticky: true });
3360
- console.log(e);
3361
- }
3362
- this.isLoading.set(false);
3690
+ });
3691
+ this._lastUsedFilters = JSON.parse(JSON.stringify(filters));
3363
3692
  }
3364
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.ConfirmationService }, { token: i2$1.MessageService }, { token: i1$1.FormService }, { token: i0.ElementRef }, { token: ProblemService }], target: i0.ɵɵFactoryTarget.Component });
3365
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldFormComponent, isStandalone: true, selector: "rw-form", inputs: { allowDelete: { classPropertyName: "allowDelete", publicName: "allowDelete", isSignal: true, isRequired: false, transformFunction: null }, allowReload: { classPropertyName: "allowReload", publicName: "allowReload", isSignal: true, isRequired: false, transformFunction: null }, allowSubmit: { classPropertyName: "allowSubmit", publicName: "allowSubmit", isSignal: true, isRequired: false, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: true, transformFunction: null }, showDelete: { classPropertyName: "showDelete", publicName: "showDelete", isSignal: true, isRequired: false, transformFunction: null }, showReload: { classPropertyName: "showReload", publicName: "showReload", isSignal: true, isRequired: false, transformFunction: null }, showSubmit: { classPropertyName: "showSubmit", publicName: "showSubmit", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { afterDelete: "afterDelete", afterSubmit: "afterSubmit", template: "templateChange", valueChanges: "valueChanges" }, queries: [{ propertyName: "buttonsRef", first: true, predicate: ["buttons"], descendants: true, isSignal: true }, { propertyName: "beforeButtonsRef", first: true, predicate: ["beforeButtons"], descendants: true, isSignal: true }, { propertyName: "afterButtonsRef", first: true, predicate: ["afterButtons"], descendants: true, isSignal: true }, { propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (formGroup() !== undefined && template() !== undefined) {\n <form [formGroup]=\"formGroup()\" (ngSubmit)=\"submit()\">\n <div class=\"blockable-container\">\n <div class=\"blockable-element\">\n <div class=\"grid field\">\n <div class=\"col-12 md:col-10 md:col-offset-2\">\n <rw-validation-errors [form]=\"formGroup()\"></rw-validation-errors>\n </div>\n </div>\n <ng-template #defaultContent>\n <rw-input-template [template]=\"template()\" [apiName]=\"apiName()\"></rw-input-template>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"contentRef() ?? defaultContent; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n @if (isLoading()) {\n <div class=\"blockable-overlay\">\n <p-progressSpinner></p-progressSpinner>\n </div>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"col\">\n <div class=\"flex justify-content-end w-full\">\n <ng-template #defaultButtons>\n @if (beforeButtonsRef(); as beforeButtons) {\n <ng-container\n *ngTemplateOutlet=\"beforeButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n <button pButton pRipple type=\"submit\" label=\"Save\" icon=\"far fa-save\" class=\"mx-2 p-button-success\"\n [disabled]=\"!allowSubmit()\"></button>\n <button pButton pRipple type=\"button\" label=\"Reload\" icon=\"fas fa-redo\" class=\"mx-2 p-button-info\"\n (click)=\"reload()\" [disabled]=\"!canReload()\"></button>\n <button pButton pRipple type=\"button\" label=\"Delete\" icon=\"far fa-trash-alt\"\n class=\"ml-2 p-button-danger\" (click)=\"showDeleteConfirmatioModal()\"\n [disabled]=\"!canDelete()\"></button>\n @if (afterButtonsRef(); as afterButtons) {\n <ng-container\n *ngTemplateOutlet=\"afterButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"buttonsRef() ?? defaultButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n </div>\n </div>\n </form>\n}\n", styles: [".blockable-container{display:grid;place-items:center;grid-template-areas:\"inner\"}.blockable-element{grid-area:inner;width:100%}.blockable-overlay{grid-area:inner;height:100%;width:100%;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:1}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: RestWorldValidationErrorsComponent, selector: "rw-validation-errors", inputs: ["form", "property"] }, { kind: "component", type: RestWorldInputTemplateComponent, selector: "rw-input-template", inputs: ["apiName", "template"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i6.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i8.Ripple, selector: "[pRipple]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
3693
+ prefixObjectProperties(obj, prefix) {
3694
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [`${prefix}${key}`, value]));
3695
+ }
3696
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableComponent, deps: [{ token: i2$3.ControlContainer }, { token: i1$1.FormService }, { token: i3$3.Router }, { token: i3$3.ActivatedRoute }, { token: i4$2.PrimeNG }, { token: i2$1.FilterService }], target: i0.ɵɵFactoryTarget.Component });
3697
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldTableComponent, isStandalone: true, selector: "rw-table", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, cellStyleClass: { classPropertyName: "cellStyleClass", publicName: "cellStyleClass", isSignal: true, isRequired: false, transformFunction: null }, editTemplate: { classPropertyName: "editTemplate", publicName: "editTemplate", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, paginator: { classPropertyName: "paginator", publicName: "paginator", isSignal: true, isRequired: false, transformFunction: null }, oDataParameters: { classPropertyName: "oDataParameters", publicName: "oDataParameters", isSignal: true, isRequired: false, transformFunction: null }, reflectParametersInUrl: { classPropertyName: "reflectParametersInUrl", publicName: "reflectParametersInUrl", isSignal: true, isRequired: false, transformFunction: null }, rowHover: { classPropertyName: "rowHover", publicName: "rowHover", isSignal: true, isRequired: false, transformFunction: null }, rowMenu: { classPropertyName: "rowMenu", publicName: "rowMenu", isSignal: true, isRequired: false, transformFunction: null }, rowStyleClass: { classPropertyName: "rowStyleClass", publicName: "rowStyleClass", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, searchTemplate: { classPropertyName: "searchTemplate", publicName: "searchTemplate", isSignal: true, isRequired: true, transformFunction: null }, selectedRows: { classPropertyName: "selectedRows", publicName: "selectedRows", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, showRowMenuAsColumn: { classPropertyName: "showRowMenuAsColumn", publicName: "showRowMenuAsColumn", isSignal: true, isRequired: false, transformFunction: null }, showRowMenuOnRightClick: { classPropertyName: "showRowMenuOnRightClick", publicName: "showRowMenuOnRightClick", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, tableStyle: { classPropertyName: "tableStyle", publicName: "tableStyle", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, urlParameterPrefix: { classPropertyName: "urlParameterPrefix", publicName: "urlParameterPrefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { oDataParameters: "oDataParametersChange", selectedRows: "selectedRowsChange" }, viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true, isSignal: true }, { propertyName: "primeNgTable", first: true, predicate: ["table"], descendants: true, isSignal: true }], ngImport: i0, template: "<p-table\r\n #table\r\n [value]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [lazy]=\"lazy()\"\r\n [lazyLoadOnInit]=\"false\"\r\n (onLazyLoad)=\"load($event)\"\r\n [paginator]=\"paginator()\"\r\n [rowsPerPageOptions]=\"rowsPerPageOptions()\"\r\n [rows]=\"rowsPerPage()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [loading]=\"isLoading()\"\r\n sortMode=\"multiple\"\r\n [multiSortMeta]=\"multiSortMeta()\"\r\n [class]=\"styleClass()\"\r\n [tableStyle]=\"tableStyle()\"\r\n [scrollable]=\"scrollable()\"\r\n [scrollHeight]=\"scrollHeight()\"\r\n [selectionMode]=\"selectionMode()\"\r\n [rowHover]=\"rowHover()\"\r\n [(selection)]=\"selectedRows\"\r\n [filters]=\"$any(filters())\"\r\n [first]=\"rowsBeforeCurrentPage()\"\r\n (onSort)=\"onSort($event)\"\r\n >\r\n\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @for (col of columns; track col.name) {\r\n <th [pSortableColumn]=\"col.name\">\r\n <div class=\"p-d-flex p-jc-between p-ai-center gap-1\">\r\n {{col.prompt}}\r\n @if(!col.readOnly) {\r\n <p-sortIcon [field]=\"col.name\"></p-sortIcon>\r\n @if (lazy()) {\r\n <p-columnFilter #f\r\n [type]=\"toColumnFilterType(col.type)\"\r\n [maxFractionDigits]=\"toMaxFractionDigits(col)\"\r\n matchMode=\"noFilter\"\r\n [matchModeOptions]=\"toMatchModeOptions(col)\"\r\n [showMatchModes]=\"true\"\r\n [field]=\"col.name\"\r\n display=\"menu\"\r\n [maxConstraints]=\"100\"\r\n [class.has-filter]=\"f.hasFilter\">\r\n <ng-template #filter let-value let-filterConstraint=\"filterConstraint\" let-field=\"field\">\r\n <rw-table-column-filter-element [property]=\"col\" [value]=\"value\" [apiName]=\"apiName()\" [filterConstraint]=\"filterConstraint\"></rw-table-column-filter-element>\r\n </ng-template>\r\n </p-columnFilter>\r\n }\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (showMenuColumn()) {\r\n <th>\r\n <rw-menu-button [items]=\"headerMenu()\"></rw-menu-button>\r\n </th>\r\n }\r\n </tr>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"body\" let-entity let-columns=\"columns\" let-rowIndex=\"rowIndex\">\r\n <tr (contextmenu)=\"openContextMenu($event, entity)\" [pSelectableRow]=\"entity\" [pSelectableRowDisabled]=\"selectionMode() === null\" [className]=\"rowStyleClasses()[getAbsoluteIndex(rowIndex)]\">\r\n @for (col of columns; track col.name) {\r\n <td [className]=\"cellStyleClasses()[getAbsoluteIndex(rowIndex)][col.name]\">\r\n @let fomrArray = formArray();\r\n @if (showInputField(col) && fomrArray) {\r\n @let formControl = getFormGroupAtIndex(rowIndex);\r\n @if (formControl) {\r\n <ng-container [formGroup]=\"formControl\" >\r\n <rw-input [apiName]=\"apiName()\" [property]=\"editProperties()[col.name]!\"></rw-input>\r\n </ng-container>\r\n }\r\n }\r\n @else {\r\n <rw-display [property]=\"col\" [value]=\"entity[col.name]\" [apiName]=\"apiName()!\"></rw-display>\r\n }\r\n </td>\r\n }\r\n @if (showMenuColumn()) {\r\n <td>\r\n @if (showRowMenuAsColumn()) {\r\n <rw-menu-button [items]=\"rowMenus()[getAbsoluteIndex(rowIndex)]\"></rw-menu-button>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n </ng-template>\r\n</p-table>\r\n\r\n<p-contextMenu #contextMenu appendTo=\"body\" [model]=\"contextMenuItems()\"></p-contextMenu>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}::ng-deep rw-table rw-label.md\\:col-2{width:100%;font-weight:600}::ng-deep rw-table .p-d-flex{display:flex}::ng-deep rw-table .p-ai-center{align-items:center}::ng-deep rw-table .has-filter filtericon{color:var(--p-datatable-header-cell-selected-color)}::ng-deep rw-table p-columnfilter .p-datatable-column-filter-button{width:unset;height:23px!important;padding-block-end:0;padding-block-start:0}\n"], dependencies: [{ kind: "ngmodule", type: TableModule }, { kind: "component", type: i6.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i6.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i6.SelectableRow, selector: "[pSelectableRow]", inputs: ["pSelectableRow", "pSelectableRowIndex", "pSelectableRowDisabled"] }, { kind: "component", type: i6.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i6.ColumnFilter, selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps"], outputs: ["onShow", "onHide"] }, { kind: "component", type: RestWorldMenuButtonComponent, selector: "rw-menu-button", inputs: ["items"] }, { kind: "component", type: RestWorldInputComponent, selector: "rw-input" }, { kind: "component", type: RestWorldDisplayComponent, selector: "rw-display", inputs: ["apiName", "property", "value"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i7.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo"], outputs: ["onShow", "onHide"] }, { kind: "component", type: RestWorldTableColumnFilterElementComponent, selector: "rw-table-column-filter-element", inputs: ["filterConstraint", "property", "apiName", "value"] }], viewProviders: [{
3698
+ provide: ControlContainer,
3699
+ deps: [[Optional, FormArrayName]],
3700
+ useFactory: (arrayName) => arrayName,
3701
+ }] });
3366
3702
  }
3367
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormComponent, decorators: [{
3703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableComponent, decorators: [{
3368
3704
  type: Component,
3369
- args: [{ selector: 'rw-form', standalone: true, imports: [ReactiveFormsModule, RestWorldValidationErrorsComponent, RestWorldInputTemplateComponent, ProgressSpinnerModule, ButtonModule, RippleModule, NgTemplateOutlet], template: "@if (formGroup() !== undefined && template() !== undefined) {\n <form [formGroup]=\"formGroup()\" (ngSubmit)=\"submit()\">\n <div class=\"blockable-container\">\n <div class=\"blockable-element\">\n <div class=\"grid field\">\n <div class=\"col-12 md:col-10 md:col-offset-2\">\n <rw-validation-errors [form]=\"formGroup()\"></rw-validation-errors>\n </div>\n </div>\n <ng-template #defaultContent>\n <rw-input-template [template]=\"template()\" [apiName]=\"apiName()\"></rw-input-template>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"contentRef() ?? defaultContent; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n @if (isLoading()) {\n <div class=\"blockable-overlay\">\n <p-progressSpinner></p-progressSpinner>\n </div>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"col\">\n <div class=\"flex justify-content-end w-full\">\n <ng-template #defaultButtons>\n @if (beforeButtonsRef(); as beforeButtons) {\n <ng-container\n *ngTemplateOutlet=\"beforeButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n <button pButton pRipple type=\"submit\" label=\"Save\" icon=\"far fa-save\" class=\"mx-2 p-button-success\"\n [disabled]=\"!allowSubmit()\"></button>\n <button pButton pRipple type=\"button\" label=\"Reload\" icon=\"fas fa-redo\" class=\"mx-2 p-button-info\"\n (click)=\"reload()\" [disabled]=\"!canReload()\"></button>\n <button pButton pRipple type=\"button\" label=\"Delete\" icon=\"far fa-trash-alt\"\n class=\"ml-2 p-button-danger\" (click)=\"showDeleteConfirmatioModal()\"\n [disabled]=\"!canDelete()\"></button>\n @if (afterButtonsRef(); as afterButtons) {\n <ng-container\n *ngTemplateOutlet=\"afterButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"buttonsRef() ?? defaultButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n </div>\n </div>\n </form>\n}\n", styles: [".blockable-container{display:grid;place-items:center;grid-template-areas:\"inner\"}.blockable-element{grid-area:inner;width:100%}.blockable-overlay{grid-area:inner;height:100%;width:100%;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:1}\n"] }]
3370
- }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.ConfirmationService }, { type: i2$1.MessageService }, { type: i1$1.FormService }, { type: i0.ElementRef }, { type: ProblemService }] });
3705
+ args: [{ selector: 'rw-table', standalone: true, viewProviders: [{
3706
+ provide: ControlContainer,
3707
+ deps: [[Optional, FormArrayName]],
3708
+ useFactory: (arrayName) => arrayName,
3709
+ }], imports: [TableModule, RestWorldMenuButtonComponent, RestWorldInputComponent, RestWorldDisplayComponent, ReactiveFormsModule, ContextMenuModule, RestWorldTableColumnFilterElementComponent, RestWorldTableColumnFilterElementComponent], template: "<p-table\r\n #table\r\n [value]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [lazy]=\"lazy()\"\r\n [lazyLoadOnInit]=\"false\"\r\n (onLazyLoad)=\"load($event)\"\r\n [paginator]=\"paginator()\"\r\n [rowsPerPageOptions]=\"rowsPerPageOptions()\"\r\n [rows]=\"rowsPerPage()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [loading]=\"isLoading()\"\r\n sortMode=\"multiple\"\r\n [multiSortMeta]=\"multiSortMeta()\"\r\n [class]=\"styleClass()\"\r\n [tableStyle]=\"tableStyle()\"\r\n [scrollable]=\"scrollable()\"\r\n [scrollHeight]=\"scrollHeight()\"\r\n [selectionMode]=\"selectionMode()\"\r\n [rowHover]=\"rowHover()\"\r\n [(selection)]=\"selectedRows\"\r\n [filters]=\"$any(filters())\"\r\n [first]=\"rowsBeforeCurrentPage()\"\r\n (onSort)=\"onSort($event)\"\r\n >\r\n\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @for (col of columns; track col.name) {\r\n <th [pSortableColumn]=\"col.name\">\r\n <div class=\"p-d-flex p-jc-between p-ai-center gap-1\">\r\n {{col.prompt}}\r\n @if(!col.readOnly) {\r\n <p-sortIcon [field]=\"col.name\"></p-sortIcon>\r\n @if (lazy()) {\r\n <p-columnFilter #f\r\n [type]=\"toColumnFilterType(col.type)\"\r\n [maxFractionDigits]=\"toMaxFractionDigits(col)\"\r\n matchMode=\"noFilter\"\r\n [matchModeOptions]=\"toMatchModeOptions(col)\"\r\n [showMatchModes]=\"true\"\r\n [field]=\"col.name\"\r\n display=\"menu\"\r\n [maxConstraints]=\"100\"\r\n [class.has-filter]=\"f.hasFilter\">\r\n <ng-template #filter let-value let-filterConstraint=\"filterConstraint\" let-field=\"field\">\r\n <rw-table-column-filter-element [property]=\"col\" [value]=\"value\" [apiName]=\"apiName()\" [filterConstraint]=\"filterConstraint\"></rw-table-column-filter-element>\r\n </ng-template>\r\n </p-columnFilter>\r\n }\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (showMenuColumn()) {\r\n <th>\r\n <rw-menu-button [items]=\"headerMenu()\"></rw-menu-button>\r\n </th>\r\n }\r\n </tr>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"body\" let-entity let-columns=\"columns\" let-rowIndex=\"rowIndex\">\r\n <tr (contextmenu)=\"openContextMenu($event, entity)\" [pSelectableRow]=\"entity\" [pSelectableRowDisabled]=\"selectionMode() === null\" [className]=\"rowStyleClasses()[getAbsoluteIndex(rowIndex)]\">\r\n @for (col of columns; track col.name) {\r\n <td [className]=\"cellStyleClasses()[getAbsoluteIndex(rowIndex)][col.name]\">\r\n @let fomrArray = formArray();\r\n @if (showInputField(col) && fomrArray) {\r\n @let formControl = getFormGroupAtIndex(rowIndex);\r\n @if (formControl) {\r\n <ng-container [formGroup]=\"formControl\" >\r\n <rw-input [apiName]=\"apiName()\" [property]=\"editProperties()[col.name]!\"></rw-input>\r\n </ng-container>\r\n }\r\n }\r\n @else {\r\n <rw-display [property]=\"col\" [value]=\"entity[col.name]\" [apiName]=\"apiName()!\"></rw-display>\r\n }\r\n </td>\r\n }\r\n @if (showMenuColumn()) {\r\n <td>\r\n @if (showRowMenuAsColumn()) {\r\n <rw-menu-button [items]=\"rowMenus()[getAbsoluteIndex(rowIndex)]\"></rw-menu-button>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n </ng-template>\r\n</p-table>\r\n\r\n<p-contextMenu #contextMenu appendTo=\"body\" [model]=\"contextMenuItems()\"></p-contextMenu>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}::ng-deep rw-table rw-label.md\\:col-2{width:100%;font-weight:600}::ng-deep rw-table .p-d-flex{display:flex}::ng-deep rw-table .p-ai-center{align-items:center}::ng-deep rw-table .has-filter filtericon{color:var(--p-datatable-header-cell-selected-color)}::ng-deep rw-table p-columnfilter .p-datatable-column-filter-button{width:unset;height:23px!important;padding-block-end:0;padding-block-start:0}\n"] }]
3710
+ }], ctorParameters: () => [{ type: i2$3.ControlContainer }, { type: i1$1.FormService }, { type: i3$3.Router }, { type: i3$3.ActivatedRoute }, { type: i4$2.PrimeNG }, { type: i2$1.FilterService }] });
3711
+ var ColumnFilterType;
3712
+ (function (ColumnFilterType) {
3713
+ ColumnFilterType["text"] = "text";
3714
+ ColumnFilterType["numeric"] = "numeric";
3715
+ ColumnFilterType["boolean"] = "boolean";
3716
+ ColumnFilterType["date"] = "date";
3717
+ ColumnFilterType["enum"] = "enum";
3718
+ })(ColumnFilterType || (ColumnFilterType = {}));
3371
3719
 
3372
3720
  /**
3373
- * Component for navigating to a resource by its ID.
3374
- *
3375
- * @remarks
3376
- * This component is used to navigate to a resource by its ID. It takes in the API name, the `rel` of the resource, and an optional `urlPrefix` to use for the URL that is returned from the backend. It also provides a form for entering the ID of the resource to navigate to.
3721
+ * A base class for all input components..
3377
3722
  */
3378
- class RestWorldIdNavigationComponent {
3379
- _clients;
3380
- _messageService;
3381
- _router;
3382
- _route;
3383
- apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
3384
- rel = input.required(...(ngDevMode ? [{ debugName: "rel" }] : []));
3723
+ class RestWorldInputBaseComponent {
3385
3724
  /**
3386
- * A prefix to use for the URL that is returned from the backend.
3387
- * If none is provided, a relative navigation will be performed which means that the last part of the current URL is replaced with the one from the backend.
3725
+ * The property to display.
3726
+ * @required
3388
3727
  */
3389
- urlPrefix = input(...(ngDevMode ? [undefined, { debugName: "urlPrefix" }] : []));
3390
- idNavigationForm = new FormGroup({
3391
- id: new FormControl(null, Validators.compose([Validators.min(1), Validators.max(Number.MAX_SAFE_INTEGER)]))
3392
- });
3393
- _client = computed(() => this._clients.getClient(this.apiName()), ...(ngDevMode ? [{ debugName: "_client" }] : []));
3394
- constructor(_clients, _messageService, _router, _route) {
3395
- this._clients = _clients;
3396
- this._messageService = _messageService;
3397
- this._router = _router;
3398
- this._route = _route;
3399
- }
3400
- async navigateById() {
3401
- const rel = this.rel();
3402
- if (!rel)
3403
- throw new Error('The "rel" must be set through the uri of this page for the ID navigation to work.');
3404
- if (!this.idNavigationForm.valid) {
3405
- this._messageService.add({ detail: 'You must enter a valid ID to naviage to.', severity: 'error' });
3406
- return;
3407
- }
3408
- var idToNavigateTo = this.idNavigationForm.controls.id.value;
3409
- var client = this._client();
3410
- var response = await client.getList(rel, { $filter: `id eq ${idToNavigateTo}` });
3411
- if (!response.ok || ProblemDetails.isProblemDetails(response.body) || !response.body) {
3412
- this._messageService.add({ severity: 'error', summary: 'Error', detail: 'Error while loading the resources from the API.', data: response });
3413
- return;
3414
- }
3415
- var resource = response.body?._embedded?.items?.[0];
3416
- if (!resource) {
3417
- this._messageService.add({ severity: 'error', summary: 'Error', detail: 'No resource found with the specified ID.' });
3418
- return;
3419
- }
3420
- const urlPrefix = this.urlPrefix();
3421
- if (urlPrefix !== undefined)
3422
- await this._router.navigate([urlPrefix, resource._links.self[0].href]);
3423
- else
3424
- await this._router.navigate(["..", resource._links.self[0].href], { relativeTo: this._route });
3425
- this.idNavigationForm.reset();
3426
- }
3427
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldIdNavigationComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.MessageService }, { token: i3$4.Router }, { token: i3$4.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
3428
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldIdNavigationComponent, isStandalone: true, selector: "rw-id-navigation", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: true, transformFunction: null }, urlPrefix: { classPropertyName: "urlPrefix", publicName: "urlPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<form [formGroup]=\"idNavigationForm\" (ngSubmit)=\"navigateById()\" class=\"mr-3\">\n <div class=\"p-inputgroup\">\n <p-inputNumber formControlName=\"id\" placeholder=\"Navigate by ID\"></p-inputNumber>\n <button type=\"submit\" pButton pRipple icon=\"fa-solid fa-arrow-right\"></button>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i1$5.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
3728
+ property = model.required(...(ngDevMode ? [{ debugName: "property" }] : []));
3729
+ /**
3730
+ * Set this to true if the input should use template driven forms instead of the default reactive forms.
3731
+ */
3732
+ useTemplateDrivenForms = input(false, ...(ngDevMode ? [{ debugName: "useTemplateDrivenForms" }] : []));
3733
+ model = model(...(ngDevMode ? [undefined, { debugName: "model" }] : []));
3734
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3735
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldInputBaseComponent, isStandalone: true, inputs: { property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, useTemplateDrivenForms: { classPropertyName: "useTemplateDrivenForms", publicName: "useTemplateDrivenForms", isSignal: true, isRequired: false, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { property: "propertyChange", model: "modelChange" }, ngImport: i0 });
3429
3736
  }
3430
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldIdNavigationComponent, decorators: [{
3431
- type: Component,
3432
- args: [{ selector: 'rw-id-navigation', standalone: true, imports: [InputNumberModule, ButtonModule, ReactiveFormsModule], template: "<form [formGroup]=\"idNavigationForm\" (ngSubmit)=\"navigateById()\" class=\"mr-3\">\n <div class=\"p-inputgroup\">\n <p-inputNumber formControlName=\"id\" placeholder=\"Navigate by ID\"></p-inputNumber>\n <button type=\"submit\" pButton pRipple icon=\"fa-solid fa-arrow-right\"></button>\n </div>\n</form>\n" }]
3433
- }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.MessageService }, { type: i3$4.Router }, { type: i3$4.ActivatedRoute }] });
3434
-
3737
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputBaseComponent, decorators: [{
3738
+ type: Directive
3739
+ }] });
3435
3740
  /**
3436
- * Displays one button for every `MenuItem` given in the items array.
3437
- * If a `MenuItem` has nested items, a button with a dropdown menu will be displayed.
3438
- * @example
3439
- * <rw-menu-button [items]="items"></rw-menu-button>
3741
+ * A base class for all input components which also feature lazy loading, like dropdowns.
3440
3742
  */
3441
- class RestWorldMenuButtonComponent {
3442
- _router;
3743
+ class RestWorldInputLazyLoadBaseComponent extends RestWorldInputBaseComponent {
3443
3744
  /**
3444
- * An array of menu items to be displayed.
3745
+ * The name of the API to use for the property.
3746
+ * @required
3747
+ * @remarks This is the name of the API as defined in the `RestWorldClientCollection`.
3445
3748
  */
3446
- items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
3447
- constructor(_router) {
3448
- this._router = _router;
3449
- }
3450
- onClick(event, item) {
3451
- const openInNewWindow = event.ctrlKey || event.button === 1 || event.metaKey;
3452
- if (item.url) {
3453
- window.open(item.url, openInNewWindow ? '_blank' : '_self');
3454
- }
3455
- else if (item.routerLink) {
3456
- if (openInNewWindow) {
3457
- const tree = this._router.createUrlTree(item.routerLink, {
3458
- queryParams: item.queryParams,
3459
- fragment: item.fragment,
3460
- queryParamsHandling: item.queryParamsHandling,
3461
- preserveFragment: item.preserveFragment,
3462
- });
3463
- const url = this._router.serializeUrl(tree);
3464
- window.open(url, '_blank');
3465
- }
3466
- else {
3467
- this._router.navigate(item.routerLink, {
3468
- queryParams: item.queryParams,
3469
- fragment: item.fragment,
3470
- queryParamsHandling: item.queryParamsHandling,
3471
- preserveFragment: item.preserveFragment,
3472
- skipLocationChange: item.skipLocationChange,
3473
- replaceUrl: item.replaceUrl,
3474
- });
3475
- }
3476
- }
3477
- else if (item.command) {
3478
- item.command({ item: item });
3479
- }
3480
- }
3481
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldMenuButtonComponent, deps: [{ token: i3$4.Router }], target: i0.ɵɵFactoryTarget.Component });
3482
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldMenuButtonComponent, isStandalone: true, selector: "rw-menu-button", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@for (item of items(); track item; let i = $index) {\n @if (!item.items) {\n <p-button\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-button>\n } \n @else {\n <p-splitButton\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [model]=\"item.items\"\n appendTo=\"body\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-splitButton>\n }\n}\n", styles: [":host{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: SplitButtonModule }, { kind: "component", type: i4$1.SplitButton, selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }] });
3483
- }
3484
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldMenuButtonComponent, decorators: [{
3749
+ apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
3750
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputLazyLoadBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
3751
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldInputLazyLoadBaseComponent, isStandalone: true, inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
3752
+ }
3753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputLazyLoadBaseComponent, decorators: [{
3754
+ type: Directive
3755
+ }] });
3756
+ /**
3757
+ * A form element with a label that is automatically created from a property in a form template.
3758
+ * This may also be a complex object or a collection in which case multiple and nested input elements may be rendered.
3759
+ * If you want a form element without a label, use {@link RestWorldFormInput} `<rw-input>`.
3760
+ * @example
3761
+ * <rw-form-element [property]="property" [apiName]="apiName"></rw-form-element>
3762
+ */
3763
+ class RestWorldFormElementComponent extends RestWorldInputLazyLoadBaseComponent {
3764
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormElementComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3765
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.1", type: RestWorldFormElementComponent, isStandalone: true, selector: "rw-form-element", usesInheritance: true, ngImport: i0, template: "<div class=\"grid field\">\r\n <rw-label [property]=\"property()\" class=\"col-12 md:col-2 flex align-items-center\"></rw-label>\r\n <rw-input [apiName]=\"apiName()\" [property]=\"property()\" class=\"col-12 md:col-10\"></rw-input>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputComponent), selector: "rw-input" }, { kind: "component", type: i0.forwardRef(() => RestWorldLabelComponent), selector: "rw-label", inputs: ["property"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3766
+ }
3767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormElementComponent, decorators: [{
3485
3768
  type: Component,
3486
- args: [{ selector: 'rw-menu-button', standalone: true, imports: [ButtonModule, TooltipModule, SplitButtonModule], template: "@for (item of items(); track item; let i = $index) {\n @if (!item.items) {\n <p-button\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-button>\n } \n @else {\n <p-splitButton\n [label]=\"item.label!\"\n [icon]=\"item.icon!\"\n [model]=\"item.items\"\n appendTo=\"body\"\n [disabled]=\"item.disabled!\"\n [style]=\"item.style\"\n [styleClass]=\"item.styleClass!\"\n [pTooltip]=\"item.tooltip!\"\n [tooltipPosition]=\"item.tooltipPosition!\"\n (onClick)=\"onClick($event, item)\"\n [class.ml-2]=\"i > 0\">\n </p-splitButton>\n }\n}\n", styles: [":host{display:flex;justify-content:flex-end}\n"] }]
3487
- }], ctorParameters: () => [{ type: i3$4.Router }] });
3488
-
3769
+ args: [{ selector: 'rw-form-element', viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputComponent), RestWorldLabelComponent], template: "<div class=\"grid field\">\r\n <rw-label [property]=\"property()\" class=\"col-12 md:col-2 flex align-items-center\"></rw-label>\r\n <rw-input [apiName]=\"apiName()\" [property]=\"property()\" class=\"col-12 md:col-10\"></rw-input>\r\n</div>\r\n" }]
3770
+ }] });
3489
3771
  /**
3490
- * This visitor turns a parsed OData $filter query string represented as a {@link Token} into a map of property names to filter metadata.
3491
- * The filter metadata is used by the PrimeNG table component to filter the rows of
3492
- * a table based on the values of the columns.
3772
+ * A collection that is automatically created from the given property.
3773
+ * The collection supports drag & drop to re order the elements and can also be nested.
3774
+ * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
3775
+ * instead of using this component directly.
3776
+ * @example
3777
+ * <rw-input-collection [property]="property" [apiName]="apiName"></rw-input-collection>
3493
3778
  */
3494
- class OdataVisitor {
3495
- _filters = new Map();
3496
- operator;
3497
- propertyName;
3498
- value;
3499
- static correctFilterMatchMode(matchMode, literalValue) {
3500
- if (literalValue instanceof Date) {
3501
- switch (matchMode) {
3502
- case FilterMatchMode.EQUALS:
3503
- return FilterMatchMode.DATE_IS;
3504
- case FilterMatchMode.NOT_EQUALS:
3505
- return FilterMatchMode.DATE_IS_NOT;
3506
- case FilterMatchMode.LESS_THAN:
3507
- return FilterMatchMode.DATE_BEFORE;
3508
- case FilterMatchMode.LESS_THAN_OR_EQUAL_TO:
3509
- return FilterMatchMode.DATE_BEFORE;
3510
- case FilterMatchMode.GREATER_THAN:
3511
- return FilterMatchMode.DATE_AFTER;
3512
- case FilterMatchMode.GREATER_THAN_OR_EQUAL_TO:
3513
- return FilterMatchMode.DATE_AFTER;
3779
+ class RestWorldInputCollectionComponent extends RestWorldInputLazyLoadBaseComponent {
3780
+ _formService;
3781
+ _controlContainer;
3782
+ defaultTemplate = computed(() => this.property()._templates.default, ...(ngDevMode ? [{ debugName: "defaultTemplate" }] : []));
3783
+ innerFormArray = computed(() => this._controlContainer.control?.controls[this.property().name], ...(ngDevMode ? [{ debugName: "innerFormArray" }] : []));
3784
+ templates = computed(() => this.getCollectionEntryTemplates(this.property()), ...(ngDevMode ? [{ debugName: "templates" }] : []));
3785
+ rows = toSignal(merge(
3786
+ // Get the initial value when 'control' changes
3787
+ toObservable(this.innerFormArray).pipe(map((ctl) => ctl.value)),
3788
+ // Get the new value when 'control.value' changes
3789
+ toObservable(this.innerFormArray).pipe(mergeMap((ctl) => ctl.valueChanges))), { initialValue: [] });
3790
+ inputCollectionRef;
3791
+ headerMenu = computed(() => {
3792
+ const isReadOnly = this.property().readOnly;
3793
+ if (isReadOnly)
3794
+ return [];
3795
+ return [
3796
+ {
3797
+ icon: "fas fa-plus",
3798
+ styleClass: "p-button-outlined p-button-info",
3799
+ command: () => this.addNewItemToCollection()
3514
3800
  }
3515
- }
3516
- return matchMode;
3517
- }
3518
- static parseLiteralValue(node) {
3519
- const type = node.value;
3520
- switch (type) {
3521
- case PrimitiveTypeEnum.Binary:
3522
- case PrimitiveTypeEnum.Boolean:
3523
- return node.raw === "true";
3524
- case PrimitiveTypeEnum.Byte:
3525
- case PrimitiveTypeEnum.Int16:
3526
- case PrimitiveTypeEnum.Int32:
3527
- case PrimitiveTypeEnum.Int64:
3528
- case PrimitiveTypeEnum.SByte:
3529
- return Number.parseInt(node.raw);
3530
- case PrimitiveTypeEnum.Date:
3531
- case PrimitiveTypeEnum.DateTime:
3532
- case PrimitiveTypeEnum.DateTimeOffset:
3533
- case PrimitiveTypeEnum.Duration:
3534
- case PrimitiveTypeEnum.TimeOfDay:
3535
- return new Date(node.raw);
3536
- case PrimitiveTypeEnum.Decimal:
3537
- case PrimitiveTypeEnum.Double:
3538
- case PrimitiveTypeEnum.Single:
3539
- return Number.parseFloat(node.raw);
3540
- default:
3541
- switch (node.raw) {
3542
- case "null":
3543
- return null;
3544
- default:
3545
- return node.raw.startsWith("'") ? node.raw.substring(1, node.raw.length - 1) : node.raw;
3546
- }
3547
- }
3548
- }
3549
- static parseMethodCallExpression(node) {
3550
- switch (node.value.method) {
3551
- case 'startswith':
3552
- return FilterMatchMode.STARTS_WITH;
3553
- case 'contains':
3554
- return FilterMatchMode.CONTAINS;
3555
- case 'not contains':
3556
- return FilterMatchMode.NOT_CONTAINS;
3557
- case 'endswith':
3558
- return FilterMatchMode.ENDS_WITH;
3559
- default:
3560
- throw Error(`Unknown method call ${node.value.method}`);
3561
- }
3562
- }
3563
- /***
3564
- * Parses an OData $filter query string and returns a map of property names to filter metadata.
3565
- * @param node The root node of the OData $filter query string.
3566
- * @param logToConsole If true, logs the type and raw value of each token to the console.
3567
- * @returns A map of property names to filter metadata.
3568
- */
3569
- parse(node, logToConsole) {
3570
- if (logToConsole) {
3571
- const visitLogAll = createTraverser(Object.fromEntries(Object.entries(TokenType)
3572
- .map(([key, value]) => [key, (node) => console.log(`${node.type}: ${node.raw}`)])), true);
3573
- visitLogAll(node);
3574
- }
3575
- const visit = createTraverser({
3576
- EqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.EQUALS),
3577
- NotEqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.NOT_EQUALS),
3578
- GreaterThanExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.GREATER_THAN),
3579
- GreaterOrEqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.GREATER_THAN_OR_EQUAL_TO),
3580
- LesserThanExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.LESS_THAN),
3581
- LesserOrEqualsExpression: (node) => this.CreateFilter(this.propertyName, this.value, FilterMatchMode.LESS_THAN_OR_EQUAL_TO),
3582
- HasExpression: (node) => this.CreateFilter(this.propertyName, [this.value], FilterMatchMode.EQUALS),
3583
- ODataIdentifier: (node) => this.propertyName = node.value.name,
3584
- Literal: (node) => this.value = OdataVisitor.parseLiteralValue(node),
3585
- AndExpression: (node) => this.operator = "and",
3586
- OrExpression: (node) => this.operator = "or",
3587
- BoolParenExpression: (node) => this.SetOperatorForFilters(),
3588
- MethodCallExpression: (node) => this.visitMethodCallExpression(node, visit),
3589
- }, true);
3590
- visit(node);
3591
- return this._filters;
3801
+ ];
3802
+ }, ...(ngDevMode ? [{ debugName: "headerMenu" }] : []));
3803
+ rowMenu = computed(() => (row, openedByRightClick) => {
3804
+ const isReadOnly = this.property().readOnly;
3805
+ if (isReadOnly)
3806
+ return [];
3807
+ return [
3808
+ {
3809
+ icon: "fas fa-trash-alt",
3810
+ label: openedByRightClick ? "Delete" : undefined,
3811
+ tooltip: !openedByRightClick ? "Delete" : undefined,
3812
+ tooltipPosition: "left",
3813
+ styleClass: "p-button-outlined p-button-danger",
3814
+ command: () => this.deleteItemFromCollection(row)
3815
+ }
3816
+ ];
3817
+ }, ...(ngDevMode ? [{ debugName: "rowMenu" }] : []));
3818
+ constructor(_formService, _controlContainer) {
3819
+ super();
3820
+ this._formService = _formService;
3821
+ this._controlContainer = _controlContainer;
3592
3822
  }
3593
- CreateFilter(propertyName, value, matchMode) {
3594
- if (propertyName === undefined)
3595
- throw Error(`Cannot add a filter for the value ${value} and the match mode ${matchMode} without a property name`);
3596
- matchMode = OdataVisitor.correctFilterMatchMode(matchMode, value);
3597
- const filter = this._filters.get(propertyName);
3598
- if (filter === undefined) {
3599
- this._filters.set(propertyName, [{ value, operator: "and", matchMode: matchMode }]);
3600
- }
3601
- else if (filter.length === 1 && Array.isArray(filter[0].value) && Array.isArray(value)) {
3602
- filter[0].value.push(...value); // Flags enum
3603
- }
3604
- else {
3605
- filter.push({ value, operator: "and", matchMode: matchMode });
3606
- }
3823
+ getCollectionEntryTemplates(property) {
3824
+ if (!property)
3825
+ return [];
3826
+ return Object.entries(property._templates)
3827
+ .filter(([key, value]) => Number.isInteger(Number.parseInt(key)) && Number.isInteger(Number.parseInt(value?.title ?? "")))
3828
+ .map(([, value]) => new NumberTemplate(value));
3607
3829
  }
3608
- SetOperatorForFilters() {
3609
- // In this case we are in the outermost expression which is just parantheses around the complete $filter value
3610
- if (this.propertyName === undefined)
3611
- return;
3612
- // If we have not found an operator, or it is "and", we can just stay with the default created with the filter.
3613
- if (this.operator === undefined || this.operator === "and")
3614
- return;
3615
- const filter = this._filters.get(this.propertyName);
3616
- if (filter === undefined)
3617
- return;
3618
- // The operator is "or", so we need to set the operator for all filters to "or"
3619
- for (const f of filter)
3620
- f.operator = this.operator;
3830
+ addNewItemToCollection() {
3831
+ const templates = this.templates();
3832
+ const defaultTemplate = this.defaultTemplate();
3833
+ const maxIndex = Math.max(...Object.keys(templates)
3834
+ .map(key => Number.parseInt(key))
3835
+ .filter(key => Number.isSafeInteger(key)));
3836
+ const nextIndex = maxIndex < 0 ? 0 : maxIndex + 1;
3837
+ const copiedTemplateDto = JSON.parse(JSON.stringify(defaultTemplate));
3838
+ copiedTemplateDto.title = nextIndex.toString();
3839
+ const copiedTemplate = new Template(copiedTemplateDto);
3840
+ this.innerFormArray().push(this._formService.createFormGroupFromTemplate(this.defaultTemplate()));
3841
+ this.property.update((property) => { property._templates[nextIndex] = copiedTemplate; return { ...property }; });
3621
3842
  }
3622
- // The visitor does not correctly implement the MethodCallExpressionToken, so we need to do this manually
3623
- visitMethodCallExpression(node, visit) {
3624
- for (const parameter of node.value.parameters)
3625
- visit(parameter);
3626
- const matchMode = OdataVisitor.parseMethodCallExpression(node);
3627
- this.CreateFilter(this.propertyName, this.value, matchMode);
3843
+ deleteItemFromCollection(row) {
3844
+ const index = this.rows().indexOf(row);
3845
+ this.innerFormArray().removeAt(index);
3628
3846
  }
3847
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputCollectionComponent, deps: [{ token: i1$1.FormService }, { token: i2$3.ControlContainer }], target: i0.ɵɵFactoryTarget.Component });
3848
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.1", type: RestWorldInputCollectionComponent, isStandalone: true, selector: "rw-input-collection", queries: [{ propertyName: "inputCollectionRef", first: true, predicate: ["inputCollection"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<rw-table\n [apiName]=\"apiName()\"\n [editTemplate]=\"defaultTemplate()\"\n [formArrayName]=\"property().name\"\n [lazy]=\"false\"\n [rows]=\"rows()\"\n [searchTemplate]=\"defaultTemplate()\"\n [headerMenu]=\"headerMenu()\"\n [rowMenu]=\"rowMenu()\"\n\n >\n</rw-table>\n", styles: [".cdk-drag-handle{cursor:move}.cdk-drag-preview{background-color:#ffffffd0;border:2px dashed rgb(206,212,218);cursor:move}.cdk-drag-placeholder{border:2px dashed rgb(206,212,218);margin:-2px}.brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"], dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => DragDropModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "directive", type: i0.forwardRef(() => i2$3.NgControlStatusGroup), selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i0.forwardRef(() => i2$3.FormArrayName), selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i0.forwardRef(() => RestWorldTableComponent), selector: "rw-table", inputs: ["apiName", "cellStyleClass", "editTemplate", "headerMenu", "isLoading", "lazy", "paginator", "oDataParameters", "reflectParametersInUrl", "rowHover", "rowMenu", "rowStyleClass", "rows", "rowsPerPageOptions", "scrollHeight", "scrollable", "searchTemplate", "selectedRows", "selectionMode", "showRowMenuAsColumn", "showRowMenuOnRightClick", "styleClass", "tableStyle", "totalRecords", "urlParameterPrefix"], outputs: ["oDataParametersChange", "selectedRowsChange"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3629
3849
  }
3630
-
3850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputCollectionComponent, decorators: [{
3851
+ type: Component,
3852
+ args: [{ selector: 'rw-input-collection', viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputTemplateComponent), DragDropModule, ReactiveFormsModule, RestWorldTableComponent], template: "<rw-table\n [apiName]=\"apiName()\"\n [editTemplate]=\"defaultTemplate()\"\n [formArrayName]=\"property().name\"\n [lazy]=\"false\"\n [rows]=\"rows()\"\n [searchTemplate]=\"defaultTemplate()\"\n [headerMenu]=\"headerMenu()\"\n [rowMenu]=\"rowMenu()\"\n\n >\n</rw-table>\n", styles: [".cdk-drag-handle{cursor:move}.cdk-drag-preview{background-color:#ffffffd0;border:2px dashed rgb(206,212,218);cursor:move}.cdk-drag-placeholder{border:2px dashed rgb(206,212,218);margin:-2px}.brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"] }]
3853
+ }], ctorParameters: () => [{ type: i1$1.FormService }, { type: i2$3.ControlContainer }], propDecorators: { inputCollectionRef: [{
3854
+ type: ContentChild,
3855
+ args: ['inputCollection', { static: false }]
3856
+ }] } });
3631
3857
  /**
3632
- * This class is used to parse an OData $filter string into a Record of filter constraints.
3858
+ * A form input element that is automatically created from a property in a form template.
3859
+ * This may also be a complex object or a collection in which case multiple and nested input elements may be rendered.
3860
+ * If you also want a label, use {@link RestWorldFormElement} `<rw-form-element>`.
3861
+ * You can also use one of the different RestWorldInput... `<rw-input-...>` elements to render a specific input,
3862
+ * but it is advised to control the rendered input through the passed in property.
3863
+ * @example
3864
+ * <rw-input [property]="property" [apiName]="apiName"></rw-input>
3633
3865
  */
3634
- class ODataFilterParser {
3635
- static parseFilter(filter, properties) {
3636
- if (!filter)
3637
- return {};
3638
- // The parser has a bug where it does not correctly parse cast expressions, so we need to replace them with the first argument
3639
- // See https://github.com/Soontao/odata-v4-parser/issues/283
3640
- filter = ODataFilterParser.replaceCastExpressions(filter);
3641
- const ast = defaultParser.filter(filter);
3642
- const visitor = new OdataVisitor();
3643
- const filters = visitor.parse(ast);
3644
- /// OData needs enum values in pascal case, but JSON and therefor HAL-Forms needs them in camel case
3645
- ODataFilterParser.MakeEnumValuesCamelCase(filters, properties);
3646
- return Object.fromEntries(filters);
3866
+ class RestWorldInputComponent extends RestWorldInputLazyLoadBaseComponent {
3867
+ get PropertyType() {
3868
+ return PropertyType;
3647
3869
  }
3648
- static MakeEnumValuesCamelCase(filters, properties) {
3649
- for (const [key, value] of filters) {
3650
- for (const filter of value) {
3651
- const options = properties[key]?.options;
3652
- if (options && !options.link && typeof filter.value === "string") {
3653
- filter.value = filter.value.charAt(0).toLowerCase() + filter.value.slice(1);
3654
- }
3655
- }
3656
- }
3657
- }
3658
- static replaceCastExpressions(input) {
3659
- // Regular expression to match cast(x, y) and extract x
3660
- const regex = /cast\(([^,]+),\s*[^)]+\)/g;
3661
- // Replace matches with the first captured group (x)
3662
- const result = input.replace(regex, (match, x) => x.trim());
3663
- return result;
3870
+ get PropertyWithOptions() {
3871
+ return PropertyWithOptions;
3664
3872
  }
3873
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3874
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputComponent, isStandalone: true, selector: "rw-input", usesInheritance: true, ngImport: i0, template: "@if (property()) {\r\n @if (property().options) {\r\n <rw-input-dropdown [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-dropdown>\r\n }\r\n @else {\r\n @switch (property().type) {\r\n @case (PropertyType.Object) {\r\n <rw-input-object [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-object>\r\n }\r\n @case (PropertyType.Collection) {\r\n <rw-input-collection [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-collection>\r\n }\r\n @default {\r\n <rw-input-simple [property]=\"property()\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-simple>\r\n }\r\n }\r\n }\r\n\r\n <rw-validation-errors [property]=\"property()\"></rw-validation-errors>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputDropdownComponent), selector: "rw-input-dropdown", inputs: ["caseSensitive", "getLabel", "getTooltip"], outputs: ["onChange"] }, { kind: "component", type: i0.forwardRef(() => RestWorldInputObjectComponent), selector: "rw-input-object" }, { kind: "component", type: i0.forwardRef(() => RestWorldInputSimpleComponent), selector: "rw-input-simple" }, { kind: "component", type: i0.forwardRef(() => RestWorldInputCollectionComponent), selector: "rw-input-collection" }, { kind: "component", type: i0.forwardRef(() => RestWorldValidationErrorsComponent), selector: "rw-validation-errors", inputs: ["form", "property"] }, { kind: "ngmodule", type: i0.forwardRef(() => FormsModule) }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3665
3875
  }
3666
-
3876
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputComponent, decorators: [{
3877
+ type: Component,
3878
+ args: [{ selector: 'rw-input', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputDropdownComponent), forwardRef(() => RestWorldInputObjectComponent), forwardRef(() => RestWorldInputSimpleComponent), RestWorldInputCollectionComponent, RestWorldValidationErrorsComponent, FormsModule], template: "@if (property()) {\r\n @if (property().options) {\r\n <rw-input-dropdown [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-dropdown>\r\n }\r\n @else {\r\n @switch (property().type) {\r\n @case (PropertyType.Object) {\r\n <rw-input-object [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-object>\r\n }\r\n @case (PropertyType.Collection) {\r\n <rw-input-collection [apiName]=\"apiName()\" [property]=\"$any(property())\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-collection>\r\n }\r\n @default {\r\n <rw-input-simple [property]=\"property()\" [useTemplateDrivenForms]=\"useTemplateDrivenForms()\"></rw-input-simple>\r\n }\r\n }\r\n }\r\n\r\n <rw-validation-errors [property]=\"property()\"></rw-validation-errors>\r\n}\r\n" }]
3879
+ }] });
3667
3880
  /**
3668
- * A static class that provides utility methods for creating OData filters, order by clauses, and parameters from {@link TableLazyLoadEvent} objects.
3881
+ * A dropdown that is automatically created from the given property.
3882
+ * The dropdown supports searching through a RESTWorld list endpoint on the backend if the `link` of the options is set.
3883
+ * Otherwise the dropdown will use the `inline` of the options.
3884
+ * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
3885
+ * instead of using this component directly.
3886
+ * @example
3887
+ * <rw-input-dropdown [property]="property" [apiName]="apiName"></rw-input-dropdown>
3669
3888
  */
3670
- class ODataService {
3889
+ class RestWorldInputDropdownComponent extends RestWorldInputLazyLoadBaseComponent {
3890
+ _controlContainer;
3671
3891
  /**
3672
- * Creates an OData filter string for a given property and filter metadata.
3673
- * @param property - The property to filter on.
3674
- * @param filter - The filter metadata to use.
3675
- * @returns The OData filter string, or undefined if the filter value is falsy.
3892
+ * A flag that indicates if the search should be case sensitive.
3893
+ * The default is false.
3676
3894
  */
3677
- static createFilterForProperty(property, filter) {
3678
- if (filter.matchMode == TranslationKeys.NO_FILTER)
3679
- return undefined;
3680
- // Enums are handled differently
3681
- if (property.options && !property.options.link)
3682
- return ODataService.createFilterForEnum(property, filter);
3683
- const oDataOperator = ODataService.createODataOperator(filter.matchMode);
3684
- const comparisonValue = ODataService.createComparisonValue(property, filter.value);
3685
- switch (oDataOperator) {
3686
- case 'contains':
3687
- case 'not contains':
3688
- case 'startswith':
3689
- case 'endswith':
3690
- return `${oDataOperator}(${property.name}, ${comparisonValue})`;
3691
- default:
3692
- return `${property.name} ${oDataOperator} ${comparisonValue}`;
3693
- }
3694
- }
3895
+ caseSensitive = input(false, ...(ngDevMode ? [{ debugName: "caseSensitive" }] : []));
3695
3896
  /**
3696
- * Creates an OData `$filter` value for an array of {@link FilterMetadata} objects and a given {@link Property}.
3697
- * @param property - The {@link Property} to filter on.
3698
- * @param filters - An array of {@link FilterMetadata} objects.
3699
- * @returns The OData `$filter` value, or undefined if no filters were provided.
3897
+ * A function that returns the label for the given item.
3898
+ * The default returns the prompt and optionally the value in brackets.
3899
+ * The value in brackets will only be displayed if the `cols` field of the property is undefined or greater than 1.
3900
+ * Overwrite this function to change the label.
3901
+ * @param item The item to get the label for.
3700
3902
  */
3701
- static createFilterForPropertyArray(property, filters) {
3702
- const filter = filters
3703
- .map(f => ODataService.createFilterForProperty(property, f))
3704
- .filter(f => !!f)
3705
- .join(` ${filters[0].operator} `);
3706
- if (filter === '')
3707
- return undefined;
3708
- return `(${filter})`;
3709
- }
3903
+ getLabel = input(...(ngDevMode ? [undefined, { debugName: "getLabel" }] : []));
3710
3904
  /**
3711
- * Creates an OData `$filter` value from a {@link TableLazyLoadEvent} and an array of {@link Property Properties}.
3712
- * @param event The {@link TableLazyLoadEvent} containing the filter data.
3713
- * @param properties An optional array of {@link Property Properties} to filter on.
3714
- * @returns The OData $`$filter` value, or undefined if no filters were applied or no properties were provided.
3905
+ * A function that returns the tooltip for the given item.
3906
+ * The default returns all properties of the item except the ones that start with an underscore or the ones that are in the list of default properties to exclude.
3907
+ * The default properties to exclude are: createdAt, createdBy, lastChangedAt, lastChangedBy, timestamp, promptField, valueField.
3908
+ * Overwrite this function to change the tooltip.
3909
+ * @param item The item to get the label for.
3715
3910
  */
3716
- static createFilterFromTableLoadEvent(event, properties) {
3717
- const eventFilters = event.filters;
3718
- if (eventFilters === undefined || properties === undefined)
3719
- return undefined;
3720
- const filter = properties
3721
- .map(property => ({ property, filters: eventFilters[property.name] }))
3722
- .filter(f => f.filters !== undefined)
3723
- .map(f => ODataService.createFilterForPropertyArray(f.property, f.filters))
3724
- .filter(f => !!f)
3725
- .join(' and ');
3726
- if (filter === '')
3727
- return undefined;
3728
- return `(${filter})`;
3729
- }
3911
+ getTooltip = input(...(ngDevMode ? [undefined, { debugName: "getTooltip" }] : []));
3912
+ inputOptionsMultipleRef = contentChild("inputOptionsMultiple", ...(ngDevMode ? [{ debugName: "inputOptionsMultipleRef" }] : []));
3913
+ inputOptionsSingleRef = contentChild("inputOptionsSingle", ...(ngDevMode ? [{ debugName: "inputOptionsSingleRef" }] : []));
3914
+ multiSelect = viewChild(MultiSelect, ...(ngDevMode ? [{ debugName: "multiSelect" }] : []));
3730
3915
  /**
3731
- * Creates a map of property names to filter metadata from an OData $filter string.
3732
- * @param filter The OData filter string to parse.
3733
- * @returns A record of property names to filter metadata.
3916
+ * An event that is emitted when the selected value changes.
3734
3917
  */
3735
- static createFilterMetadataFromODataFilter(filter, properties) {
3736
- const filters = ODataFilterParser.parseFilter(filter, properties);
3737
- return filters;
3918
+ onChange = output();
3919
+ onOptionsFiltered = debounce(this.onOptionsFilteredInternal, 500);
3920
+ optionsManager;
3921
+ _formControl = computed(() => {
3922
+ const formGroup = this._controlContainer.control;
3923
+ return formGroup.controls[this.property().name];
3924
+ }, ...(ngDevMode ? [{ debugName: "_formControl" }] : []));
3925
+ _value = toSignal(toObservable(this._formControl).pipe(distinctUntilChanged(), switchMap(formControl => formControl
3926
+ ? formControl.valueChanges.pipe(debounceTime(0), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))).pipe(startWith(formControl.value))
3927
+ : EMPTY)));
3928
+ constructor(_controlContainer, optionsService) {
3929
+ super();
3930
+ this._controlContainer = _controlContainer;
3931
+ this.optionsManager = optionsService.getManager(this.apiName, this.property, this._value, this.getLabel, this.getTooltip);
3738
3932
  }
3739
- /**
3740
- * Creates a OData `$orderby` value from a {@link TableLazyLoadEvent}.
3741
- * @param event The {@link TableLazyLoadEvent} to create the `$orderby` value from.
3742
- * @returns The `$orderby` value created from the {@link TableLazyLoadEvent}.
3743
- */
3744
- static createOrderByFromTableLoadEvent(event) {
3745
- if (event.multiSortMeta && event.multiSortMeta.length > 0) {
3746
- return event.multiSortMeta
3747
- .map(m => `${m.field} ${m.order > 0 ? 'asc' : 'desc'}`)
3748
- .join(', ');
3749
- }
3750
- if (event.sortField) {
3751
- const order = !event.sortOrder || event.sortOrder > 0 ? 'asc' : 'desc';
3752
- return `${event.sortField} ${order}`;
3753
- }
3754
- return undefined;
3933
+ onOptionsChanged(event) {
3934
+ this.onChange.emit(event);
3755
3935
  }
3756
- /**
3757
- * Creates an {@link ODataParameters} object from a given {@link ActivatedRoute}.
3758
- * @param route The {@link ActivatedRoute} to create the {@link ODataParameters} from.
3759
- * @param prefix An optional prefix to use for the query parameter keys.
3760
- * @returns The {@link ODataParameters} object created from the {@link ActivatedRoute}.
3761
- */
3762
- static createParametersFromRoute(route, prefix) {
3763
- const snapshot = route.snapshot;
3764
- let oDataParameters = {};
3765
- const filter = snapshot.queryParamMap.get(`${prefix}$filter`);
3766
- const orderBy = snapshot.queryParamMap.get(`${prefix}$orderby`);
3767
- const top = snapshot.queryParamMap.get(`${prefix}$top`);
3768
- const skip = snapshot.queryParamMap.get(`${prefix}$skip`);
3769
- if (filter)
3770
- oDataParameters.$filter = filter;
3771
- if (orderBy)
3772
- oDataParameters.$orderby = orderBy;
3773
- if (top) {
3774
- const topNumber = Number.parseInt(top);
3775
- if (Number.isInteger(topNumber))
3776
- oDataParameters.$top = topNumber;
3777
- }
3778
- if (skip) {
3779
- const skipNumber = Number.parseInt(skip);
3780
- if (Number.isInteger(skipNumber))
3781
- oDataParameters.$skip = skipNumber;
3936
+ async onOptionsFilteredInternal(event) {
3937
+ const options = this.optionsManager.options();
3938
+ const currentItems = this.optionsManager.items.value();
3939
+ if (!(event.filter) || event.filter === '')
3940
+ return;
3941
+ if (event.originalEvent.type === "input") {
3942
+ const inputEvent = event.originalEvent;
3943
+ if (inputEvent.inputType === "insertFromPaste") {
3944
+ // If the user pasted in multiple ids as comma separated list, we want to get them all and set them as the selected value.
3945
+ var values = event.filter
3946
+ .split(",")
3947
+ .filter(v => v !== '')
3948
+ .map(v => v.trim())
3949
+ .map(v => {
3950
+ const n = Number.parseFloat(v);
3951
+ return Number.isNaN(n) ? this.makeUpperIfCaseInsensitive(v.toUpperCase(), false) : n;
3952
+ });
3953
+ if (!values || values.length === 0)
3954
+ return;
3955
+ const allAreNumbers = values.every(v => typeof v === "number" && !isNaN(v));
3956
+ const filter = allAreNumbers
3957
+ ? `${options.valueField} in (${values.join(',')})`
3958
+ : `contains(${this.makeUpperIfCaseInsensitive(options.promptField, true)}, '${values.join("', '")}')`;
3959
+ if ((options?.link?.href))
3960
+ await this.optionsManager.updateItemsFromFilter(filter);
3961
+ if (currentItems) {
3962
+ const selectedValues = currentItems
3963
+ .map(i => this.optionsManager.getValue(i))
3964
+ .filter(v => values.includes(v));
3965
+ this._formControl().setValue(selectedValues);
3966
+ this.multiSelect()?.resetFilter();
3967
+ }
3968
+ }
3969
+ else {
3970
+ // This is the normal case where the user types in a filter.
3971
+ let filter = `contains(${this.makeUpperIfCaseInsensitive(options.promptField, true)}, '${this.makeUpperIfCaseInsensitive(event.filter, false)}')`;
3972
+ if (options.valueField?.toLowerCase() === 'id' && !Number.isNaN(Number.parseInt(event.filter)))
3973
+ filter = `(${options.valueField} eq ${event.filter}) or (${filter})`;
3974
+ if ((options?.link?.href))
3975
+ await this.optionsManager.updateItemsFromFilter(filter);
3976
+ }
3782
3977
  }
3783
- return oDataParameters;
3784
3978
  }
3785
- /**
3786
- * Creates {@link ODataParameters} from a {@link TableLazyLoadEvent} and an optional {@link Template}.
3787
- * @param event The {@link TableLazyLoadEvent} to create OData parameters from.
3788
- * @param template An optional {@link Template} to use for creating the OData parameters.
3789
- * @returns An {@link ODataParameters} object containing the `$filter`, `$orderby`, `$top`, and `$skip` parameters.
3790
- */
3791
- static createParametersFromTableLoadEvent(event, template) {
3792
- const oDataParameters = {
3793
- $filter: ODataService.createFilterFromTableLoadEvent(event, template?.properties),
3794
- $orderby: ODataService.createOrderByFromTableLoadEvent(event),
3795
- $top: ODataService.createTopFromTableLoadEvent(event),
3796
- $skip: ODataService.createSkipFromTableLoadEvent(event)
3797
- };
3798
- return oDataParameters;
3979
+ makeUpperIfCaseInsensitive(filter, isOData) {
3980
+ if (this.caseSensitive() || typeof filter !== "string")
3981
+ return filter;
3982
+ if (isOData)
3983
+ return `toupper(${filter})`;
3984
+ return filter.toUpperCase();
3799
3985
  }
3800
- /**
3801
- * Creates a OData `$skip` value from a {@link TableLazyLoadEvent}.
3802
- * @param event The {@link TableLazyLoadEvent} to create the `$skip` value from.
3803
- * @returns The `$skip` value created from the {@link TableLazyLoadEvent}.
3804
- */
3805
- static createSkipFromTableLoadEvent(event) {
3806
- return event.first;
3986
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputDropdownComponent, deps: [{ token: i2$3.ControlContainer }, { token: OptionsService }], target: i0.ɵɵFactoryTarget.Component });
3987
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputDropdownComponent, isStandalone: true, selector: "rw-input-dropdown", inputs: { caseSensitive: { classPropertyName: "caseSensitive", publicName: "caseSensitive", isSignal: true, isRequired: false, transformFunction: null }, getLabel: { classPropertyName: "getLabel", publicName: "getLabel", isSignal: true, isRequired: false, transformFunction: null }, getTooltip: { classPropertyName: "getTooltip", publicName: "getTooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChange: "onChange" }, queries: [{ propertyName: "inputOptionsMultipleRef", first: true, predicate: ["inputOptionsMultiple"], descendants: true, isSignal: true }, { propertyName: "inputOptionsSingleRef", first: true, predicate: ["inputOptionsSingle"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "multiSelect", first: true, predicate: MultiSelect, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-template #defaultInputOptionsSingle let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-select\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n @else {\n <p-select\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n</ng-template>\n\n<ng-template #defaultInputOptionsMultiple let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-multiSelect\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n @else {\n <p-multiSelect\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n</ng-template>\n\n@if(!optionsManager.options().maxItems || optionsManager.options().maxItems == 1) {\n <ng-container *ngTemplateOutlet=\"inputOptionsSingleRef() ?? defaultInputOptionsSingle; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n@else {\n <ng-container *ngTemplateOutlet=\"inputOptionsMultipleRef() ?? defaultInputOptionsMultiple; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n", styles: ["::ng-deep .p-multiselect-label{display:inline-flex!important}::ng-deep .p-multiselect-label-empty{height:36px}::ng-deep .p-chip{background-color:#eff6ff;color:#1d4ed8}::ng-deep .pi-chip-remove-icon:hover{filter:brightness(.6)}\n"], dependencies: [{ kind: "component", type: Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "component", type: MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "component", type: Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: HalFormsModule }, { kind: "directive", type: FormControlProperty, selector: "[formControlProperty]:not([useTemplateDrivenForms=true])", inputs: ["formControlProperty"] }, { kind: "directive", type: PropertyControlStatus, selector: "[formControlProperty]" }, { kind: "directive", type: PropertySelectAttributes, selector: "p-select[formControlProperty], p-select[propertyAttributes], p-multiSelect[formControlProperty], p-multiSelect[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "directive", type: PropertyAttributes, selector: "[formControlProperty],[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3988
+ }
3989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputDropdownComponent, decorators: [{
3990
+ type: Component,
3991
+ args: [{ selector: 'rw-input-dropdown', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [Select, ReactiveFormsModule, MultiSelect, Tooltip, Chip, NgTemplateOutlet, HalFormsModule, FormsModule], template: "<ng-template #defaultInputOptionsSingle let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-select\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n @else {\n <p-select\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selectedItem let-selectedItem>\n <span [pTooltip]=\"optionsManager.getTooltip()(selectedItem)\">{{optionsManager.getLabel()(selectedItem)}}</span>\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-select>\n }\n</ng-template>\n\n<ng-template #defaultInputOptionsMultiple let-property=\"property\" let-template=\"template\" let-items=\"items\" let-useTemplateDrivenForms=\"useTemplateDrivenForms\">\n @if (useTemplateDrivenForms()) {\n <p-multiSelect\n [propertyAttributes]=\"property()\"\n [(ngModel)]=\"model\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n @else {\n <p-multiSelect\n [formControlProperty]=\"property()\"\n [options]=\"optionsManager.items.value()\"\n (onFilter)=\"onOptionsFiltered($event)\"\n (onChange)=\"onOptionsChanged($event)\"\n styleClass=\"w-full\"\n [panelStyleClass]=\"optionsManager.loading() ? 'loading' : ''\"\n [emptyFilterMessage]=\"optionsManager.loading() ? 'Loading...' : ''\"\n [loading]=\"optionsManager.loading()\"\n >\n <ng-template #selecteditems let-items let-removeChip=\"removeChip\">\n @for (item of items; track item; let i = $index) {\n <p-chip\n [pTooltip]=\"optionsManager.getTooltip()(item)\"\n [label]=\"optionsManager.getLabel()(item)\"\n [removable]=\"true\"\n (onRemove)=\"removeChip(optionsManager.getValue(item), $event)\"\n >\n </p-chip>\n }\n </ng-template>\n <ng-template #item let-item>\n <span [pTooltip]=\"optionsManager.getTooltip()(item)\">{{optionsManager.getLabel()(item)}}</span>\n </ng-template>\n </p-multiSelect>\n }\n</ng-template>\n\n@if(!optionsManager.options().maxItems || optionsManager.options().maxItems == 1) {\n <ng-container *ngTemplateOutlet=\"inputOptionsSingleRef() ?? defaultInputOptionsSingle; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n@else {\n <ng-container *ngTemplateOutlet=\"inputOptionsMultipleRef() ?? defaultInputOptionsMultiple; context: { property: property, apiName: apiName, items: optionsManager.items, useTemplateDrivenForms: useTemplateDrivenForms, model: model }\"></ng-container>\n}\n", styles: ["::ng-deep .p-multiselect-label{display:inline-flex!important}::ng-deep .p-multiselect-label-empty{height:36px}::ng-deep .p-chip{background-color:#eff6ff;color:#1d4ed8}::ng-deep .pi-chip-remove-icon:hover{filter:brightness(.6)}\n"] }]
3992
+ }], ctorParameters: () => [{ type: i2$3.ControlContainer }, { type: OptionsService }] });
3993
+ /**
3994
+ * A complex object with multiple properties that is automatically created from the given property.
3995
+ * The object can also be nested.
3996
+ * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
3997
+ * instead of using this component directly.
3998
+ * @example
3999
+ * <rw-input-object [property]="property" [apiName]="apiName"></rw-input-object>
4000
+ */
4001
+ class RestWorldInputObjectComponent extends RestWorldInputLazyLoadBaseComponent {
4002
+ _controlContainer;
4003
+ inputObjectRef = contentChild("inputObject", ...(ngDevMode ? [{ debugName: "inputObjectRef" }] : []));
4004
+ constructor(_controlContainer) {
4005
+ super();
4006
+ this._controlContainer = _controlContainer;
3807
4007
  }
3808
- /**
3809
- * Creates a OData `$top` value from a {@link TableLazyLoadEvent}.
3810
- * @param event The {@link TableLazyLoadEvent} to create the `$top` value from.
3811
- * @returns The `$top` value created from the {@link TableLazyLoadEvent}.
3812
- */
3813
- static createTopFromTableLoadEvent(event) {
3814
- return event.rows === null ? undefined : event.rows;
4008
+ innerFormGroup = computed(() => {
4009
+ const formGroup = this._controlContainer.control;
4010
+ return formGroup.controls[this.property().name];
4011
+ }, ...(ngDevMode ? [{ debugName: "innerFormGroup" }] : []));
4012
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputObjectComponent, deps: [{ token: i2$3.ControlContainer }], target: i0.ɵɵFactoryTarget.Component });
4013
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.1", type: RestWorldInputObjectComponent, isStandalone: true, selector: "rw-input-object", queries: [{ propertyName: "inputObjectRef", first: true, predicate: ["inputObject"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-template #defaultInputObject let-property=\"property\" let-innerFormGroup=\"innerFormGroup\", let-apiName=\"apiName\">\n <div class=\"flex align-items-center\">\n <div class=\"brace\">\n </div>\n <div class=\"w-full\">\n <rw-input-template [formGroup]=\"innerFormGroup()\" [template]=\"property()._templates.default\" [apiName]=\"apiName()\"></rw-input-template>\n </div>\n </div>\n</ng-template>\n\n<ng-container>\n <ng-container *ngTemplateOutlet=\"inputObjectRef() || defaultInputObject; context: { property: property, innerFormGroup: innerFormGroup, apiName: apiName }\"></ng-container>\n</ng-container>\n", styles: [".brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => RestWorldInputTemplateComponent), selector: "rw-input-template", inputs: ["apiName", "template"] }, { kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "directive", type: i0.forwardRef(() => i2$3.NgControlStatusGroup), selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i0.forwardRef(() => i2$3.FormGroupDirective), selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i0.forwardRef(() => NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
4014
+ }
4015
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputObjectComponent, decorators: [{
4016
+ type: Component,
4017
+ args: [{ selector: 'rw-input-object', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [forwardRef(() => RestWorldInputTemplateComponent), ReactiveFormsModule, NgTemplateOutlet], template: "<ng-template #defaultInputObject let-property=\"property\" let-innerFormGroup=\"innerFormGroup\", let-apiName=\"apiName\">\n <div class=\"flex align-items-center\">\n <div class=\"brace\">\n </div>\n <div class=\"w-full\">\n <rw-input-template [formGroup]=\"innerFormGroup()\" [template]=\"property()._templates.default\" [apiName]=\"apiName()\"></rw-input-template>\n </div>\n </div>\n</ng-template>\n\n<ng-container>\n <ng-container *ngTemplateOutlet=\"inputObjectRef() || defaultInputObject; context: { property: property, innerFormGroup: innerFormGroup, apiName: apiName }\"></ng-container>\n</ng-container>\n", styles: [".brace{align-self:stretch;margin:.2rem .5rem;border-left:1px solid rgb(206,212,218);border-top:1px solid rgb(206,212,218);border-bottom:1px solid rgb(206,212,218);width:1rem}\n"] }]
4018
+ }], ctorParameters: () => [{ type: i2$3.ControlContainer }] });
4019
+ /**
4020
+ * A simple input element, like a string, a number or a Date that is automatically created from the given property.
4021
+ * @remarks It is advised to use {@link RestWorldInputComponent} `<rw-input>` and control the rendered inputs with the passed in property
4022
+ * instead of using this component directly.
4023
+ * @example
4024
+ * <rw-input-simple [property]="property" [apiName]="apiName"></rw-input-simple>
4025
+ */
4026
+ class RestWorldInputSimpleComponent extends RestWorldInputBaseComponent {
4027
+ static _dateFormat = new Date(3333, 10, 22) // months start at 0 in JS
4028
+ .toLocaleDateString()
4029
+ .replace("22", "dd")
4030
+ .replace("11", "mm")
4031
+ .replace("3333", "yy")
4032
+ .replace("33", "y");
4033
+ static _timeFormat = new Date(1, 1, 1, 22, 33, 44)
4034
+ .toLocaleTimeString()
4035
+ .replace("22", "hh")
4036
+ .replace("33", "mm")
4037
+ .replace("44", "ss");
4038
+ _inputChild = viewChild("inputElement", ...(ngDevMode ? [{ debugName: "_inputChild" }] : []));
4039
+ _controlChild = viewChild(NG_VALUE_ACCESSOR, ...(ngDevMode ? [{ debugName: "_controlChild" }] : []));
4040
+ constructor() {
4041
+ super();
3815
4042
  }
3816
- static createComparisonValue(property, value, isEnum) {
3817
- if (value === null || value === undefined)
3818
- return 'null';
3819
- const type = ODataService.getPropertyType(property, value);
3820
- switch (type) {
3821
- case PropertyType.Date:
3822
- return `cast(${value.toISOString()}, Edm.DateOnly)`;
3823
- case PropertyType.DatetimeLocal:
3824
- return `cast(${value.toISOString()}, Edm.DateTime)`;
3825
- case PropertyType.DatetimeOffset:
3826
- return `cast(${value.toISOString()}, Edm.DateTimeOffset)`;
3827
- case PropertyType.Time:
3828
- return `cast(${value.toISOString()}, Edm.TimeOnly)`;
3829
- case PropertyType.Duration:
3830
- return `cast(${value.toISOString()}, Edm.TimeSpan)`;
3831
- case PropertyType.Bool:
3832
- case PropertyType.Number:
3833
- case PropertyType.Currency:
3834
- case PropertyType.Month:
3835
- return '' + value;
3836
- case PropertyType.Percent:
3837
- return '' + (value / 100);
3838
- default:
3839
- return `'${isEnum && typeof value === "string" ? value.charAt(0).toUpperCase() + value.slice(1) : value}'`;
4043
+ writeValue(obj) {
4044
+ const controlChild = this._controlChild();
4045
+ const inputChild = this._inputChild();
4046
+ if (controlChild !== undefined)
4047
+ controlChild.writeValue(obj);
4048
+ else if (inputChild !== undefined)
4049
+ inputChild.nativeElement.value = obj;
4050
+ }
4051
+ registerOnChange(fn) {
4052
+ const controlChild = this._controlChild();
4053
+ const inputChild = this._inputChild();
4054
+ if (controlChild !== undefined)
4055
+ controlChild.registerOnChange(fn);
4056
+ else if (inputChild !== undefined)
4057
+ inputChild.nativeElement.oninput = (event) => fn(event.target.value);
4058
+ }
4059
+ registerOnTouched(fn) {
4060
+ const controlChild = this._controlChild();
4061
+ const inputChild = this._inputChild();
4062
+ if (controlChild !== undefined)
4063
+ controlChild.registerOnTouched(fn);
4064
+ else if (inputChild !== undefined)
4065
+ inputChild.nativeElement.onblur = (event) => fn();
4066
+ }
4067
+ setDisabledState(isDisabled) {
4068
+ const controlChild = this._controlChild();
4069
+ const inputChild = this._inputChild();
4070
+ if (controlChild !== undefined && controlChild.setDisabledState !== undefined)
4071
+ controlChild.setDisabledState(isDisabled);
4072
+ else if (inputChild !== undefined) {
4073
+ if (isDisabled)
4074
+ inputChild.nativeElement.setAttribute("disabled", "disabled");
4075
+ else
4076
+ inputChild.nativeElement.removeAttribute("disabled");
3840
4077
  }
3841
4078
  }
3842
- static createFilterForEnum(property, filter) {
3843
- if (filter.matchMode == TranslationKeys.NO_FILTER)
3844
- return undefined;
3845
- const options = property.options;
3846
- if (options === undefined)
3847
- throw Error(`Property ${property.name} has no options`);
3848
- const maxItems = options.maxItems ?? Number.MAX_SAFE_INTEGER;
3849
- const oDataOperator = ODataService.createODataOperator(filter.matchMode);
3850
- // Normal enum
3851
- if (maxItems === 1) {
3852
- const comparisonValue = ODataService.createComparisonValue(property, filter.value, true);
3853
- return `${property.name} ${oDataOperator} ${comparisonValue}`;
3854
- }
3855
- // Flags enum
3856
- if (filter.value === null || filter.value === undefined)
3857
- return undefined;
3858
- const values = Array.isArray(filter.value) ? filter.value : [filter.value];
3859
- const comparisonValues = values.map(v => ODataService.createComparisonValue(property, v, true));
3860
- const filters = comparisonValues.map(v => `${property.name} has ${v}`);
3861
- const concatenatedFilters = filters.join(' and ');
3862
- return `(${concatenatedFilters})`;
4079
+ get PropertyType() {
4080
+ return PropertyType;
3863
4081
  }
3864
- static createODataOperator(matchMode) {
3865
- switch (matchMode) {
3866
- case FilterMatchMode.STARTS_WITH:
3867
- return 'startswith';
3868
- case FilterMatchMode.CONTAINS:
3869
- return 'contains';
3870
- case FilterMatchMode.NOT_CONTAINS:
3871
- return 'not contains';
3872
- case FilterMatchMode.ENDS_WITH:
3873
- return 'endswith';
3874
- case FilterMatchMode.EQUALS:
3875
- return 'eq';
3876
- case FilterMatchMode.NOT_EQUALS:
3877
- return 'ne';
3878
- case FilterMatchMode.IN:
3879
- return 'in';
3880
- case FilterMatchMode.LESS_THAN:
3881
- return 'lt';
3882
- case FilterMatchMode.LESS_THAN_OR_EQUAL_TO:
3883
- return 'le';
3884
- case FilterMatchMode.GREATER_THAN:
3885
- return 'gt';
3886
- case FilterMatchMode.GREATER_THAN_OR_EQUAL_TO:
3887
- return 'ge';
3888
- case FilterMatchMode.IS:
3889
- return 'eq';
3890
- case FilterMatchMode.IS_NOT:
3891
- return 'ne';
3892
- case FilterMatchMode.BEFORE:
3893
- return 'lt';
3894
- case FilterMatchMode.AFTER:
3895
- return 'gt';
3896
- case FilterMatchMode.DATE_AFTER:
3897
- return 'ge';
3898
- case FilterMatchMode.DATE_BEFORE:
3899
- return 'lt';
3900
- case FilterMatchMode.DATE_IS:
3901
- return 'eq';
3902
- case FilterMatchMode.DATE_IS_NOT:
3903
- return 'ne';
3904
- default:
3905
- throw Error(`Unknown matchMode ${matchMode}`);
3906
- }
4082
+ get PropertyWithImage() {
4083
+ return PropertyWithImage;
3907
4084
  }
3908
- static getPropertyType(property, value) {
3909
- if (property.options) {
3910
- if (typeof value === "string" ||
3911
- Array.isArray(value) && value.every(v => typeof v === "string") ||
3912
- property.options.inline?.some(o => property.options.valueField !== undefined && typeof o[property.options.valueField] === "string"))
3913
- return PropertyType.Text;
3914
- return PropertyType.Number;
3915
- }
3916
- return property.type;
4085
+ get dateFormat() {
4086
+ return RestWorldInputSimpleComponent._dateFormat;
3917
4087
  }
3918
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ODataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3919
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ODataService, providedIn: 'root' });
4088
+ // public readonly formControl = computed(() => {
4089
+ // const formGroup = this._controlContainer.control as FormGroup<any>;
4090
+ // return formGroup.controls[this.property().name] as FormControl<SimpleValue | SimpleValue[]>;
4091
+ // });
4092
+ get timeFormat() {
4093
+ return RestWorldInputSimpleComponent._timeFormat;
4094
+ }
4095
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputSimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4096
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputSimpleComponent, isStandalone: true, selector: "rw-input-simple", providers: [{
4097
+ provide: NG_VALUE_ACCESSOR,
4098
+ useExisting: forwardRef(() => RestWorldInputSimpleComponent),
4099
+ multi: true
4100
+ }], viewQueries: [{ propertyName: "_inputChild", first: true, predicate: ["inputElement"], descendants: true, isSignal: true }, { propertyName: "_controlChild", first: true, predicate: NG_VALUE_ACCESSOR, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@switch(property().type) {\r\n @case (PropertyType.Textarea) {\r\n @if (useTemplateDrivenForms()){\r\n <textarea #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n @else {\r\n <textarea #inputElement [formControlProperty]=\"property()\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n }\r\n @case (PropertyType.Date) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Month) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Time) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.DatetimeLocal) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Number) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-inputNumber [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n @else {\r\n <p-inputNumber inputMode=\"decimal\" [formControlProperty]=\"property()\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n }\r\n @case (PropertyType.Bool) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-tri-state-checkbox [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [required]=\"property().required ?? false\"></rw-tri-state-checkbox>\r\n }\r\n @else {\r\n <rw-tri-state-checkbox [formControlProperty]=\"property()\" [required]=\"property().required\"></rw-tri-state-checkbox>\r\n }\r\n }\r\n @case (PropertyType.DatetimeOffset) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Duration) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Image) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-image [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n @else {\r\n <rw-image [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n }\r\n @case (PropertyType.File) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-file [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n @else {\r\n <rw-file [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n }\r\n @default {\r\n <!-- <input [formControlName]=\"property().name\" [id]=\"property().name\" type=\"text\" pInputText class=\"w-full\" [class.p-disabled]=\"property().readOnly\" /> -->\r\n @if (useTemplateDrivenForms()) {\r\n <input #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputText class=\"w-full\" />\r\n }\r\n @else {\r\n <input #inputElement [formControlProperty]=\"property()\" pInputText class=\"w-full\" />\r\n }\r\n }\r\n}\r\n", styles: [".p-inputtext.ng-touched.ng-invalid{border-color:#e24c4c}\n"], dependencies: [{ kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "component", type: TriStateCheckbox, selector: "p-tri-state-checkbox, p-tri-state-checkBox, p-tri-state-check-box, rw-tri-state-checkbox, rw-tri-state-checkBox, rw-tri-state-check-box", inputs: ["value", "name", "disabled", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "model", "variant"], outputs: ["disabledChange", "modelChange", "onChange", "onFocus", "onBlur"] }, { kind: "component", type: RestWorldImageComponent, selector: "rw-image", inputs: ["property", "backgroundColor", "displayCropDialog"], outputs: ["backgroundColorChange", "displayCropDialogChange"] }, { kind: "component", type: RestWorldFileComponent, selector: "rw-file", inputs: ["accept", "fileName"] }, { kind: "directive", type: InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: HalFormsModule }, { kind: "directive", type: FormControlProperty, selector: "[formControlProperty]:not([useTemplateDrivenForms=true])", inputs: ["formControlProperty"] }, { kind: "directive", type: DefaultPropertyValueAccessor, selector: "input:not([type=checkbox])[formControlProperty], textarea[formControlProperty], select[formControlProperty]" }, { kind: "directive", type: PropertyControlStatus, selector: "[formControlProperty]" }, { kind: "directive", type: PropertyAttributes, selector: "[formControlProperty],[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "directive", type: PropertyInputNumberAttributes, selector: "p-inputNumber[formControlProperty], p-inputNumber[propertyAttributes]", inputs: ["formControlProperty", "propertyAttributes"] }, { kind: "directive", type: i2$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3920
4101
  }
3921
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ODataService, decorators: [{
3922
- type: Injectable,
3923
- args: [{
3924
- providedIn: 'root',
3925
- }]
3926
- }] });
3927
-
4102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputSimpleComponent, decorators: [{
4103
+ type: Component,
4104
+ args: [{ selector: 'rw-input-simple', standalone: true, providers: [{
4105
+ provide: NG_VALUE_ACCESSOR,
4106
+ useExisting: forwardRef(() => RestWorldInputSimpleComponent),
4107
+ multi: true
4108
+ }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [DatePicker, InputNumber, TriStateCheckbox, RestWorldImageComponent, RestWorldFileComponent, InputText, HalFormsModule, FormsModule], template: "@switch(property().type) {\r\n @case (PropertyType.Textarea) {\r\n @if (useTemplateDrivenForms()){\r\n <textarea #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n @else {\r\n <textarea #inputElement [formControlProperty]=\"property()\" pInputTextarea class=\"w-full p-inputtextarea p-inputtext p-component p-element\"></textarea>\r\n }\r\n }\r\n @case (PropertyType.Date) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"true\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Month) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showWeek]=\"false\" view=\"month\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Time) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.DatetimeLocal) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Number) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-inputNumber [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n @else {\r\n <p-inputNumber inputMode=\"decimal\" [formControlProperty]=\"property()\" class=\"w-full\" styleClass=\"w-full\"></p-inputNumber>\r\n }\r\n }\r\n @case (PropertyType.Bool) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-tri-state-checkbox [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [required]=\"property().required ?? false\"></rw-tri-state-checkbox>\r\n }\r\n @else {\r\n <rw-tri-state-checkbox [formControlProperty]=\"property()\" [required]=\"property().required\"></rw-tri-state-checkbox>\r\n }\r\n }\r\n @case (PropertyType.DatetimeOffset) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"dateFormat\" [showTime]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Duration) {\r\n @if (useTemplateDrivenForms()) {\r\n <p-datepicker [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n @else {\r\n <p-datepicker [formControlProperty]=\"property()\" [dateFormat]=\"timeFormat\" [showTime]=\"true\" [timeOnly]=\"true\" [showWeek]=\"false\" [showIcon]=\"true\" styleClass=\"w-full\"></p-datepicker>\r\n }\r\n }\r\n @case (PropertyType.Image) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-image [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n @else {\r\n <rw-image [formControlName]=\"property().name\" [property]=\"$any(property())\"></rw-image>\r\n }\r\n }\r\n @case (PropertyType.File) {\r\n @if (useTemplateDrivenForms()) {\r\n <rw-file [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n @else {\r\n <rw-file [formControlName]=\"property().name\" [fileName]=\"property().name\" [accept]=\"$any(property().placeholder)\"></rw-file>\r\n }\r\n }\r\n @default {\r\n <!-- <input [formControlName]=\"property().name\" [id]=\"property().name\" type=\"text\" pInputText class=\"w-full\" [class.p-disabled]=\"property().readOnly\" /> -->\r\n @if (useTemplateDrivenForms()) {\r\n <input #inputElement [propertyAttributes]=\"property()\" [(ngModel)]=\"model\" pInputText class=\"w-full\" />\r\n }\r\n @else {\r\n <input #inputElement [formControlProperty]=\"property()\" pInputText class=\"w-full\" />\r\n }\r\n }\r\n}\r\n", styles: [".p-inputtext.ng-touched.ng-invalid{border-color:#e24c4c}\n"] }]
4109
+ }], ctorParameters: () => [] });
3928
4110
  /**
3929
- * This component is used to display a filter element for a table column.
3930
- * It is used internally by the <rw-table> component, but can also be used in the #filter template of a column in the header of a <p-table>.
4111
+ * A collection of `<rw-form>` elements automatically created from a template.
4112
+ * Does not have any buttons on its own.
4113
+ * If you want buttons, use {@link RestWorldForm} `<rw-form>`.
4114
+ * @example
4115
+ * <rw-form-collection [template]="template" [apiName]="apiName"></rw-form-collection>
3931
4116
  */
3932
- class RestWorldTableColumnFilterElementComponent {
3933
- _formService;
3934
- /**
3935
- * The filter constraint to update when the value changes.
3936
- * This is coming from the $context of the #filter template
3937
- */
3938
- filterConstraint = input.required(...(ngDevMode ? [{ debugName: "filterConstraint" }] : []));
3939
- /**
3940
- * The HAL-Forms property to filter by.
3941
- * This is normally the column.
3942
- */
3943
- property = input.required(...(ngDevMode ? [{ debugName: "property" }] : []));
4117
+ class RestWorldInputTemplateComponent {
3944
4118
  /**
3945
- * The name of the API to use when generating dropdowns.
4119
+ * The name of the API to use for the property.
4120
+ * @required
4121
+ * @remarks This is the name of the API as defined in the `RestWorldClientCollection`.
3946
4122
  */
3947
4123
  apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
3948
4124
  /**
3949
- * The initial value of the filter.
4125
+ * The template to display.
4126
+ * @required
4127
+ * @remarks This is the template that defines the properties to display.
3950
4128
  */
3951
- value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
3952
- form = computed(() => this._formService.createFormGroupFromTemplate(this.template()), ...(ngDevMode ? [{ debugName: "form" }] : []));
3953
- template = computed(() => new Template({
3954
- properties: [this.property()],
3955
- }), ...(ngDevMode ? [{ debugName: "template" }] : []));
3956
- _formValueChangesSubscription;
3957
- constructor(_formService) {
3958
- this._formService = _formService;
3959
- effect(() => {
3960
- this._formValueChangesSubscription?.unsubscribe();
3961
- const form = this.form();
3962
- const property = this.property();
3963
- const value = untracked(() => this.value());
3964
- const formControl = form.controls[property.name];
3965
- this._formValueChangesSubscription = formControl.valueChanges.subscribe(this.setFilterValue.bind(this));
3966
- formControl.setValue(value);
3967
- });
3968
- }
3969
- setFilterValue(value) {
3970
- this.filterConstraint().value = value;
3971
- }
3972
- ngOnDestroy() {
3973
- this._formValueChangesSubscription?.unsubscribe();
3974
- }
3975
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableColumnFilterElementComponent, deps: [{ token: i1$1.FormService }], target: i0.ɵɵFactoryTarget.Component });
3976
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldTableColumnFilterElementComponent, isStandalone: true, selector: "rw-table-column-filter-element", inputs: { filterConstraint: { classPropertyName: "filterConstraint", publicName: "filterConstraint", isSignal: true, isRequired: true, transformFunction: null }, property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: true, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<form [formGroup]=\"form()\">\r\n <rw-input [property]=\"property()\" [apiName]=\"apiName()\"></rw-input>\r\n</form>\r\n", styles: [""], dependencies: [{ kind: "component", type: RestWorldInputComponent, selector: "rw-input" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
4129
+ template = input.required(...(ngDevMode ? [{ debugName: "template" }] : []));
4130
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputTemplateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4131
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldInputTemplateComponent, isStandalone: true, selector: "rw-input-template", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@for (property of template().properties; track property.name) {\r\n <rw-form-element [property]=\"property\" [apiName]=\"apiName()\"></rw-form-element>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: RestWorldFormElementComponent, selector: "rw-form-element" }], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }] });
3977
4132
  }
3978
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableColumnFilterElementComponent, decorators: [{
4133
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldInputTemplateComponent, decorators: [{
3979
4134
  type: Component,
3980
- args: [{ selector: 'rw-table-column-filter-element', imports: [RestWorldInputComponent, ReactiveFormsModule], template: "<form [formGroup]=\"form()\">\r\n <rw-input [property]=\"property()\" [apiName]=\"apiName()\"></rw-input>\r\n</form>\r\n" }]
3981
- }], ctorParameters: () => [{ type: i1$1.FormService }] });
4135
+ args: [{ selector: 'rw-input-template', standalone: true, viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [RestWorldFormElementComponent], template: "@for (property of template().properties; track property.name) {\r\n <rw-form-element [property]=\"property\" [apiName]=\"apiName()\"></rw-form-element>\r\n}\r\n" }]
4136
+ }] });
3982
4137
 
3983
4138
  /**
3984
- * Displays a table based on a search-, an edit-template and a list of items.
3985
- * The search-template is required and used to display the table columns and to filter and sort the items.
3986
- * The edit-template is optional and used to edit the items. For the edit capability, the table must be part of a reactive form.
3987
- * The items are displayed as table rows.
3988
- * The table supports lazy loading, row selection, row menus, and context menus.
3989
- *
4139
+ * A form with Save, Reload and Delete buttons.
4140
+ * If you do not want buttons, use RestWorldFormTemplateComponent <rw-form-template>.
4141
+ * You can also provide your own buttons by passing in a template.
3990
4142
  * @example
3991
- * <rw-table
3992
- * [apiName]="apiName"
3993
- * [searchTemplate]="searchTemplate"
3994
- * [editTemplate]="editTemplate"
3995
- * [rows]="rows"
3996
- * [rowsPerPageOptions]="[10, 25, 50]"
3997
- * [headerMenu]="headerMenu"
3998
- * [rowMenu]="rowMenu"
3999
- * [rowStyleClass]="rowStyleClass"
4000
- * [cellStyleClass]="cellStyleClass"
4001
- * [totalRecords]="totalRecords"
4002
- * [multiSortMeta]="multiSortMeta"
4003
- * [styleClass]="styleClass"
4004
- * [tableStyle]="tableStyle"
4005
- * [scrollable]="scrollable"
4006
- * [scrollHeight]="scrollHeight"
4007
- * [selectionMode]="selectionMode"
4008
- * [rowHover]="rowHover"
4009
- * [selection]="selection"
4010
- * [contextMenuItems]="contextMenuItems"
4011
- * [isLoading]="isLoading"
4012
- * [(selectedRows)]="selectedRows"
4013
- * [(oDataParameters)]="oDataParameters"
4014
- * </rw-table>
4015
- *
4016
- */
4017
- class RestWorldTableComponent {
4018
- _controlContainer;
4019
- _formService;
4020
- PropertyType = PropertyType;
4021
- /**
4022
- * The name of the api.
4023
- * For the editing capability, you must also set the editTemplate and the formArray.
4024
- */
4025
- apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
4026
- /**
4027
- * A function that returns the style class for a cell.
4028
- * @param row The row for which to return the style class.
4029
- * @param column The column for which to return the style class.
4030
- * @param rowIndex The index of the row on the currently displayed page.
4031
- * @param columnIndex The index of the column.
4032
- * @returns The style class for the cell.
4033
- */
4034
- cellStyleClass = input(() => "", ...(ngDevMode ? [{ debugName: "cellStyleClass" }] : []));
4035
- cellStyleClasses = computed(() => this.rows().map((r, ri) => Object.fromEntries(this.columns().map((c, ci) => [c.name, this.cellStyleClass()(r, c, ri, ci)]))), ...(ngDevMode ? [{ debugName: "cellStyleClasses" }] : []));
4036
- columns = computed(() => this.searchTemplate()?.properties.filter(p => p.type !== PropertyType.Hidden) ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : []));
4037
- contextMenu = viewChild("contextMenu", ...(ngDevMode ? [{ debugName: "contextMenu" }] : []));
4038
- contextMenuItems = signal([], ...(ngDevMode ? [{ debugName: "contextMenuItems" }] : []));
4039
- dateFormat = new Date(3333, 10, 22) // months start at 0 in JS
4040
- .toLocaleDateString()
4041
- .replace("22", "dd")
4042
- .replace("11", "MM")
4043
- .replace("3333", "yy")
4044
- .replace("33", "y");
4045
- editProperties = computed(() => this.editTemplate()?.propertiesRecord ?? {}, ...(ngDevMode ? [{ debugName: "editProperties" }] : []));
4143
+ * <rw-form
4144
+ * [template]="template"
4145
+ * apiName="apiName"
4146
+ * rel="rel"
4147
+ * [allowSubmit]="true"
4148
+ * [allowDelete]="true"
4149
+ * [allowReload]="true"
4150
+ * [showSubmit]="true"
4151
+ * [showDelete]="true"
4152
+ * [showReload]="true">
4153
+ * <ng-template #content let-form="form" let-template="template" let-apiName="apiName">
4154
+ * <!-- Custom form content here -->
4155
+ * <!-- This is optional and will replace the default which renders labels and inputs if present -->
4156
+ * </ng-template>
4157
+ * <ng-template #buttons let-form="form" let-template="template" let-apiName="apiName">
4158
+ * <!-- Custom buttons here -->
4159
+ * <!-- This is optional and will replace the default which renders the Save, Reload and Delete buttons if present -->
4160
+ * </ng-template>
4161
+ * </rw-form>
4162
+ */
4163
+ class RestWorldFormComponent {
4164
+ _clients;
4165
+ _confirmationService;
4166
+ _messageService;
4167
+ _formService;
4168
+ _elementRef;
4169
+ _problemService;
4046
4170
  /**
4047
- * The template that is used to edit the items.
4048
- * Bind this to the template that is used to edit the items.
4049
- * Normally this is returned from the backend as part of the hal-forms resource from a list endpoint.
4050
- * For the editing capability, you must also set the apiName and the formArray.
4171
+ * Emitted after the resource has been deleted.
4051
4172
  */
4052
- editTemplate = input(...(ngDevMode ? [undefined, { debugName: "editTemplate" }] : []));
4053
- filters = computed(() => {
4054
- const filter = this.oDataParameters().$filter;
4055
- const properties = this.searchTemplate()?.propertiesRecord;
4056
- if (filter === null || filter === undefined || typeof filter !== "string" || properties === undefined)
4057
- return {};
4058
- return ODataService.createFilterMetadataFromODataFilter(filter, properties);
4059
- }, ...(ngDevMode ? [{ debugName: "filters" }] : []));
4173
+ afterDelete = output();
4060
4174
  /**
4061
- * The form array that contains the form groups for the items.
4062
- * Bind this to the form array that contains the form groups for the items.
4063
- * Each entry in the array represents one row in the currently displayed page of the table.
4064
- * For the editing capability, you must also set the apiName and the editTemplate.
4175
+ * Emitted after the form has been submitted.
4065
4176
  */
4066
- formArray = computed(() => this._controlContainer?.control, ...(ngDevMode ? [{ debugName: "formArray" }] : []));
4177
+ afterSubmit = output();
4067
4178
  /**
4068
- * An optional menu that is displayed at the top right of the table.
4069
- * @see RestWorldMenuButtonComponent
4070
- */
4071
- headerMenu = input([], ...(ngDevMode ? [{ debugName: "headerMenu" }] : []));
4072
- isEditable = computed(() => this.editTemplate() !== undefined && this.formArray() !== undefined && this.apiName() !== undefined, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
4179
+ * Determines whether to enable the delete button.
4180
+ */
4181
+ allowDelete = input(true, ...(ngDevMode ? [{ debugName: "allowDelete" }] : []));
4073
4182
  /**
4074
- * Indicates whether the table is currently loading.
4075
- * Set this to true while loading new items from the backend when reacting to the `onFilterOrSortChanged` event.
4076
- */
4077
- isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
4183
+ * Determines whether to enable the reload button.
4184
+ */
4185
+ allowReload = input(true, ...(ngDevMode ? [{ debugName: "allowReload" }] : []));
4078
4186
  /**
4079
- * Indicates whether the table is lazy loaded.
4080
- * If set to true, sorting and filtering needs to be handled by the `load` event.
4081
- * If set to false, sorting and filtering is handled by the table component itself.
4082
- * The default is `true`.
4083
- * @see load
4084
- */
4085
- lazy = input(true, ...(ngDevMode ? [{ debugName: "lazy" }] : []));
4187
+ * Determines whether to enable the submit button.
4188
+ */
4189
+ allowSubmit = input(true, ...(ngDevMode ? [{ debugName: "allowSubmit" }] : []));
4086
4190
  /**
4087
- * Indicates whether the table has a paginator.
4088
- * If set to true, the table will display a paginator at the bottom.
4089
- * If set to false, the table will not display a paginator and all rows will be displayed at once.
4090
- * The default is `true`.
4091
- * In order to customize the number of rows per page, you can set the `rowsPerPageOptions` property.
4092
- * @see rowsPerPageOptions
4191
+ * The name of the API to use.
4093
4192
  */
4094
- paginator = input(true, ...(ngDevMode ? [{ debugName: "paginator" }] : []));
4095
- multiSortMeta = computed(() => {
4096
- const orderBy = this.oDataParameters().$orderby;
4097
- if (orderBy === null || orderBy === undefined || typeof orderBy !== "string")
4098
- return undefined;
4099
- return orderBy
4100
- .split(",")
4101
- .map(o => o.trim())
4102
- .filter(o => o !== "")
4103
- .map(o => {
4104
- const [field, order] = o.split(" ");
4105
- const orderAsNumber = order?.toLowerCase() === "desc" ? -1 : 1;
4106
- return { field: field, order: orderAsNumber };
4107
- });
4108
- }, ...(ngDevMode ? [{ debugName: "multiSortMeta" }] : []));
4109
- oDataParameters = model({}, ...(ngDevMode ? [{ debugName: "oDataParameters" }] : []));
4110
- reflectParametersInUrl = input(true, ...(ngDevMode ? [{ debugName: "reflectParametersInUrl" }] : []));
4193
+ apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
4111
4194
  /**
4112
- * Indicates whether the table rows are highlighted when the mouse hovers over them.
4195
+ * Determines whether the resource can be deleted.
4113
4196
  */
4114
- rowHover = input(false, ...(ngDevMode ? [{ debugName: "rowHover" }] : []));
4115
- /**
4116
- * A function that returns the menu for a row.
4117
- * Based on the openedByRightClick parameter, the function can return different menus.
4118
- * The menu when it has not been opened by a right click is displayed in an extra column to the right of the table if `showRowMenuAsColumn` is `true`.
4119
- * The menu when it has been opened by a right click is displayed as a context menu if `showRowMenuOnRightClick` is `true`.
4120
- * @param row The row for which to return the menu.
4121
- * @param openedByRightClick Indicates whether the menu was opened by a right click.
4122
- * @returns The menu for the row.
4123
- * @see showRowMenuAsColumn
4124
- * @see showRowMenuOnRightClick
4125
- */
4126
- rowMenu = input(() => [], ...(ngDevMode ? [{ debugName: "rowMenu" }] : []));
4127
- rowMenus = computed(() => {
4128
- return this.showRowMenuAsColumn() ? this.rows().map(r => this.rowMenu()(r, false)) : [];
4129
- }, ...(ngDevMode ? [{ debugName: "rowMenus" }] : []));
4197
+ canDelete = computed(() => this.allowDelete() &&
4198
+ this.template() !== undefined &&
4199
+ this.template().target !== undefined &&
4200
+ this.template().method == "PUT" &&
4201
+ this.formGroup() !== undefined &&
4202
+ this.formGroup()?.value.id !== undefined &&
4203
+ this.formGroup()?.value.timestamp !== undefined &&
4204
+ !this.isLoading(), ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
4130
4205
  /**
4131
- * A function that returns the style class for a row.
4132
- * @param row The row for which to return the style class.
4133
- * @param rowIndex The index of the row on the currently displayed page.
4134
- * @returns The style class for the row.
4206
+ * Determines whether the form can be reloaded.
4135
4207
  */
4136
- rowStyleClass = input(() => "", ...(ngDevMode ? [{ debugName: "rowStyleClass" }] : []));
4137
- rowStyleClasses = computed(() => this.rows().map((r, i) => this.rowStyleClass()(r, i)), ...(ngDevMode ? [{ debugName: "rowStyleClasses" }] : []));
4208
+ canReload = computed(() => this.allowReload() &&
4209
+ this.template() !== undefined &&
4210
+ this.template().target !== undefined &&
4211
+ this.template().title !== undefined &&
4212
+ this.template().properties.some(p => p.name === "id" && p.value !== undefined && p.value !== null && p.value !== 0) &&
4213
+ !this.isLoading(), ...(ngDevMode ? [{ debugName: "canReload" }] : []));
4138
4214
  /**
4139
- * The items that are displayed as table rows.
4140
- * Bind this to the items that are displayed as table rows.
4141
- * Normally this is returned from the backend as part of the hal-forms resource from a list endpoint.
4215
+ * Determines whether the form can be submitted.
4142
4216
  */
4143
- rows = input.required(...(ngDevMode ? [{ debugName: "rows" }] : []));
4144
- rowsBeforeCurrentPage = computed(() => this.oDataParameters().$skip ?? 0, ...(ngDevMode ? [{ debugName: "rowsBeforeCurrentPage" }] : []));
4217
+ canSubmit = computed(() => this.allowSubmit() &&
4218
+ this.template() !== undefined &&
4219
+ this.template().target !== undefined &&
4220
+ !this.isLoading() &&
4221
+ this.formGroup() !== undefined, ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
4145
4222
  /**
4146
- * The number of rows per page.
4147
- * The default is the first element of rowsPerPageOptions.
4223
+ * The form group that represents the form.
4148
4224
  */
4149
- rowsPerPage = computed(() => this.oDataParameters().$top ?? this.rowsPerPageOptions()[0], ...(ngDevMode ? [{ debugName: "rowsPerPage" }] : []));
4225
+ formGroup = computed(() => this._formService.createFormGroupFromTemplate(this.template()), ...(ngDevMode ? [{ debugName: "formGroup" }] : []));
4226
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
4150
4227
  /**
4151
- * The possible values for the number of rows per page.
4152
- * The default is [10, 25, 50].
4228
+ * The rel of the form.
4153
4229
  */
4154
- rowsPerPageOptions = input([10, 25, 50], ...(ngDevMode ? [{ debugName: "rowsPerPageOptions" }] : []));
4230
+ rel = input.required(...(ngDevMode ? [{ debugName: "rel" }] : []));
4155
4231
  /**
4156
- * The height of the scrollable table.
4157
- * The default is "flex".
4158
- */
4159
- scrollHeight = input("flex", ...(ngDevMode ? [{ debugName: "scrollHeight" }] : []));
4232
+ * Determines whether to show the delete button.
4233
+ */
4234
+ showDelete = input(true, ...(ngDevMode ? [{ debugName: "showDelete" }] : []));
4160
4235
  /**
4161
- * Indicates whether the table is scrollable.
4162
- * The default is `true`.
4163
- */
4164
- scrollable = input(true, ...(ngDevMode ? [{ debugName: "scrollable" }] : []));
4236
+ * Determines whether to show the reload button.
4237
+ */
4238
+ showReload = input(true, ...(ngDevMode ? [{ debugName: "showReload" }] : []));
4165
4239
  /**
4166
- * The template that is used to display the table columns and to filter and sort the items.
4167
- * Bind this to the template that is used to display the table columns and to filter and sort the items.
4168
- * Normally this is returned from the backend as part of the hal-forms resource from a list endpoint.
4169
- */
4170
- searchTemplate = input.required(...(ngDevMode ? [{ debugName: "searchTemplate" }] : []));
4240
+ * Determines whether to show the submit button.
4241
+ */
4242
+ showSubmit = input(true, ...(ngDevMode ? [{ debugName: "showSubmit" }] : []));
4171
4243
  /**
4172
- * The currently selected rows.
4244
+ * The template used to render the form.
4173
4245
  */
4174
- selectedRows = model([], ...(ngDevMode ? [{ debugName: "selectedRows" }] : []));
4246
+ template = model.required(...(ngDevMode ? [{ debugName: "template" }] : []));
4175
4247
  /**
4176
- * The mode how rows can be selected.
4177
- * The default is `null` which means rows cannot be selected.
4248
+ * Emitted when the form value changes.
4178
4249
  */
4179
- selectionMode = input(null, ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
4180
- showMenuColumn = computed(() => this.headerMenu().length > 0 || (this.showRowMenuAsColumn() && this.rowMenus().some(m => m.length > 0)), ...(ngDevMode ? [{ debugName: "showMenuColumn" }] : []));
4250
+ valueChanges = output();
4181
4251
  /**
4182
- * Indicates whether the row menu is displayed as a column to the right of the table.
4183
- */
4184
- showRowMenuAsColumn = input(true, ...(ngDevMode ? [{ debugName: "showRowMenuAsColumn" }] : []));
4252
+ * A reference to a template that can be used to render custom buttons for the form.
4253
+ */
4254
+ buttonsRef = contentChild('buttons', ...(ngDevMode ? [{ debugName: "buttonsRef" }] : []));
4185
4255
  /**
4186
- * Indicates whether the row menu is displayed as a context menu when the user right clicks on a row.
4187
- */
4188
- showRowMenuOnRightClick = input(true, ...(ngDevMode ? [{ debugName: "showRowMenuOnRightClick" }] : []));
4256
+ * A reference to a template that can be used to render content before the default buttons.
4257
+ */
4258
+ beforeButtonsRef = contentChild('beforeButtons', ...(ngDevMode ? [{ debugName: "beforeButtonsRef" }] : []));
4189
4259
  /**
4190
- * The style class for the table.
4191
- * The default is "".
4192
- */
4193
- styleClass = input("", ...(ngDevMode ? [{ debugName: "styleClass" }] : []));
4260
+ * A reference to a template that can be used to render content after the default buttons.
4261
+ */
4262
+ afterButtonsRef = contentChild('afterButtons', ...(ngDevMode ? [{ debugName: "afterButtonsRef" }] : []));
4194
4263
  /**
4195
- * The inline style for the table.
4196
- */
4197
- tableStyle = input(...(ngDevMode ? [undefined, { debugName: "tableStyle" }] : []));
4198
- totalRecords = input(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
4199
- urlParameterPrefix = input("", ...(ngDevMode ? [{ debugName: "urlParameterPrefix" }] : []));
4200
- // private _formArray?: FormArray<FormGroup<{ [K in keyof TListItem]: AbstractControl<unknown> }>>;
4201
- _filterMatchModeOptions;
4202
- timeFormat = new Date(1, 1, 1, 22, 33, 44)
4203
- .toLocaleTimeString()
4204
- .replace("22", "hh")
4205
- .replace("33", "mm")
4206
- .replace("44", "ss");
4207
- _initialQueryParamsSet = false;
4208
- _lastUsedFilters = {};
4209
- constructor(_controlContainer, _formService, router, activatedRoute, primeNGConfig) {
4210
- this._controlContainer = _controlContainer;
4211
- this._formService = _formService;
4212
- this._filterMatchModeOptions = {
4213
- text: [TranslationKeys.NO_FILTER, ...primeNGConfig.filterMatchModeOptions.text].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
4214
- numeric: [TranslationKeys.NO_FILTER, ...primeNGConfig.filterMatchModeOptions.numeric].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
4215
- date: [TranslationKeys.NO_FILTER, ...primeNGConfig.filterMatchModeOptions.date].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
4216
- boolean: [TranslationKeys.NO_FILTER, TranslationKeys.EQUALS, TranslationKeys.NOT_EQUALS].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
4217
- enum: [TranslationKeys.NO_FILTER, TranslationKeys.EQUALS, TranslationKeys.NOT_EQUALS].map(o => ({ label: primeNGConfig.getTranslation(o), value: o })),
4218
- };
4219
- // Update the form array on changes
4220
- effect(() => {
4221
- const formArray = this.formArray();
4222
- const editTemplate = this.editTemplate();
4223
- const rows = this.rows();
4224
- if (!this.isEditable() || !formArray || !editTemplate)
4225
- return;
4226
- formArray.clear();
4227
- const newControls = rows
4228
- .map(r => {
4229
- const formGroup = this._formService.createFormGroupFromTemplate(editTemplate);
4230
- formGroup.patchValue(r);
4231
- return formGroup;
4232
- });
4233
- for (const control of newControls)
4234
- formArray.push(control);
4235
- });
4236
- // update the url when the oDataParameters change
4237
- effect(async () => {
4238
- if (!this.reflectParametersInUrl())
4239
- return;
4240
- const urlParameterPrefix = this.urlParameterPrefix();
4241
- const oDataParameters = this.oDataParameters();
4242
- // Set the initial query parameters on the first change
4243
- if (!this._initialQueryParamsSet) {
4244
- this._initialQueryParamsSet = true;
4245
- const oDataParametersFromUrl = ODataService.createParametersFromRoute(activatedRoute, urlParameterPrefix);
4246
- const mergedParameters = { ...oDataParameters, ...oDataParametersFromUrl };
4247
- this.oDataParameters.set(mergedParameters);
4248
- return;
4249
- }
4250
- // Update the query parameters in the url after the first change
4251
- const parameters = this.prefixObjectProperties(oDataParameters, urlParameterPrefix);
4252
- await router.navigate([], { queryParams: parameters, queryParamsHandling: 'merge' });
4264
+ * A reference to a template that can be used to render custom content inside the <form> element instead of the default form.
4265
+ */
4266
+ contentRef = contentChild('content', ...(ngDevMode ? [{ debugName: "contentRef" }] : []));
4267
+ _client = computed(() => this._clients.getClient(this.apiName()), ...(ngDevMode ? [{ debugName: "_client" }] : []));
4268
+ _formValueChangesSubscription;
4269
+ constructor(_clients, _confirmationService, _messageService, _formService, _elementRef, _problemService) {
4270
+ this._clients = _clients;
4271
+ this._confirmationService = _confirmationService;
4272
+ this._messageService = _messageService;
4273
+ this._formService = _formService;
4274
+ this._elementRef = _elementRef;
4275
+ this._problemService = _problemService;
4276
+ // Update the form value changes subscription to always track the current form group.
4277
+ effect(() => {
4278
+ this._formValueChangesSubscription?.unsubscribe();
4279
+ const formGroup = this.formGroup();
4280
+ this._formValueChangesSubscription = formGroup?.valueChanges.subscribe(newValue => this.valueChanges.emit(newValue));
4281
+ this.valueChanges.emit(formGroup?.value);
4253
4282
  });
4254
4283
  }
4255
- load(event) {
4256
- this.fixUserFilterErrors(event.filters);
4257
- // this.multiSortMeta = event.multiSortMeta;
4258
- const currentParameters = this.oDataParameters();
4259
- const searchTemplate = this.searchTemplate();
4260
- if (!searchTemplate || searchTemplate.properties.length === 0)
4261
- return;
4262
- const parameters = ODataService.createParametersFromTableLoadEvent(event, searchTemplate);
4263
- ODataService.createFilterMetadataFromODataFilter(parameters.$filter, searchTemplate.propertiesRecord);
4264
- if (currentParameters.$filter !== parameters.$filter || currentParameters.$orderby !== parameters.$orderby || currentParameters.$top !== parameters.$top || currentParameters.$skip !== parameters.$skip)
4265
- this.oDataParameters.set(parameters);
4266
- }
4267
- openContextMenu(event, row) {
4268
- const contextMenu = this.contextMenu();
4269
- if (!this.showRowMenuOnRightClick() || contextMenu === undefined)
4284
+ async delete() {
4285
+ if (!this.canDelete())
4270
4286
  return;
4271
- this.contextMenuItems.set(this.rowMenu()(row, true));
4272
- contextMenu.show(event);
4273
- event.stopPropagation();
4274
- }
4275
- showInputField(column) {
4276
- if (!this.isEditable())
4277
- return false;
4278
- const editProperty = this.editProperties()[column.name];
4279
- return editProperty !== undefined && editProperty.type !== PropertyType.Hidden && !editProperty.readOnly;
4280
- }
4281
- toColumnFilterType(property) {
4282
- if (!property)
4283
- return ColumnFilterType.text;
4284
- const propertyType = property.type;
4285
- switch (propertyType) {
4286
- case PropertyType.Number:
4287
- case PropertyType.Percent:
4288
- case PropertyType.Currency:
4289
- case PropertyType.Month:
4290
- return ColumnFilterType.numeric;
4291
- case PropertyType.Bool:
4292
- return ColumnFilterType.boolean;
4293
- case PropertyType.Date:
4294
- case PropertyType.DatetimeLocal:
4295
- case PropertyType.DatetimeOffset:
4296
- return ColumnFilterType.date;
4297
- default:
4298
- return property.options ? property.options.link ? ColumnFilterType.numeric : ColumnFilterType.enum : ColumnFilterType.text;
4299
- }
4300
- }
4301
- toMatchModeOptions(property) {
4302
- const columnFilterType = this.toColumnFilterType(property);
4303
- return this._filterMatchModeOptions[columnFilterType];
4304
- }
4305
- toMaxFractionDigits(property) {
4306
- switch (property.type) {
4307
- case PropertyType.Number:
4308
- case PropertyType.Percent:
4309
- case PropertyType.Currency:
4310
- return property.step?.toString().split(".")[1]?.length ?? 2;
4311
- case PropertyType.Month:
4312
- return 0;
4313
- default:
4314
- return undefined;
4287
+ const formGroup = this.formGroup();
4288
+ if (formGroup === undefined)
4289
+ throw new Error("formGroup cannot be undefined.");
4290
+ const template = this.template();
4291
+ if (template === undefined)
4292
+ throw new Error("template cannot be undefined.");
4293
+ // canDelete already checks that the timestamp is present, so the cast is safe.
4294
+ const result = await this._client().deleteByTemplateAndForm(template, formGroup);
4295
+ if (this._problemService.checkResponseAndDisplayErrors(result, formGroup)) {
4296
+ this._messageService.add({ severity: 'success', summary: 'Deleted', detail: 'The resource has been deleted.' });
4297
+ this.afterDelete.emit();
4315
4298
  }
4316
4299
  }
4317
- fixUserFilterError(filterEntry, lastFilterEntry, propertyName) {
4318
- if (!filterEntry)
4300
+ async reload() {
4301
+ const canReload = this.canReload();
4302
+ const template = this.template();
4303
+ if (!canReload || template === undefined)
4319
4304
  return;
4320
- if (lastFilterEntry !== undefined &&
4321
- lastFilterEntry.matchMode !== TranslationKeys.NO_FILTER &&
4322
- filterEntry.matchMode === TranslationKeys.NO_FILTER) {
4323
- // The user changed the mode from something to no filter
4324
- // => We reset the value
4325
- filterEntry.value = null;
4305
+ this.isLoading.set(true);
4306
+ try {
4307
+ const response = await this._client().getForm(template.target);
4308
+ if (this._problemService.checkResponseAndDisplayErrors(response, this.formGroup())) {
4309
+ this.template.set(response.body.getTemplateByTitle(template.title));
4310
+ }
4326
4311
  }
4327
- else if (filterEntry.matchMode === TranslationKeys.NO_FILTER &&
4328
- (lastFilterEntry === undefined || lastFilterEntry.value === null) &&
4329
- filterEntry.value !== null) {
4330
- // The user entered a value into the filter, but forgot to change the mode
4331
- // => We set the match mode to the default for the type that is not no filter
4332
- filterEntry.matchMode = this._filterMatchModeOptions[this.toColumnFilterType(this.searchTemplate().propertiesRecord[propertyName])][1].value;
4312
+ catch (e) {
4313
+ this._messageService.add({ severity: 'error', summary: 'Error', detail: `An unknown error occurred. ${JSON.stringify(e)}`, sticky: true });
4314
+ console.log(e);
4333
4315
  }
4316
+ this.isLoading.set(false);
4334
4317
  }
4335
- fixUserFilterErrors(filters) {
4336
- if (!filters)
4318
+ showDeleteConfirmatioModal() {
4319
+ this._confirmationService.confirm({
4320
+ message: 'Do you really want to delete this resource?',
4321
+ header: 'Confirm delete',
4322
+ icon: 'far fa-trash-alt',
4323
+ accept: () => this.delete()
4324
+ });
4325
+ }
4326
+ async submit() {
4327
+ const formGroup = this.formGroup();
4328
+ const template = this.template();
4329
+ if (formGroup !== undefined) {
4330
+ formGroup.markAllAsTouched();
4331
+ if (!formGroup.valid) {
4332
+ this._messageService.add({
4333
+ severity: 'error',
4334
+ summary: 'Error',
4335
+ detail: 'Please correct the errors before submitting.',
4336
+ });
4337
+ ProblemService.scrollToFirstValidationError(this._elementRef.nativeElement);
4338
+ return;
4339
+ }
4340
+ }
4341
+ if (!this.canSubmit() || formGroup === undefined || template === undefined)
4337
4342
  return;
4338
- Object.entries(filters).forEach(([propertyName, filter]) => {
4339
- const lastFilter = this._lastUsedFilters[propertyName];
4340
- if (Array.isArray(filter)) {
4341
- filter.forEach((filterEntry, index) => this.fixUserFilterError(filterEntry, Array.isArray(lastFilter) ? lastFilter[index] : lastFilter, propertyName));
4343
+ this.isLoading.set(true);
4344
+ try {
4345
+ const response = await this._client().submit(template, formGroup.value);
4346
+ if (!this._problemService.checkResponseAndDisplayErrors(response, formGroup, "Error while saving the resource")) {
4347
+ }
4348
+ else if (response.status == 201) {
4349
+ if (!response.headers.has('Location')) {
4350
+ this._messageService.add({ severity: 'error', summary: 'Error', detail: 'The server returned a 201 Created response, but did not return a Location header.', data: response, sticky: true });
4351
+ return;
4352
+ }
4353
+ this._messageService.add({ severity: 'success', summary: 'Created', detail: 'The resource has been created.' });
4354
+ var createdAtUri = response.headers.get('Location');
4355
+ this.afterSubmit.emit({ location: createdAtUri, status: 201 });
4342
4356
  }
4343
4357
  else {
4344
- this.fixUserFilterError(filter, Array.isArray(lastFilter) ? lastFilter[0] : lastFilter, propertyName);
4358
+ const responseResource = response.body;
4359
+ const newTemplate = responseResource.getTemplateByTitle(template.title);
4360
+ this.template.set(newTemplate);
4361
+ this._messageService.add({ severity: 'success', summary: 'Saved', detail: 'The resource has been saved.' });
4362
+ this.afterSubmit.emit({ old: template, new: newTemplate, status: 200 });
4345
4363
  }
4346
- });
4347
- this._lastUsedFilters = JSON.parse(JSON.stringify(filters));
4364
+ }
4365
+ catch (e) {
4366
+ this._messageService.add({ severity: 'error', summary: 'Error', detail: `An unknown error occurred. ${JSON.stringify(e)}`, sticky: true });
4367
+ console.log(e);
4368
+ }
4369
+ this.isLoading.set(false);
4348
4370
  }
4349
- prefixObjectProperties(obj, prefix) {
4350
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => [`${prefix}${key}`, value]));
4371
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.ConfirmationService }, { token: i2$1.MessageService }, { token: i1$1.FormService }, { token: i0.ElementRef }, { token: ProblemService }], target: i0.ɵɵFactoryTarget.Component });
4372
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldFormComponent, isStandalone: true, selector: "rw-form", inputs: { allowDelete: { classPropertyName: "allowDelete", publicName: "allowDelete", isSignal: true, isRequired: false, transformFunction: null }, allowReload: { classPropertyName: "allowReload", publicName: "allowReload", isSignal: true, isRequired: false, transformFunction: null }, allowSubmit: { classPropertyName: "allowSubmit", publicName: "allowSubmit", isSignal: true, isRequired: false, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: true, transformFunction: null }, showDelete: { classPropertyName: "showDelete", publicName: "showDelete", isSignal: true, isRequired: false, transformFunction: null }, showReload: { classPropertyName: "showReload", publicName: "showReload", isSignal: true, isRequired: false, transformFunction: null }, showSubmit: { classPropertyName: "showSubmit", publicName: "showSubmit", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { afterDelete: "afterDelete", afterSubmit: "afterSubmit", template: "templateChange", valueChanges: "valueChanges" }, queries: [{ propertyName: "buttonsRef", first: true, predicate: ["buttons"], descendants: true, isSignal: true }, { propertyName: "beforeButtonsRef", first: true, predicate: ["beforeButtons"], descendants: true, isSignal: true }, { propertyName: "afterButtonsRef", first: true, predicate: ["afterButtons"], descendants: true, isSignal: true }, { propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (formGroup() !== undefined && template() !== undefined) {\n <form [formGroup]=\"formGroup()\" (ngSubmit)=\"submit()\">\n <div class=\"blockable-container\">\n <div class=\"blockable-element\">\n <div class=\"grid field\">\n <div class=\"col-12 md:col-10 md:col-offset-2\">\n <rw-validation-errors [form]=\"formGroup()\"></rw-validation-errors>\n </div>\n </div>\n <ng-template #defaultContent>\n <rw-input-template [template]=\"template()\" [apiName]=\"apiName()\"></rw-input-template>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"contentRef() ?? defaultContent; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n @if (isLoading()) {\n <div class=\"blockable-overlay\">\n <p-progressSpinner></p-progressSpinner>\n </div>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"col\">\n <div class=\"flex justify-content-end w-full\">\n <ng-template #defaultButtons>\n @if (beforeButtonsRef(); as beforeButtons) {\n <ng-container\n *ngTemplateOutlet=\"beforeButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n <button pButton pRipple type=\"submit\" label=\"Save\" icon=\"far fa-save\" class=\"mx-2 p-button-success\"\n [disabled]=\"!allowSubmit()\"></button>\n <button pButton pRipple type=\"button\" label=\"Reload\" icon=\"fas fa-redo\" class=\"mx-2 p-button-info\"\n (click)=\"reload()\" [disabled]=\"!canReload()\"></button>\n <button pButton pRipple type=\"button\" label=\"Delete\" icon=\"far fa-trash-alt\"\n class=\"ml-2 p-button-danger\" (click)=\"showDeleteConfirmatioModal()\"\n [disabled]=\"!canDelete()\"></button>\n @if (afterButtonsRef(); as afterButtons) {\n <ng-container\n *ngTemplateOutlet=\"afterButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"buttonsRef() ?? defaultButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n </div>\n </div>\n </form>\n}\n", styles: [".blockable-container{display:grid;place-items:center;grid-template-areas:\"inner\"}.blockable-element{grid-area:inner;width:100%}.blockable-overlay{grid-area:inner;height:100%;width:100%;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:1}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: RestWorldValidationErrorsComponent, selector: "rw-validation-errors", inputs: ["form", "property"] }, { kind: "component", type: RestWorldInputTemplateComponent, selector: "rw-input-template", inputs: ["apiName", "template"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i6$1.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i8.Ripple, selector: "[pRipple]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
4373
+ }
4374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldFormComponent, decorators: [{
4375
+ type: Component,
4376
+ args: [{ selector: 'rw-form', standalone: true, imports: [ReactiveFormsModule, RestWorldValidationErrorsComponent, RestWorldInputTemplateComponent, ProgressSpinnerModule, ButtonModule, RippleModule, NgTemplateOutlet], template: "@if (formGroup() !== undefined && template() !== undefined) {\n <form [formGroup]=\"formGroup()\" (ngSubmit)=\"submit()\">\n <div class=\"blockable-container\">\n <div class=\"blockable-element\">\n <div class=\"grid field\">\n <div class=\"col-12 md:col-10 md:col-offset-2\">\n <rw-validation-errors [form]=\"formGroup()\"></rw-validation-errors>\n </div>\n </div>\n <ng-template #defaultContent>\n <rw-input-template [template]=\"template()\" [apiName]=\"apiName()\"></rw-input-template>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"contentRef() ?? defaultContent; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n @if (isLoading()) {\n <div class=\"blockable-overlay\">\n <p-progressSpinner></p-progressSpinner>\n </div>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"col\">\n <div class=\"flex justify-content-end w-full\">\n <ng-template #defaultButtons>\n @if (beforeButtonsRef(); as beforeButtons) {\n <ng-container\n *ngTemplateOutlet=\"beforeButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n <button pButton pRipple type=\"submit\" label=\"Save\" icon=\"far fa-save\" class=\"mx-2 p-button-success\"\n [disabled]=\"!allowSubmit()\"></button>\n <button pButton pRipple type=\"button\" label=\"Reload\" icon=\"fas fa-redo\" class=\"mx-2 p-button-info\"\n (click)=\"reload()\" [disabled]=\"!canReload()\"></button>\n <button pButton pRipple type=\"button\" label=\"Delete\" icon=\"far fa-trash-alt\"\n class=\"ml-2 p-button-danger\" (click)=\"showDeleteConfirmatioModal()\"\n [disabled]=\"!canDelete()\"></button>\n @if (afterButtonsRef(); as afterButtons) {\n <ng-container\n *ngTemplateOutlet=\"afterButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n }\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"buttonsRef() ?? defaultButtons; context: { form: formGroup, template: template, apiName: apiName() }\"></ng-container>\n </div>\n </div>\n </div>\n </form>\n}\n", styles: [".blockable-container{display:grid;place-items:center;grid-template-areas:\"inner\"}.blockable-element{grid-area:inner;width:100%}.blockable-overlay{grid-area:inner;height:100%;width:100%;background-color:#0006;display:flex;align-items:center;justify-content:center;z-index:1}\n"] }]
4377
+ }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.ConfirmationService }, { type: i2$1.MessageService }, { type: i1$1.FormService }, { type: i0.ElementRef }, { type: ProblemService }] });
4378
+
4379
+ /**
4380
+ * Component for navigating to a resource by its ID.
4381
+ *
4382
+ * @remarks
4383
+ * This component is used to navigate to a resource by its ID. It takes in the API name, the `rel` of the resource, and an optional `urlPrefix` to use for the URL that is returned from the backend. It also provides a form for entering the ID of the resource to navigate to.
4384
+ */
4385
+ class RestWorldIdNavigationComponent {
4386
+ _clients;
4387
+ _messageService;
4388
+ _router;
4389
+ _route;
4390
+ apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
4391
+ rel = input.required(...(ngDevMode ? [{ debugName: "rel" }] : []));
4392
+ /**
4393
+ * A prefix to use for the URL that is returned from the backend.
4394
+ * If none is provided, a relative navigation will be performed which means that the last part of the current URL is replaced with the one from the backend.
4395
+ */
4396
+ urlPrefix = input(...(ngDevMode ? [undefined, { debugName: "urlPrefix" }] : []));
4397
+ idNavigationForm = new FormGroup({
4398
+ id: new FormControl(null, Validators.compose([Validators.min(1), Validators.max(Number.MAX_SAFE_INTEGER)]))
4399
+ });
4400
+ _client = computed(() => this._clients.getClient(this.apiName()), ...(ngDevMode ? [{ debugName: "_client" }] : []));
4401
+ constructor(_clients, _messageService, _router, _route) {
4402
+ this._clients = _clients;
4403
+ this._messageService = _messageService;
4404
+ this._router = _router;
4405
+ this._route = _route;
4351
4406
  }
4352
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableComponent, deps: [{ token: i2$3.ControlContainer }, { token: i1$1.FormService }, { token: i3$4.Router }, { token: i3$4.ActivatedRoute }, { token: i4$2.PrimeNG }], target: i0.ɵɵFactoryTarget.Component });
4353
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RestWorldTableComponent, isStandalone: true, selector: "rw-table", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, cellStyleClass: { classPropertyName: "cellStyleClass", publicName: "cellStyleClass", isSignal: true, isRequired: false, transformFunction: null }, editTemplate: { classPropertyName: "editTemplate", publicName: "editTemplate", isSignal: true, isRequired: false, transformFunction: null }, headerMenu: { classPropertyName: "headerMenu", publicName: "headerMenu", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, paginator: { classPropertyName: "paginator", publicName: "paginator", isSignal: true, isRequired: false, transformFunction: null }, oDataParameters: { classPropertyName: "oDataParameters", publicName: "oDataParameters", isSignal: true, isRequired: false, transformFunction: null }, reflectParametersInUrl: { classPropertyName: "reflectParametersInUrl", publicName: "reflectParametersInUrl", isSignal: true, isRequired: false, transformFunction: null }, rowHover: { classPropertyName: "rowHover", publicName: "rowHover", isSignal: true, isRequired: false, transformFunction: null }, rowMenu: { classPropertyName: "rowMenu", publicName: "rowMenu", isSignal: true, isRequired: false, transformFunction: null }, rowStyleClass: { classPropertyName: "rowStyleClass", publicName: "rowStyleClass", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, searchTemplate: { classPropertyName: "searchTemplate", publicName: "searchTemplate", isSignal: true, isRequired: true, transformFunction: null }, selectedRows: { classPropertyName: "selectedRows", publicName: "selectedRows", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, showRowMenuAsColumn: { classPropertyName: "showRowMenuAsColumn", publicName: "showRowMenuAsColumn", isSignal: true, isRequired: false, transformFunction: null }, showRowMenuOnRightClick: { classPropertyName: "showRowMenuOnRightClick", publicName: "showRowMenuOnRightClick", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, tableStyle: { classPropertyName: "tableStyle", publicName: "tableStyle", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, urlParameterPrefix: { classPropertyName: "urlParameterPrefix", publicName: "urlParameterPrefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { oDataParameters: "oDataParametersChange", selectedRows: "selectedRowsChange" }, viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true, isSignal: true }], ngImport: i0, template: "<p-table\r\n #table\r\n [value]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [lazy]=\"lazy()\"\r\n [lazyLoadOnInit]=\"false\"\r\n (onLazyLoad)=\"load($event)\"\r\n responsiveLayout=\"scroll\"\r\n [paginator]=\"paginator()\"\r\n [rowsPerPageOptions]=\"rowsPerPageOptions()\"\r\n [rows]=\"rowsPerPage()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [loading]=\"isLoading()\"\r\n sortMode=\"multiple\"\r\n [multiSortMeta]=\"multiSortMeta()\"\r\n [styleClass]=\"styleClass()\"\r\n [tableStyle]=\"tableStyle()\"\r\n [scrollable]=\"scrollable()\"\r\n [scrollHeight]=\"scrollHeight()\"\r\n [selectionMode]=\"selectionMode()\"\r\n [rowHover]=\"rowHover()\"\r\n [(selection)]=\"selectedRows\"\r\n [filters]=\"$any(filters())\"\r\n [first]=\"rowsBeforeCurrentPage()\"\r\n >\r\n\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @for (col of columns; track col) {\r\n <th [pSortableColumn]=\"col.name\">\r\n <div class=\"p-d-flex p-jc-between p-ai-center gap-1\">\r\n {{col.prompt}}\r\n @if(!col.readOnly) {\r\n <p-sortIcon [field]=\"col.name\"></p-sortIcon>\r\n <p-columnFilter #f\r\n [type]=\"toColumnFilterType(col.type)\"\r\n [maxFractionDigits]=\"toMaxFractionDigits(col)\"\r\n matchMode=\"noFilter\"\r\n [matchModeOptions]=\"toMatchModeOptions(col)\"\r\n [showMatchModes]=\"true\"\r\n [field]=\"col.name\"\r\n display=\"menu\"\r\n [maxConstraints]=\"100\"\r\n [class.has-filter]=\"f.hasFilter\">\r\n <ng-template #filter let-value let-filterConstraint=\"filterConstraint\" let-field=\"field\">\r\n <rw-table-column-filter-element [property]=\"col\" [value]=\"value\" [apiName]=\"apiName()\" [filterConstraint]=\"filterConstraint\"></rw-table-column-filter-element>\r\n </ng-template>\r\n </p-columnFilter>\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (showMenuColumn()) {\r\n <th>\r\n <rw-menu-button [items]=\"headerMenu()\"></rw-menu-button>\r\n </th>\r\n }\r\n </tr>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"body\" let-entity let-columns=\"columns\" let-rowIndex=\"rowIndex\">\r\n <tr (contextmenu)=\"openContextMenu($event, entity)\" [pSelectableRow]=\"entity\" [pSelectableRowDisabled]=\"selectionMode() === null\" [className]=\"rowStyleClasses()[rowIndex - (table.first ?? 0)]\">\r\n @for (col of columns; track col) {\r\n <td [className]=\"cellStyleClasses()[rowIndex - (table.first ?? 0)][col.name]\">\r\n @let fomrArray = formArray();\r\n @if (showInputField(col) && fomrArray) {\r\n <ng-container [formGroup]=\"fomrArray.controls[rowIndex - (table.first ?? 0)]\" >\r\n <rw-input [apiName]=\"apiName()\" [property]=\"editProperties()[col.name]!\"></rw-input>\r\n </ng-container>\r\n }\r\n @else {\r\n <rw-display [property]=\"col\" [value]=\"entity[col.name]\" [apiName]=\"apiName()!\"></rw-display>\r\n }\r\n </td>\r\n }\r\n @if (showMenuColumn()) {\r\n <td>\r\n @if (showRowMenuAsColumn()) {\r\n <rw-menu-button [items]=\"rowMenus()[rowIndex - (table.first ?? 0)]\"></rw-menu-button>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n </ng-template>\r\n</p-table>\r\n\r\n<p-contextMenu #contextMenu appendTo=\"body\" [model]=\"contextMenuItems()\"></p-contextMenu>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}::ng-deep rw-table rw-label.md\\:col-2{width:100%;font-weight:600}::ng-deep rw-table .p-d-flex{display:flex}::ng-deep rw-table .p-ai-center{align-items:center}::ng-deep rw-table .has-filter filtericon{color:var(--p-datatable-header-cell-selected-color)}::ng-deep rw-table p-columnfilter .p-datatable-column-filter-button{width:unset;height:23px!important;padding-block-end:0;padding-block-start:0}\n"], dependencies: [{ kind: "ngmodule", type: TableModule }, { kind: "component", type: i5.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i5.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i5.SelectableRow, selector: "[pSelectableRow]", inputs: ["pSelectableRow", "pSelectableRowIndex", "pSelectableRowDisabled"] }, { kind: "component", type: i5.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i5.ColumnFilter, selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps"], outputs: ["onShow", "onHide"] }, { kind: "component", type: RestWorldMenuButtonComponent, selector: "rw-menu-button", inputs: ["items"] }, { kind: "component", type: RestWorldInputComponent, selector: "rw-input" }, { kind: "component", type: RestWorldDisplayComponent, selector: "rw-display", inputs: ["apiName", "property", "value"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i7.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo"], outputs: ["onShow", "onHide"] }, { kind: "component", type: RestWorldTableColumnFilterElementComponent, selector: "rw-table-column-filter-element", inputs: ["filterConstraint", "property", "apiName", "value"] }], viewProviders: [{
4354
- provide: ControlContainer,
4355
- deps: [[Optional, FormArrayName]],
4356
- useFactory: (arrayName) => arrayName,
4357
- }] });
4407
+ async navigateById() {
4408
+ const rel = this.rel();
4409
+ if (!rel)
4410
+ throw new Error('The "rel" must be set through the uri of this page for the ID navigation to work.');
4411
+ if (!this.idNavigationForm.valid) {
4412
+ this._messageService.add({ detail: 'You must enter a valid ID to naviage to.', severity: 'error' });
4413
+ return;
4414
+ }
4415
+ var idToNavigateTo = this.idNavigationForm.controls.id.value;
4416
+ var client = this._client();
4417
+ var response = await client.getList(rel, { $filter: `id eq ${idToNavigateTo}` });
4418
+ if (!response.ok || ProblemDetails.isProblemDetails(response.body) || !response.body) {
4419
+ this._messageService.add({ severity: 'error', summary: 'Error', detail: 'Error while loading the resources from the API.', data: response });
4420
+ return;
4421
+ }
4422
+ var resource = response.body?._embedded?.items?.[0];
4423
+ if (!resource) {
4424
+ this._messageService.add({ severity: 'error', summary: 'Error', detail: 'No resource found with the specified ID.' });
4425
+ return;
4426
+ }
4427
+ const urlPrefix = this.urlPrefix();
4428
+ if (urlPrefix !== undefined)
4429
+ await this._router.navigate([urlPrefix, resource._links.self[0].href]);
4430
+ else
4431
+ await this._router.navigate(["..", resource._links.self[0].href], { relativeTo: this._route });
4432
+ this.idNavigationForm.reset();
4433
+ }
4434
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldIdNavigationComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.MessageService }, { token: i3$3.Router }, { token: i3$3.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
4435
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.1", type: RestWorldIdNavigationComponent, isStandalone: true, selector: "rw-id-navigation", inputs: { apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: true, transformFunction: null }, urlPrefix: { classPropertyName: "urlPrefix", publicName: "urlPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<form [formGroup]=\"idNavigationForm\" (ngSubmit)=\"navigateById()\" class=\"mr-3\">\n <div class=\"p-inputgroup\">\n <p-inputNumber formControlName=\"id\" placeholder=\"Navigate by ID\"></p-inputNumber>\n <button type=\"submit\" pButton pRipple icon=\"fa-solid fa-arrow-right\"></button>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i1$5.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
4358
4436
  }
4359
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldTableComponent, decorators: [{
4437
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RestWorldIdNavigationComponent, decorators: [{
4360
4438
  type: Component,
4361
- args: [{ selector: 'rw-table', standalone: true, viewProviders: [{
4362
- provide: ControlContainer,
4363
- deps: [[Optional, FormArrayName]],
4364
- useFactory: (arrayName) => arrayName,
4365
- }], imports: [TableModule, RestWorldMenuButtonComponent, RestWorldInputComponent, RestWorldDisplayComponent, ReactiveFormsModule, ContextMenuModule, RestWorldTableColumnFilterElementComponent, RestWorldTableColumnFilterElementComponent], template: "<p-table\r\n #table\r\n [value]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [lazy]=\"lazy()\"\r\n [lazyLoadOnInit]=\"false\"\r\n (onLazyLoad)=\"load($event)\"\r\n responsiveLayout=\"scroll\"\r\n [paginator]=\"paginator()\"\r\n [rowsPerPageOptions]=\"rowsPerPageOptions()\"\r\n [rows]=\"rowsPerPage()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [loading]=\"isLoading()\"\r\n sortMode=\"multiple\"\r\n [multiSortMeta]=\"multiSortMeta()\"\r\n [styleClass]=\"styleClass()\"\r\n [tableStyle]=\"tableStyle()\"\r\n [scrollable]=\"scrollable()\"\r\n [scrollHeight]=\"scrollHeight()\"\r\n [selectionMode]=\"selectionMode()\"\r\n [rowHover]=\"rowHover()\"\r\n [(selection)]=\"selectedRows\"\r\n [filters]=\"$any(filters())\"\r\n [first]=\"rowsBeforeCurrentPage()\"\r\n >\r\n\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @for (col of columns; track col) {\r\n <th [pSortableColumn]=\"col.name\">\r\n <div class=\"p-d-flex p-jc-between p-ai-center gap-1\">\r\n {{col.prompt}}\r\n @if(!col.readOnly) {\r\n <p-sortIcon [field]=\"col.name\"></p-sortIcon>\r\n <p-columnFilter #f\r\n [type]=\"toColumnFilterType(col.type)\"\r\n [maxFractionDigits]=\"toMaxFractionDigits(col)\"\r\n matchMode=\"noFilter\"\r\n [matchModeOptions]=\"toMatchModeOptions(col)\"\r\n [showMatchModes]=\"true\"\r\n [field]=\"col.name\"\r\n display=\"menu\"\r\n [maxConstraints]=\"100\"\r\n [class.has-filter]=\"f.hasFilter\">\r\n <ng-template #filter let-value let-filterConstraint=\"filterConstraint\" let-field=\"field\">\r\n <rw-table-column-filter-element [property]=\"col\" [value]=\"value\" [apiName]=\"apiName()\" [filterConstraint]=\"filterConstraint\"></rw-table-column-filter-element>\r\n </ng-template>\r\n </p-columnFilter>\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (showMenuColumn()) {\r\n <th>\r\n <rw-menu-button [items]=\"headerMenu()\"></rw-menu-button>\r\n </th>\r\n }\r\n </tr>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"body\" let-entity let-columns=\"columns\" let-rowIndex=\"rowIndex\">\r\n <tr (contextmenu)=\"openContextMenu($event, entity)\" [pSelectableRow]=\"entity\" [pSelectableRowDisabled]=\"selectionMode() === null\" [className]=\"rowStyleClasses()[rowIndex - (table.first ?? 0)]\">\r\n @for (col of columns; track col) {\r\n <td [className]=\"cellStyleClasses()[rowIndex - (table.first ?? 0)][col.name]\">\r\n @let fomrArray = formArray();\r\n @if (showInputField(col) && fomrArray) {\r\n <ng-container [formGroup]=\"fomrArray.controls[rowIndex - (table.first ?? 0)]\" >\r\n <rw-input [apiName]=\"apiName()\" [property]=\"editProperties()[col.name]!\"></rw-input>\r\n </ng-container>\r\n }\r\n @else {\r\n <rw-display [property]=\"col\" [value]=\"entity[col.name]\" [apiName]=\"apiName()!\"></rw-display>\r\n }\r\n </td>\r\n }\r\n @if (showMenuColumn()) {\r\n <td>\r\n @if (showRowMenuAsColumn()) {\r\n <rw-menu-button [items]=\"rowMenus()[rowIndex - (table.first ?? 0)]\"></rw-menu-button>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n </ng-template>\r\n</p-table>\r\n\r\n<p-contextMenu #contextMenu appendTo=\"body\" [model]=\"contextMenuItems()\"></p-contextMenu>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}::ng-deep rw-table rw-label.md\\:col-2{width:100%;font-weight:600}::ng-deep rw-table .p-d-flex{display:flex}::ng-deep rw-table .p-ai-center{align-items:center}::ng-deep rw-table .has-filter filtericon{color:var(--p-datatable-header-cell-selected-color)}::ng-deep rw-table p-columnfilter .p-datatable-column-filter-button{width:unset;height:23px!important;padding-block-end:0;padding-block-start:0}\n"] }]
4366
- }], ctorParameters: () => [{ type: i2$3.ControlContainer }, { type: i1$1.FormService }, { type: i3$4.Router }, { type: i3$4.ActivatedRoute }, { type: i4$2.PrimeNG }] });
4367
- var ColumnFilterType;
4368
- (function (ColumnFilterType) {
4369
- ColumnFilterType["text"] = "text";
4370
- ColumnFilterType["numeric"] = "numeric";
4371
- ColumnFilterType["boolean"] = "boolean";
4372
- ColumnFilterType["date"] = "date";
4373
- ColumnFilterType["enum"] = "enum";
4374
- })(ColumnFilterType || (ColumnFilterType = {}));
4439
+ args: [{ selector: 'rw-id-navigation', standalone: true, imports: [InputNumberModule, ButtonModule, ReactiveFormsModule], template: "<form [formGroup]=\"idNavigationForm\" (ngSubmit)=\"navigateById()\" class=\"mr-3\">\n <div class=\"p-inputgroup\">\n <p-inputNumber formControlName=\"id\" placeholder=\"Navigate by ID\"></p-inputNumber>\n <button type=\"submit\" pButton pRipple icon=\"fa-solid fa-arrow-right\"></button>\n </div>\n</form>\n" }]
4440
+ }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.MessageService }, { type: i3$3.Router }, { type: i3$3.ActivatedRoute }] });
4375
4441
 
4376
4442
  class RestWorldOptions {
4377
4443
  BaseUrl;
@@ -4677,7 +4743,7 @@ class OpenTelemetryService {
4677
4743
  getTracer() {
4678
4744
  return this._tracer;
4679
4745
  }
4680
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: OpenTelemetryService, deps: [{ token: SettingsService }, { token: i3$4.Router }], target: i0.ɵɵFactoryTarget.Injectable });
4746
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: OpenTelemetryService, deps: [{ token: SettingsService }, { token: i3$3.Router }], target: i0.ɵɵFactoryTarget.Injectable });
4681
4747
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: OpenTelemetryService, providedIn: "root" });
4682
4748
  }
4683
4749
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: OpenTelemetryService, decorators: [{
@@ -4685,7 +4751,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
4685
4751
  args: [{
4686
4752
  providedIn: "root"
4687
4753
  }]
4688
- }], ctorParameters: () => [{ type: SettingsService }, { type: i3$4.Router }] });
4754
+ }], ctorParameters: () => [{ type: SettingsService }, { type: i3$3.Router }] });
4689
4755
 
4690
4756
  /**
4691
4757
  * Component for editing a resource in the RESTworld application.
@@ -4707,14 +4773,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
4707
4773
  class RESTworldEditViewComponent {
4708
4774
  _clients;
4709
4775
  _messageService;
4776
+ _problemService;
4710
4777
  _router;
4711
4778
  extraTabsRef;
4712
4779
  idNavigationForm = new FormGroup({
4713
4780
  id: new FormControl(null, Validators.compose([Validators.min(1), Validators.max(Number.MAX_SAFE_INTEGER)]))
4714
4781
  });
4715
- constructor(_clients, _messageService, _router, valdemortConfig) {
4782
+ constructor(_clients, _messageService, _problemService, _router, valdemortConfig) {
4716
4783
  this._clients = _clients;
4717
4784
  this._messageService = _messageService;
4785
+ this._problemService = _problemService;
4718
4786
  this._router = _router;
4719
4787
  valdemortConfig.errorClasses = "p-error text-sm";
4720
4788
  }
@@ -4733,9 +4801,19 @@ class RESTworldEditViewComponent {
4733
4801
  * The name of the API to load the resource from.
4734
4802
  */
4735
4803
  apiName = input.required(...(ngDevMode ? [{ debugName: "apiName" }] : []));
4804
+ newHref = computed(() => this.resource.value()?.findLink('new')?.href, ...(ngDevMode ? [{ debugName: "newHref" }] : []));
4736
4805
  templates = resource({
4806
+ params: () => ({ apiName: this.apiName(), resource: this.resource.value() }),
4807
+ loader: ({ params }) => this.loadTemplatesInternal(params.apiName, params.resource)
4808
+ });
4809
+ resource = resource({
4737
4810
  params: () => ({ apiName: this.apiName(), uri: this.uri() }),
4738
- loader: ({ params }) => this.loadInternal(params.apiName, params.uri)
4811
+ loader: async ({ params }) => {
4812
+ const client = this._clients.getClient(params.apiName);
4813
+ const resourceResponse = await client.getSingleByUri(params.uri);
4814
+ this._problemService.checkResponseDisplayErrorsAndThrow(resourceResponse);
4815
+ return resourceResponse.body;
4816
+ }
4739
4817
  });
4740
4818
  displayTab = model("Loading", ...(ngDevMode ? [{ debugName: "displayTab" }] : []));
4741
4819
  async afterSubmit($event) {
@@ -4744,14 +4822,17 @@ class RESTworldEditViewComponent {
4744
4822
  }
4745
4823
  }
4746
4824
  async afterDelete() {
4747
- await this._router.navigate(["list", this.apiName, this.rel()]);
4825
+ await this._router.navigate(["list", this.apiName(), this.rel()]);
4826
+ }
4827
+ createNew() {
4828
+ return this._router.navigate(["edit", this.apiName(), this.rel(), this.newHref()]);
4748
4829
  }
4749
- async loadInternal(apiName, uri) {
4750
- if (!apiName || !uri)
4830
+ async loadTemplatesInternal(apiName, resource) {
4831
+ if (!apiName || !resource)
4751
4832
  return [];
4752
4833
  try {
4753
4834
  const client = this._clients.getClient(apiName);
4754
- const templatesOrProblemDetails = await client.getAllTemplatesByUri(uri);
4835
+ const templatesOrProblemDetails = await client.getAllTemplates(resource);
4755
4836
  // Check if we got ProblemDetails instead of Templates
4756
4837
  if (ProblemDetails.isProblemDetails(templatesOrProblemDetails)) {
4757
4838
  this._messageService.add({
@@ -4778,13 +4859,13 @@ class RESTworldEditViewComponent {
4778
4859
  return [];
4779
4860
  }
4780
4861
  }
4781
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RESTworldEditViewComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.MessageService }, { token: i3$4.Router }, { token: i4$3.ValdemortConfig }], target: i0.ɵɵFactoryTarget.Component });
4782
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RESTworldEditViewComponent, isStandalone: true, selector: "rw-edit", inputs: { rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: true, transformFunction: null }, uri: { classPropertyName: "uri", publicName: "uri", isSignal: true, isRequired: true, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, displayTab: { classPropertyName: "displayTab", publicName: "displayTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { displayTab: "displayTabChange" }, queries: [{ propertyName: "extraTabsRef", first: true, predicate: ["extraTabs"], descendants: true }], ngImport: i0, template: "<div class=\"grid\">\r\n <div class=\"col-12 md:col-10\">\r\n <h1>Edit resource</h1>\r\n </div>\r\n <div class=\"col-12 md:col-2 align-items-center justify-content-end flex\">\r\n <rw-id-navigation [apiName]=\"apiName()\" [rel]=\"rel()\"></rw-id-navigation>\r\n </div>\r\n</div>\r\n\r\n<p-tabs [(value)]=\"displayTab\">\r\n <p-tablist>\r\n @if (templates.isLoading()) {\r\n <p-tab value=\"Loading\">Loading</p-tab>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tab [value]=\"template.title ?? i\">{{template.title}}</p-tab>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tab value=\"Extra\">Extra</p-tab>\r\n }\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @if (templates.isLoading()) {\r\n <p-tabpanel value=\"Loading\">\r\n @for(i of [1, 2, 3, 4, 5]; track i) {\r\n <div class=\"grid field\">\r\n <p-skeleton class=\"col-12 mb-2 md:col-2 md:mb-0\" height=\"39px\"></p-skeleton>\r\n <div class=\"col-12 md:col-10\">\r\n <p-skeleton class=\"w-full\" height=\"39px\"></p-skeleton>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"grid\">\r\n <div class=\"col\">\r\n <div class=\"flex justify-content-end w-full\">\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n </div>\r\n </div>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tabpanel [value]=\"template.title ?? i\">\r\n <rw-form\r\n [template]=\"template\"\r\n [apiName]=\"apiName()\"\r\n [rel]=\"rel()\"\r\n (afterDelete)=\"afterDelete()\"\r\n (afterSubmit)=\"afterSubmit($event)\"\r\n ></rw-form>\r\n </p-tabpanel>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tabpanel value=\"Extra\">\r\n <ng-container *ngTemplateOutlet=\"extraTabsRef\"></ng-container>\r\n </p-tabpanel>\r\n }\r\n }\r\n </p-tabpanels>\r\n</p-tabs>\r\n\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: ["::ng-deep .p-tooltip{max-width:fit-content!important}.field.grid>label.hasChildren{border-right:1px solid rgba(0,0,0,.1)}\n"], dependencies: [{ kind: "component", type: RestWorldIdNavigationComponent, selector: "rw-id-navigation", inputs: ["apiName", "rel", "urlPrefix"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i5$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i6$1.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i6$1.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i6$1.TabPanel, selector: "p-tabpanel", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: i6$1.TabList, selector: "p-tablist" }, { kind: "component", type: i6$1.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: RestWorldFormComponent, selector: "rw-form", inputs: ["allowDelete", "allowReload", "allowSubmit", "apiName", "rel", "showDelete", "showReload", "showSubmit", "template"], outputs: ["afterDelete", "afterSubmit", "templateChange", "valueChanges"] }, { kind: "ngmodule", type: ConfirmDialogModule }, { kind: "component", type: i7$1.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
4862
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RESTworldEditViewComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.MessageService }, { token: ProblemService }, { token: i3$3.Router }, { token: i5.ValdemortConfig }], target: i0.ɵɵFactoryTarget.Component });
4863
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RESTworldEditViewComponent, isStandalone: true, selector: "rw-edit", inputs: { rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: true, transformFunction: null }, uri: { classPropertyName: "uri", publicName: "uri", isSignal: true, isRequired: true, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: true, transformFunction: null }, displayTab: { classPropertyName: "displayTab", publicName: "displayTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { displayTab: "displayTabChange" }, queries: [{ propertyName: "extraTabsRef", first: true, predicate: ["extraTabs"], descendants: true }], ngImport: i0, template: "<div class=\"grid\">\r\n <div class=\"col-12 md:col-10\">\r\n <h1>Edit resource</h1>\r\n </div>\r\n <div class=\"col-12 md:col-2 align-items-center justify-content-end flex\">\r\n <rw-id-navigation [apiName]=\"apiName()\" [rel]=\"rel()\"></rw-id-navigation>\r\n @if (newHref()) {\r\n <button pButton pRipple type=\"button\" icon=\"fas fa-plus\" class=\"p-button-success mr-3\" (click)=\"createNew()\" pTooltip=\"Create new\"></button>\r\n }\r\n </div>\r\n</div>\r\n\r\n<p-tabs [(value)]=\"displayTab\">\r\n <p-tablist>\r\n @if (templates.isLoading()) {\r\n <p-tab value=\"Loading\">Loading</p-tab>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tab [value]=\"template.title ?? i\">{{template.title}}</p-tab>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tab value=\"Extra\">Extra</p-tab>\r\n }\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @if (templates.isLoading()) {\r\n <p-tabpanel value=\"Loading\">\r\n @for(i of [1, 2, 3, 4, 5]; track i) {\r\n <div class=\"grid field\">\r\n <p-skeleton class=\"col-12 mb-2 md:col-2 md:mb-0\" height=\"39px\"></p-skeleton>\r\n <div class=\"col-12 md:col-10\">\r\n <p-skeleton class=\"w-full\" height=\"39px\"></p-skeleton>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"grid\">\r\n <div class=\"col\">\r\n <div class=\"flex justify-content-end w-full\">\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n </div>\r\n </div>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tabpanel [value]=\"template.title ?? i\">\r\n <rw-form\r\n [template]=\"template\"\r\n [apiName]=\"apiName()\"\r\n [rel]=\"rel()\"\r\n (afterDelete)=\"afterDelete()\"\r\n (afterSubmit)=\"afterSubmit($event)\"\r\n ></rw-form>\r\n </p-tabpanel>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tabpanel value=\"Extra\">\r\n <ng-container *ngTemplateOutlet=\"extraTabsRef\"></ng-container>\r\n </p-tabpanel>\r\n }\r\n }\r\n </p-tabpanels>\r\n</p-tabs>\r\n\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: ["::ng-deep .p-tooltip{max-width:fit-content!important}.field.grid>label.hasChildren{border-right:1px solid rgba(0,0,0,.1)}\n"], dependencies: [{ kind: "component", type: RestWorldIdNavigationComponent, selector: "rw-id-navigation", inputs: ["apiName", "rel", "urlPrefix"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i6$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i7$1.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i7$1.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i7$1.TabPanel, selector: "p-tabpanel", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: i7$1.TabList, selector: "p-tablist" }, { kind: "component", type: i7$1.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: RestWorldFormComponent, selector: "rw-form", inputs: ["allowDelete", "allowReload", "allowSubmit", "apiName", "rel", "showDelete", "showReload", "showSubmit", "template"], outputs: ["afterDelete", "afterSubmit", "templateChange", "valueChanges"] }, { kind: "ngmodule", type: ConfirmDialogModule }, { kind: "component", type: i8$1.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }] });
4783
4864
  }
4784
4865
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RESTworldEditViewComponent, decorators: [{
4785
4866
  type: Component,
4786
- args: [{ selector: "rw-edit", standalone: true, imports: [RestWorldIdNavigationComponent, SkeletonModule, TabsModule, RestWorldFormComponent, ConfirmDialogModule, NgTemplateOutlet], template: "<div class=\"grid\">\r\n <div class=\"col-12 md:col-10\">\r\n <h1>Edit resource</h1>\r\n </div>\r\n <div class=\"col-12 md:col-2 align-items-center justify-content-end flex\">\r\n <rw-id-navigation [apiName]=\"apiName()\" [rel]=\"rel()\"></rw-id-navigation>\r\n </div>\r\n</div>\r\n\r\n<p-tabs [(value)]=\"displayTab\">\r\n <p-tablist>\r\n @if (templates.isLoading()) {\r\n <p-tab value=\"Loading\">Loading</p-tab>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tab [value]=\"template.title ?? i\">{{template.title}}</p-tab>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tab value=\"Extra\">Extra</p-tab>\r\n }\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @if (templates.isLoading()) {\r\n <p-tabpanel value=\"Loading\">\r\n @for(i of [1, 2, 3, 4, 5]; track i) {\r\n <div class=\"grid field\">\r\n <p-skeleton class=\"col-12 mb-2 md:col-2 md:mb-0\" height=\"39px\"></p-skeleton>\r\n <div class=\"col-12 md:col-10\">\r\n <p-skeleton class=\"w-full\" height=\"39px\"></p-skeleton>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"grid\">\r\n <div class=\"col\">\r\n <div class=\"flex justify-content-end w-full\">\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n </div>\r\n </div>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tabpanel [value]=\"template.title ?? i\">\r\n <rw-form\r\n [template]=\"template\"\r\n [apiName]=\"apiName()\"\r\n [rel]=\"rel()\"\r\n (afterDelete)=\"afterDelete()\"\r\n (afterSubmit)=\"afterSubmit($event)\"\r\n ></rw-form>\r\n </p-tabpanel>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tabpanel value=\"Extra\">\r\n <ng-container *ngTemplateOutlet=\"extraTabsRef\"></ng-container>\r\n </p-tabpanel>\r\n }\r\n }\r\n </p-tabpanels>\r\n</p-tabs>\r\n\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: ["::ng-deep .p-tooltip{max-width:fit-content!important}.field.grid>label.hasChildren{border-right:1px solid rgba(0,0,0,.1)}\n"] }]
4787
- }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.MessageService }, { type: i3$4.Router }, { type: i4$3.ValdemortConfig }], propDecorators: { extraTabsRef: [{
4867
+ args: [{ selector: "rw-edit", standalone: true, imports: [RestWorldIdNavigationComponent, SkeletonModule, TabsModule, RestWorldFormComponent, ConfirmDialogModule, NgTemplateOutlet, ButtonDirective, Tooltip], template: "<div class=\"grid\">\r\n <div class=\"col-12 md:col-10\">\r\n <h1>Edit resource</h1>\r\n </div>\r\n <div class=\"col-12 md:col-2 align-items-center justify-content-end flex\">\r\n <rw-id-navigation [apiName]=\"apiName()\" [rel]=\"rel()\"></rw-id-navigation>\r\n @if (newHref()) {\r\n <button pButton pRipple type=\"button\" icon=\"fas fa-plus\" class=\"p-button-success mr-3\" (click)=\"createNew()\" pTooltip=\"Create new\"></button>\r\n }\r\n </div>\r\n</div>\r\n\r\n<p-tabs [(value)]=\"displayTab\">\r\n <p-tablist>\r\n @if (templates.isLoading()) {\r\n <p-tab value=\"Loading\">Loading</p-tab>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tab [value]=\"template.title ?? i\">{{template.title}}</p-tab>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tab value=\"Extra\">Extra</p-tab>\r\n }\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @if (templates.isLoading()) {\r\n <p-tabpanel value=\"Loading\">\r\n @for(i of [1, 2, 3, 4, 5]; track i) {\r\n <div class=\"grid field\">\r\n <p-skeleton class=\"col-12 mb-2 md:col-2 md:mb-0\" height=\"39px\"></p-skeleton>\r\n <div class=\"col-12 md:col-10\">\r\n <p-skeleton class=\"w-full\" height=\"39px\"></p-skeleton>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"grid\">\r\n <div class=\"col\">\r\n <div class=\"flex justify-content-end w-full\">\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n <p-skeleton width=\"120px\" height=\"39px\" class=\"mx-2\"></p-skeleton>\r\n </div>\r\n </div>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n @else {\r\n @for (template of templates.value(); track template; let i = $index) {\r\n <p-tabpanel [value]=\"template.title ?? i\">\r\n <rw-form\r\n [template]=\"template\"\r\n [apiName]=\"apiName()\"\r\n [rel]=\"rel()\"\r\n (afterDelete)=\"afterDelete()\"\r\n (afterSubmit)=\"afterSubmit($event)\"\r\n ></rw-form>\r\n </p-tabpanel>\r\n }\r\n @if (extraTabsRef) {\r\n <p-tabpanel value=\"Extra\">\r\n <ng-container *ngTemplateOutlet=\"extraTabsRef\"></ng-container>\r\n </p-tabpanel>\r\n }\r\n }\r\n </p-tabpanels>\r\n</p-tabs>\r\n\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: ["::ng-deep .p-tooltip{max-width:fit-content!important}.field.grid>label.hasChildren{border-right:1px solid rgba(0,0,0,.1)}\n"] }]
4868
+ }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.MessageService }, { type: ProblemService }, { type: i3$3.Router }, { type: i5.ValdemortConfig }], propDecorators: { extraTabsRef: [{
4788
4869
  type: ContentChild,
4789
4870
  args: ["extraTabs", { static: false }]
4790
4871
  }] } });
@@ -4976,13 +5057,13 @@ class RESTworldListViewComponent {
4976
5057
  return undefined;
4977
5058
  return this._clients.getClient(apiName);
4978
5059
  }, ...(ngDevMode ? [{ debugName: "_client" }] : []));
4979
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RESTworldListViewComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.ConfirmationService }, { token: i2$1.MessageService }, { token: AvatarGenerator }, { token: i3$4.Router }, { token: ProblemService }], target: i0.ɵɵFactoryTarget.Component });
4980
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RESTworldListViewComponent, isStandalone: true, selector: "rw-list", inputs: { createButtonMenu: { classPropertyName: "createButtonMenu", publicName: "createButtonMenu", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "$filter", isSignal: true, isRequired: false, transformFunction: null }, orderby: { classPropertyName: "orderby", publicName: "$orderby", isSignal: true, isRequired: false, transformFunction: null }, skip: { classPropertyName: "skip", publicName: "$skip", isSignal: true, isRequired: false, transformFunction: null }, top: { classPropertyName: "top", publicName: "$top", isSignal: true, isRequired: false, transformFunction: null }, editLink: { classPropertyName: "editLink", publicName: "editLink", isSignal: true, isRequired: false, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: false, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "$filterChange", orderby: "$orderbyChange", skip: "$skipChange", top: "$topChange" }, ngImport: i0, template: "@let search = searchTemplate.value();\r\n@let api = apiName();\r\n@if (search && api) {\r\n <rw-table\r\n [apiName]=\"api\"\r\n [searchTemplate]=\"search\"\r\n [rows]=\"items()\"\r\n [headerMenu]=\"headerMenu()\"\r\n [rowMenu]=\"rowMenu()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [isLoading]=\"isLoading()\"\r\n >\r\n </rw-table>\r\n}\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}\n"], dependencies: [{ kind: "component", type: RestWorldTableComponent, selector: "rw-table", inputs: ["apiName", "cellStyleClass", "editTemplate", "headerMenu", "isLoading", "lazy", "paginator", "oDataParameters", "reflectParametersInUrl", "rowHover", "rowMenu", "rowStyleClass", "rows", "rowsPerPageOptions", "scrollHeight", "scrollable", "searchTemplate", "selectedRows", "selectionMode", "showRowMenuAsColumn", "showRowMenuOnRightClick", "styleClass", "tableStyle", "totalRecords", "urlParameterPrefix"], outputs: ["oDataParametersChange", "selectedRowsChange"] }, { kind: "ngmodule", type: ConfirmDialogModule }, { kind: "component", type: i7$1.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "visible", "position", "draggable"], outputs: ["onHide"] }] });
5060
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RESTworldListViewComponent, deps: [{ token: RestWorldClientCollection }, { token: i2$1.ConfirmationService }, { token: i2$1.MessageService }, { token: AvatarGenerator }, { token: i3$3.Router }, { token: ProblemService }], target: i0.ɵɵFactoryTarget.Component });
5061
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: RESTworldListViewComponent, isStandalone: true, selector: "rw-list", inputs: { createButtonMenu: { classPropertyName: "createButtonMenu", publicName: "createButtonMenu", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "$filter", isSignal: true, isRequired: false, transformFunction: null }, orderby: { classPropertyName: "orderby", publicName: "$orderby", isSignal: true, isRequired: false, transformFunction: null }, skip: { classPropertyName: "skip", publicName: "$skip", isSignal: true, isRequired: false, transformFunction: null }, top: { classPropertyName: "top", publicName: "$top", isSignal: true, isRequired: false, transformFunction: null }, editLink: { classPropertyName: "editLink", publicName: "editLink", isSignal: true, isRequired: false, transformFunction: null }, apiName: { classPropertyName: "apiName", publicName: "apiName", isSignal: true, isRequired: false, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "$filterChange", orderby: "$orderbyChange", skip: "$skipChange", top: "$topChange" }, ngImport: i0, template: "@let search = searchTemplate.value();\r\n@let api = apiName();\r\n@if (search && api) {\r\n <rw-table\r\n [apiName]=\"api\"\r\n [searchTemplate]=\"search\"\r\n [rows]=\"items()\"\r\n [headerMenu]=\"headerMenu()\"\r\n [rowMenu]=\"rowMenu()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [isLoading]=\"isLoading()\"\r\n >\r\n </rw-table>\r\n}\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}\n"], dependencies: [{ kind: "component", type: RestWorldTableComponent, selector: "rw-table", inputs: ["apiName", "cellStyleClass", "editTemplate", "headerMenu", "isLoading", "lazy", "paginator", "oDataParameters", "reflectParametersInUrl", "rowHover", "rowMenu", "rowStyleClass", "rows", "rowsPerPageOptions", "scrollHeight", "scrollable", "searchTemplate", "selectedRows", "selectionMode", "showRowMenuAsColumn", "showRowMenuOnRightClick", "styleClass", "tableStyle", "totalRecords", "urlParameterPrefix"], outputs: ["oDataParametersChange", "selectedRowsChange"] }, { kind: "ngmodule", type: ConfirmDialogModule }, { kind: "component", type: i8$1.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "visible", "position", "draggable"], outputs: ["onHide"] }] });
4981
5062
  }
4982
5063
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: RESTworldListViewComponent, decorators: [{
4983
5064
  type: Component,
4984
5065
  args: [{ selector: 'rw-list', standalone: true, imports: [RestWorldTableComponent, ConfirmDialogModule], template: "@let search = searchTemplate.value();\r\n@let api = apiName();\r\n@if (search && api) {\r\n <rw-table\r\n [apiName]=\"api\"\r\n [searchTemplate]=\"search\"\r\n [rows]=\"items()\"\r\n [headerMenu]=\"headerMenu()\"\r\n [rowMenu]=\"rowMenu()\"\r\n [totalRecords]=\"totalRecords()\"\r\n [isLoading]=\"isLoading()\"\r\n >\r\n </rw-table>\r\n}\r\n<p-confirmDialog></p-confirmDialog>\r\n", styles: [".p-tooltip{max-width:fit-content}a.p-button{text-decoration:none}\n"] }]
4985
- }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.ConfirmationService }, { type: i2$1.MessageService }, { type: AvatarGenerator }, { type: i3$4.Router }, { type: ProblemService }] });
5066
+ }], ctorParameters: () => [{ type: RestWorldClientCollection }, { type: i2$1.ConfirmationService }, { type: i2$1.MessageService }, { type: AvatarGenerator }, { type: i3$3.Router }, { type: ProblemService }] });
4986
5067
 
4987
5068
  /**
4988
5069
  * This is an HttpInterceptor that ensures all HttpClient requests