Page MenuHomeSealhub

array.ts
No OneTemporary

array.ts

/* eslint-disable @typescript-eslint/no-explicit-any */
import Router from "@koa/router";
import { hasShape, predicates } from "@sealcode/ts-predicates";
import { Context } from "koa";
import qs from "qs";
import { Field, FieldsetOutput } from "sealious";
import { tempstream } from "tempstream";
import { FormField } from "../fields/field.js";
import { SimpleFormField } from "../fields/simple-form-field.js";
import { StructuredArrayField } from "../fields/structured-array.js";
import { FormDataValue } from "../form-types.js";
import { Derived } from "./derived.js";
import { FormControl, FormControlContext } from "./form-control.js";
import { FormFieldControl } from "./form-field-control.js";
export type UnboundControlStorage<
F extends { new (...args: any): FormFieldControl } = {
new (...args: any): FormFieldControl;
}
> = [F, string, ConstructorParameters<F>];
export function UnboundControl<
F extends { new (...args: any): FormFieldControl }
>(
controlClass: F,
arbitrary_field_name: string,
params: ConstructorParameters<F>["1"]
): UnboundControlStorage {
return [controlClass, arbitrary_field_name, params];
}
export class StructuredArray<
Subfields extends Record<string, Field>
> extends FormFieldControl {
constructor(
public field: StructuredArrayField<Subfields>,
public item_controls: Array<FormControl | UnboundControlStorage>
) {
super([field]);
}
getFrameID() {
return `array-frame-${this.field.name}`;
}
getInnerFormID() {
return this.getFrameID() + "-form";
}
getActionURL(form_id?: string, action?: Record<string, unknown>) {
return `${this.getFrameID()}${
form_id || action ? "?" : ""
}${qs.stringify({ form_id, action })}`;
}
async getDefaultItemBody() {
return {};
}
makeSubfield(_field_name: string): FormField {
return new SimpleFormField(false);
}
async renderItemControl(
ctx: Context,
form_id: string,
control: FormControl | UnboundControlStorage,
item: FieldsetOutput<Subfields>,
index: number
) {
const fctx = {
ctx,
data: {},
messages: [],
field_name_prefix: "",
form_id,
validate: false,
};
if (Array.isArray(control)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const [constructor, field_name, params] = control;
const field = this.makeSubfield(field_name);
if (!field) {
console.warn(
`makeSubfield returned null for field ${field_name}`
);
return "";
}
await field.init(
`${this.field.name}[data][${index}][${field_name}]`
);
const hidden_shadow = new Derived(
[field],
async (
values,
{ inner_form_id, field_name }
) => /* HTML */ `<input
name="${field_name}"
type="hidden"
value="${values[field_name] || ""}"
form="${inner_form_id}"
/>`,
async () => ({
inner_form_id: this.getInnerFormID(),
field_name: field.name,
})
);
return tempstream`${new constructor(field, params).render({
...fctx,
data: { [field.name]: item[field_name] as FormDataValue },
})}${hidden_shadow.render({
...fctx,
data: { [field.name]: item[field_name] as FormDataValue },
form_id: this.getInnerFormID(),
})}`;
} else {
return control.render(fctx);
}
}
async renderItem(
ctx: Context,
form_id: string,
item: FieldsetOutput<Subfields>,
index: number
) {
return tempstream/* HTML */ `<div>
${this.item_controls.map((control) =>
this.renderItemControl(ctx, form_id, control, item, index)
)}
<input
type="submit"
value="remove"
form="${this.getInnerFormID()}"
formaction="${this.getActionURL(form_id, {
remove: index,
})}"
/>
</div>`;
}
async getItems(ctx: Context, raw_values: Record<string, FormDataValue>) {
return (await this.field.getValue(ctx, raw_values)).parsed as Promise<
FieldsetOutput<Subfields>[]
>;
}
async _render(
ctx: Context,
form_id: string,
raw_values: Record<string, FormDataValue>
) {
const items = await this.getItems(ctx, raw_values);
return tempstream/* HTML */ `<turbo-frame id="${this.getFrameID()}">
Reverse Single Reference
${items.map((item, index) =>
this.renderItem(ctx, form_id, item, index)
)}
<form id="${this.getInnerFormID()}" method="POST"></form>
<input
type="submit"
value="add"
form="${this.getInnerFormID()}"
formaction="${this.getActionURL(form_id, {
insert: { index: items.length, value: {} },
})}"
/>
</turbo-frame>`;
}
render(fctx: FormControlContext) {
return this._render(fctx.ctx, fctx.form_id, fctx.data);
}
async getTargetItemID(ctx: Context): Promise<string> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return ctx.params.id as string;
}
mount(router: Router) {
console.log("mounting!", this.getActionURL());
router.post(this.getActionURL(), async (ctx) => {
const form_id = ctx.query.form_id;
if (typeof form_id != "string") {
throw new Error("Missing form_id query param");
}
const item = await this.field.sealious_field.collection.getByID(
ctx.$context,
await this.getTargetItemID(ctx)
);
if (!hasShape({ action: predicates.object }, ctx.$body)) {
throw new Error("Missing action param");
}
const field_data = (ctx.$body as Record<string, unknown>)[
this.field.name
] || { data: [] };
console.log({ action: field_data });
if (
!hasShape(
{
data: predicates.array(
this.field.sealious_field.value_predicate
),
},
field_data
)
) {
throw new Error("missing data atribute in action description");
}
item.set(this.field.name, {
...ctx.$body.action,
data: field_data.data || [],
});
console.log("ACTION", {
...ctx.$body.action,
data: field_data.data || [],
});
await item.save(ctx.$context);
console.log(await item.getDecodedBody(ctx.$context, {}));
ctx.body = this._render(
ctx,
form_id,
await item.getDecodedBody(ctx.$context, {})
);
});
}
}

File Metadata

Mime Type
text/x-java
Expires
Tue, Dec 24, 14:05 (12 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
557302
Default Alt Text
array.ts (5 KB)

Event Timeline