diff --git a/packages/hal-forms/src/builder.ts b/packages/hal-forms/src/builder.ts index b0b1078..92859fc 100644 --- a/packages/hal-forms/src/builder.ts +++ b/packages/hal-forms/src/builder.ts @@ -2,6 +2,7 @@ import { HalFormsProperty, HalFormsPropertyInlineOptions, HalFormsPropertyOption import { MATCH_ANYTHING, MATCH_NOTHING } from "./_internal"; import { TypedRequestSpec } from "@contentgrid/typed-fetch"; import { HalFormsPropertyType, HalFormsPropertyValue } from "./_shape"; +import { SimpleLink } from "@contentgrid/hal"; export class HalFormsTemplateBuilder implements HalFormsTemplate> { @@ -119,7 +120,7 @@ export class HalFormsPropertyBuilder implements HalFormsProperty { return this.withOptions(opts => opts.withInline(options)); } if(typeof options === "function") { - return new HalFormsPropertyBuilder(this.name, this.type, this.readOnly, this.required, options(this._options ?? new HalFormsPropertyInlineOptionsImpl([])), this.regex, this.minLength, this.maxLength, this.prompt, this.value); + return new HalFormsPropertyBuilder(this.name, this.type, this.readOnly, this.required, options(this._options ?? new HalFormsPropertyOptionsImpl()), this.regex, this.minLength, this.maxLength, this.prompt, this.value); } throw new Error("Unknown type of options"); } @@ -153,35 +154,53 @@ export class HalFormsPropertyBuilder implements HalFormsProperty { export interface HalFormsPropertyOptionsBuilder { withInline(inline: readonly HalFormsPropertyOption[]): HalFormsPropertyOptionsBuilder; addInlineOption(option: HalFormsPropertyOption): HalFormsPropertyOptionsBuilder; + withRemote(link: SimpleLink): HalFormsPropertyOptionsBuilder; withMaxItems(maxItems: number): HalFormsPropertyOptionsBuilder; withMinItems(minItems: number): HalFormsPropertyOptionsBuilder; build(): HalFormsPropertyInlineOptions | HalFormsPropertyRemoteOptions; } -class HalFormsPropertyInlineOptionsImpl implements HalFormsPropertyInlineOptions, HalFormsPropertyOptionsBuilder { +type Writeable = { + -readonly [P in keyof T]?: T[P]; +} + +class HalFormsPropertyOptionsImpl implements HalFormsPropertyInlineOptions, HalFormsPropertyRemoteOptions, HalFormsPropertyOptionsBuilder { + public constructor( - public readonly inline: readonly HalFormsPropertyOption[], + public readonly inline: readonly HalFormsPropertyOption[] = undefined!, + public readonly link: SimpleLink = undefined!, public readonly maxItems: number = Infinity, public readonly minItems: number = 0, ) { - + // This is to ensure that the 'inline' or 'link' properties are really not available when they are unset, + // without having to create two separate classes for them + if(!link) { + delete (this as Writeable).link; + } + if(!inline) { + delete (this as Writeable).inline + } } public withInline(inline: readonly HalFormsPropertyOption[]): HalFormsPropertyOptionsBuilder { - return new HalFormsPropertyInlineOptionsImpl(inline, this.maxItems, this.minItems); + return new HalFormsPropertyOptionsImpl(inline, undefined!, this.maxItems, this.minItems); } public addInlineOption(option: HalFormsPropertyOption): HalFormsPropertyOptionsBuilder { - return this.withInline(this.inline.concat([option])); + return this.withInline((this.inline ?? []).concat([option])); + } + + public withRemote(link: SimpleLink): HalFormsPropertyOptionsBuilder { + return new HalFormsPropertyOptionsImpl(undefined!, link, this.maxItems, this.minItems); } public withMaxItems(maxItems: number): HalFormsPropertyOptionsBuilder { - return new HalFormsPropertyInlineOptionsImpl(this.inline, maxItems, this.minItems); + return new HalFormsPropertyOptionsImpl(this.inline, this.link, maxItems, this.minItems); } public withMinItems(minItems: number): HalFormsPropertyOptionsBuilder { - return new HalFormsPropertyInlineOptionsImpl(this.inline, this.maxItems, minItems); + return new HalFormsPropertyOptionsImpl(this.inline, this.link, this.maxItems, minItems); } public build(): HalFormsPropertyInlineOptions | HalFormsPropertyRemoteOptions { @@ -189,27 +208,38 @@ class HalFormsPropertyInlineOptionsImpl implements HalFormsPropertyInlineOptions } public get selectedValues(): readonly string[] { - return[] + return []; } public toOption(data: HalFormsPropertyOption): HalFormsPropertyOption { return data; } - public loadOptions(): Promise { - return Promise.resolve(this.inline); + public async loadOptions(): Promise; + public async loadOptions(fetcher: (link: SimpleLink) => Promise): Promise; + public async loadOptions(fetcher?: (link: SimpleLink) => Promise): Promise { + if (this.isRemote() && fetcher) { + return await fetcher(this.link); + } + + if (this.isInline()) { + return this.inline; + } + + throw new Error("Options are not inline or remote"); } public isInline(): this is HalFormsPropertyInlineOptions { - return true; + return this.inline !== undefined; } public isRemote(): this is HalFormsPropertyRemoteOptions { - return false; + return this.link !== undefined; } } + export default function buildTemplate(method: string, url: string): HalFormsTemplateBuilder { return HalFormsTemplateBuilder.from({ method, url }) } diff --git a/packages/hal/src/Link.ts b/packages/hal/src/Link.ts index bc18cd3..7d87aeb 100644 --- a/packages/hal/src/Link.ts +++ b/packages/hal/src/Link.ts @@ -9,6 +9,17 @@ export class SimpleLink { } + public static to(href: string): SimpleLink { + return new SimpleLink({ href }); + } + + public static templated(template: UriTemplate): SimpleLink { + return new SimpleLink({ + href: template.template, + templated: true + }) + } + // @internal #warnDeprecation() { if(this.deprecation && !this.#deprecationWarned) { @@ -50,6 +61,10 @@ export class SimpleLink { return this.data.deprecation; } + public withRel(rel: LinkRelation): Link { + return new Link(rel, this.data); + } + public toString() { return `<${this.href}>; ${toLinkParams({ name: this.name,