@sealcode/sealgen 0.19.5 → 0.19.6

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.
Files changed (67) hide show
  1. package/@types/forms/fields/field.d.ts +1 -0
  2. package/@types/forms/fields/fields.d.ts +2 -1
  3. package/@types/forms/fields/get-field-for-sealious.d.ts +2 -1
  4. package/@types/forms/fields/hybrid.d.ts +11 -0
  5. package/@types/forms/fields/item-draft.d.ts +20 -0
  6. package/@types/forms/form.d.ts +1 -0
  7. package/@types/forms/multiform.d.ts +2 -2
  8. package/@types/page/list.d.ts +5 -4
  9. package/@types/page/mountable-with-fields.d.ts +9 -2
  10. package/@types/page/mountable.d.ts +1 -1
  11. package/@types/page/page.d.ts +1 -1
  12. package/@types/templates/form.d.ts +2 -2
  13. package/lib/forms/controls/checkboxed-list-input.test.js +16 -12
  14. package/lib/forms/controls/file.test.js +2 -2
  15. package/lib/forms/controls/image.test.js +4 -4
  16. package/lib/forms/controls/single-reference.test.js +2 -2
  17. package/lib/forms/fields/field.js +1 -1
  18. package/lib/forms/fields/field.test.js +9 -6
  19. package/lib/forms/fields/fields.js +2 -1
  20. package/lib/forms/fields/file.test.js +2 -2
  21. package/lib/forms/fields/get-field-for-sealious.js +6 -0
  22. package/lib/forms/fields/hybrid.js +37 -0
  23. package/lib/forms/fields/item-draft.js +34 -0
  24. package/lib/forms/form.js +11 -5
  25. package/lib/forms/form.test.js +9 -9
  26. package/lib/forms/multiform.js +7 -4
  27. package/lib/forms/multiform.test.js +7 -7
  28. package/lib/mount.js +4 -1
  29. package/lib/page/list.js +9 -6
  30. package/lib/page/mountable-with-fields.js +16 -1
  31. package/lib/page/mountable-with-fields.test.js +2 -2
  32. package/lib/page/mountable.js +1 -1
  33. package/lib/page/page.js +1 -1
  34. package/lib/templates/form.js +9 -18
  35. package/lib/templates/shared/collection-create-form.js +8 -8
  36. package/lib/templates/shared/collection-edit-form.js +9 -9
  37. package/lib/templates/shared/collection-list.js +2 -1
  38. package/lib/templates/shared/shared-crud-form-fields.js +4 -4
  39. package/lib/utils/extract-fields-from-collection.js +3 -0
  40. package/package.json +1 -1
  41. package/src/forms/controls/checkboxed-list-input.test.ts +16 -12
  42. package/src/forms/controls/file.test.ts +2 -2
  43. package/src/forms/controls/image.test.ts +4 -4
  44. package/src/forms/controls/single-reference.test.ts +2 -2
  45. package/src/forms/fields/field.test.ts +9 -6
  46. package/src/forms/fields/field.ts +6 -7
  47. package/src/forms/fields/fields.ts +2 -1
  48. package/src/forms/fields/file.test.ts +2 -2
  49. package/src/forms/fields/get-field-for-sealious.ts +8 -0
  50. package/src/forms/fields/hybrid.ts +47 -0
  51. package/src/forms/fields/item-draft.ts +44 -0
  52. package/src/forms/form.test.ts +15 -15
  53. package/src/forms/form.ts +34 -26
  54. package/src/forms/multiform.test.ts +7 -7
  55. package/src/forms/multiform.ts +10 -7
  56. package/src/mount.ts +11 -7
  57. package/src/page/list.ts +33 -28
  58. package/src/page/mountable-with-fields.test.ts +2 -2
  59. package/src/page/mountable-with-fields.ts +26 -5
  60. package/src/page/mountable.ts +1 -1
  61. package/src/page/page.ts +1 -1
  62. package/src/templates/form.ts +9 -18
  63. package/src/templates/shared/collection-create-form.ts +8 -8
  64. package/src/templates/shared/collection-edit-form.ts +9 -9
  65. package/src/templates/shared/collection-list.ts +5 -4
  66. package/src/templates/shared/shared-crud-form-fields.ts +5 -5
  67. package/src/utils/extract-fields-from-collection.ts +3 -0
package/lib/mount.js CHANGED
@@ -50,7 +50,10 @@ function mount(router, url, mountable, use_dummy_app = false, file_manager = use
50
50
  handleHtmlPromise
51
51
  ];
52
52
  router.use(raw_url, ...args);
53
- mountable.init();
53
+ router.use(raw_url, async (_ctx, next) => {
54
+ mountable.init(raw_url, router);
55
+ await next();
56
+ });
54
57
  router.get(raw_url.slice(0, -1), async (ctx, next) => {
55
58
  const url2 = ctx.URL;
56
59
  if (!url2.pathname.endsWith("/")) {
package/lib/page/list.js CHANGED
@@ -1,10 +1,10 @@
1
- import { tempstream } from "tempstream";
2
- import { Page } from "./page.js";
3
- import { naturalNumbers, UrlWithNewParams } from "../utils/utils.js";
4
- import { makeHiddenInputs } from "../make-hidden-inputs.js";
5
1
  import qs from "qs";
2
+ import { tempstream } from "tempstream";
6
3
  import { NumberField } from "../forms/fields/number.js";
7
4
  import { TextBasedSimpleField } from "../forms/fields/simple-form-field.js";
5
+ import { makeHiddenInputs } from "../make-hidden-inputs.js";
6
+ import { naturalNumbers, UrlWithNewParams } from "../utils/utils.js";
7
+ import { Page } from "./page.js";
8
8
  const BasePagePropsShape = {};
9
9
  const DEFAULT_ITEMS_PER_PAGE = 12;
10
10
  const BaseListPageFields = {
@@ -38,8 +38,11 @@ class ListPage extends Page {
38
38
  this.filterFields = {};
39
39
  this.filterControls = [];
40
40
  }
41
- init() {
42
- super.init();
41
+ init(path, router) {
42
+ if (this.initialized) {
43
+ return;
44
+ }
45
+ super.init(path, router);
43
46
  for (const [, field] of Object.entries(this.filterFields)) {
44
47
  void field.init();
45
48
  }
@@ -6,17 +6,32 @@ import { Mountable } from "./mountable.js";
6
6
  class MountableWithFields extends Mountable {
7
7
  constructor() {
8
8
  super();
9
+ this.initialized = false;
9
10
  this.field_names_prefix = "";
10
11
  // useful for multiform, where many forms are merged into one and field assignment is made using the prefix
11
12
  this.form_id = "";
12
13
  if (!this.fields)
13
14
  this.fields = {};
14
15
  }
15
- init() {
16
+ /** most routes should be setup using .mount(), but mount runs before
17
+ initialization so we don't yet have controls, app, etc ready for dynamic
18
+ routes **/
19
+ init(path, router) {
20
+ super.init(path, router);
21
+ if (this.initialized) {
22
+ return;
23
+ }
24
+ if (this.getFields) {
25
+ this.fields = this.getFields();
26
+ } else if (!this.fields) {
27
+ throw new Error("Fields not defined and getter not provided");
28
+ }
29
+ this.controls = this.getControls(this.fields);
16
30
  for (const [field_name, field] of Object.entries(this.fields)) {
17
31
  void field.init();
18
32
  field.setName(field_name);
19
33
  }
34
+ this.initialized = true;
20
35
  }
21
36
  makeFormControlContext(ctx, data, validate, field_name_prefix = this.field_names_prefix, form_id = this.form_id) {
22
37
  return new FormControlContext(
@@ -10,8 +10,8 @@ describe("mountable-with-fields", () => {
10
10
  new class extends Form {
11
11
  constructor() {
12
12
  super(...arguments);
13
- this.fields = fields;
14
- this.controls = [];
13
+ this.getFields = () => fields;
14
+ this.getControls = () => [];
15
15
  }
16
16
  async onSubmit(ctx, form_values) {
17
17
  const data = await this.getParsedValues(ctx);
@@ -1,5 +1,5 @@
1
1
  class Mountable {
2
- init() {
2
+ init(_path, _router) {
3
3
  }
4
4
  }
5
5
  export {
package/lib/page/page.js CHANGED
@@ -7,7 +7,7 @@ class Page extends MountableWithFields {
7
7
  constructor() {
8
8
  super(...arguments);
9
9
  this.propsParser = new AllQueryParams();
10
- this.controls = [];
10
+ this.getControls = () => [];
11
11
  }
12
12
  mount(router, path) {
13
13
  router.get(path, async (ctx) => {
@@ -3,13 +3,12 @@ const defaults = {
3
3
  postimport: `
4
4
  import { Fields } from "@sealcode/sealgen";
5
5
  import type {FormDataValue} from "@sealcode/sealgen";`,
6
- fields: `
7
- password: new Fields.Password(true),`,
8
- controls: `
9
- new Controls.SimpleInput(fields.password, {
6
+ get_fields: `()=>(<const>{ password: new Fields.Password(true) })`,
7
+ get_controls: `
8
+ ()=><const>[new Controls.SimpleInput(fields.password, {
10
9
  label: "Password:",
11
10
  type: "password",
12
- }),`,
11
+ })],`,
13
12
  validate_method: `async validateValues(
14
13
  ctx: Context,
15
14
  data: Record<string, FormDataValue>
@@ -43,28 +42,20 @@ async function formTemplate(action_name, _params = defaults) {
43
42
  const params = { ...defaults, ..._params };
44
43
  const content = `import type { Context } from "koa";
45
44
  import type { FormData } from "@sealcode/sealgen";
46
- import { Form, Controls, fieldsToShape } from "@sealcode/sealgen";
45
+ import { Form, Controls } from "@sealcode/sealgen";
47
46
  import html from "src/back/html.js";
48
47
 
49
48
  ${params.postimport}
50
49
 
51
50
  export const actionName = "${action_name}";
52
51
 
53
- const fields = {
54
- ${params.fields}
55
- };
56
-
57
- Form.initFieldNames(fields);
58
-
59
- export const ${action_name}Shape = fieldsToShape(fields);
52
+ const getFields = ${params.get_fields};
60
53
 
61
- export default new (class ${action_name}Form extends Form<typeof fields, void> {
54
+ export default new (class ${action_name}Form extends Form<ReturnType<typeof getFields>, void> {
62
55
  defaultSuccessMessage = "Formularz wype\u0142niony poprawnie";
63
- fields = fields;
56
+ getFields = getFields;
64
57
 
65
- controls = [
66
- ${params.controls}
67
- ];
58
+ getControls = ${params.get_controls};
68
59
 
69
60
  ${params.other_methods}
70
61
 
@@ -4,10 +4,10 @@ import extract_fields_from_collection from "../../utils/extract-fields-from-coll
4
4
  import { wrapAttribute } from "../../utils/wrap-attributes.js";
5
5
  async function createItemFormTemplate(create_action_name, newfilefullpath, collection_name, list_action) {
6
6
  const collection_fields = await extract_fields_from_collection(collection_name);
7
- const formFieldsVar = `${toPascalCase(collection_name)}FormFields`;
7
+ const formFieldsGetterVar = `get${toPascalCase(collection_name)}FormFields`;
8
8
  return formTemplate(create_action_name, {
9
9
  success_message: "Created!",
10
- postimport: `import {${formFieldsVar}, get${toPascalCase(
10
+ postimport: `import {${formFieldsGetterVar}, get${toPascalCase(
11
11
  collection_name
12
12
  )}FormControls }from "./shared.js";
13
13
 
@@ -18,12 +18,12 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
18
18
  import { ${list_action}URL } from "src/back/routes/urls.js";
19
19
  import {tempstream} from "tempstream";
20
20
  `,
21
- fields: `...${toPascalCase(collection_name)}FormFields`,
22
- controls: `
21
+ get_fields: `get${toPascalCase(collection_name)}FormFields`,
22
+ get_controls: `(fields: ReturnType<typeof getFields>)=>[
23
23
  new Controls.FormHeader("Create ${toPascalCase(
24
24
  collection_name
25
25
  )}"),
26
- ...get${toPascalCase(collection_name)}FormControls()`,
26
+ ...get${toPascalCase(collection_name)}FormControls(this.fields)]`,
27
27
  can_access: `canAccess = async (ctx: Context) => {
28
28
  const policy = ${toPascalCase(collection_name)}.getPolicy("create");
29
29
  const response = await policy.check(ctx.$context);
@@ -41,14 +41,14 @@ async function createItemFormTemplate(create_action_name, newfilefullpath, colle
41
41
  {
42
42
  ${collection_fields.map(
43
43
  (field_info) => field_info.form_field.generateCreateValueGetter(field_info, {
44
- form_field_types: formFieldsVar,
44
+ form_field_types: "FormFieldTypes",
45
45
  sealious_field: `fields${wrapAttribute(field_info.name)}`,
46
- form_fields: "fields"
46
+ form_fields: "this.fields"
47
47
  })
48
48
  ).join(",\n")}
49
49
  });
50
50
 
51
- for(const field of Object.values(${formFieldsVar})){
51
+ for(const field of Object.values(this.fields)){
52
52
  // eslint-disable-next-line no-await-in-loop
53
53
  await field.postSealiousCreate(ctx, created, formData)
54
54
  }
@@ -4,11 +4,11 @@ import extract_fields_from_collection from "../../utils/extract-fields-from-coll
4
4
  import { wrapAttribute } from "../../utils/wrap-attributes.js";
5
5
  async function editItemFormTemplate(edit_action_name, newfilefullpath, collection_name, list_action) {
6
6
  const collection_fields = await extract_fields_from_collection(collection_name);
7
- const formFieldsVar = `${toPascalCase(collection_name)}FormFields`;
7
+ const formFieldsGetterVar = `get${toPascalCase(collection_name)}FormFields`;
8
8
  const collectionVar = `${toPascalCase(collection_name)}`;
9
9
  return formTemplate(edit_action_name, {
10
10
  success_message: "Changes saved!",
11
- postimport: `import {${formFieldsVar}, get${toPascalCase(
11
+ postimport: `import {${formFieldsGetterVar}, get${toPascalCase(
12
12
  collection_name
13
13
  )}FormControls }from "../shared.js";
14
14
  import { ${collectionVar} } from "src/back/collections/collections.js";
@@ -17,12 +17,12 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
17
17
  import type { FormDataValue } from "@sealcode/sealgen";
18
18
  import type { CollectionInputWithAllKeys } from "sealious";
19
19
  `,
20
- fields: `...${toPascalCase(collection_name)}FormFields`,
21
- controls: `
20
+ get_fields: formFieldsGetterVar,
21
+ get_controls: `(fields: ReturnType<typeof getFields>)=>[
22
22
  new Controls.FormHeader("Edit ${toPascalCase(
23
23
  collection_name
24
24
  )}"),
25
- ...get${toPascalCase(collection_name)}FormControls()`,
25
+ ...get${toPascalCase(collection_name)}FormControls(this.fields)]`,
26
26
  can_access: `canAccess = async (ctx: Context) => {
27
27
  const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
28
28
  const response = await policy.check(ctx.$context);
@@ -54,7 +54,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
54
54
  sealious_field: `${collectionVar}.fields${wrapAttribute(
55
55
  field_info.name
56
56
  )}`,
57
- form_fields: "fields"
57
+ form_fields: "this.fields"
58
58
  }
59
59
  )
60
60
  ).join(",\n")}
@@ -63,7 +63,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
63
63
 
64
64
  await item.save(ctx.$context);
65
65
 
66
- for(const field of Object.values(${formFieldsVar})){
66
+ for(const field of Object.values(this.fields)){
67
67
  // eslint-disable-next-line no-await-in-loop
68
68
  await field.postSealiousEdit(ctx, item, formData)
69
69
  }
@@ -79,7 +79,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
79
79
  return id
80
80
  }
81
81
  `,
82
- get_initial_values: `async getInitialValues(ctx: Context): Promise<{ [key in keyof typeof fields]: FormDataValue | undefined }> {
82
+ get_initial_values: `async getInitialValues(ctx: Context): Promise<{ [key in keyof typeof this.fields]: FormDataValue | undefined }> {
83
83
  const id = await this.getID(ctx);
84
84
 
85
85
  const {
@@ -99,7 +99,7 @@ async function editItemFormTemplate(edit_action_name, newfilefullpath, collectio
99
99
  ${collection_fields.map(
100
100
  (field_info) => field_info.form_field.generateInitialValue(field_info, {
101
101
  form_field_types: "FieldTypes",
102
- form_fields: "fields"
102
+ form_fields: "this.fields"
103
103
  })
104
104
  ).join(",\n")}
105
105
  };
@@ -121,7 +121,8 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
121
121
  typeof ${uppercase_collection},
122
122
  typeof BaseListPageFields
123
123
  > {
124
- fields = BaseListPageFields;
124
+ getFields = ()=>BaseListPageFields;
125
+ getControls = ()=>[];
125
126
 
126
127
  async renderFilters(ctx: Context): Promise<FlatTemplatable> {
127
128
  const query_params = qs.parse(ctx.search.slice(1));
@@ -12,12 +12,12 @@ ${Array.from(
12
12
  collection_fields.map(
13
13
  (field_info) => field_info.form_field.generateImportsForFieldList(field_info)
14
14
  ).flat().map(
15
- ({ what, from, as }) => `import {${what}${as ? ` as ${as}` : ""} } from "${from}"`
15
+ ({ what, from, as, type }) => `import ${type ? "type " : ""}{${what}${as ? ` as ${as}` : ""} } from "${from}"`
16
16
  )
17
17
  ).values()
18
18
  ).join(";\n")}
19
19
 
20
- export const ${toPascalCase(collection_name)}FormFields = <const>{
20
+ export const get${toPascalCase(collection_name)}FormFields = () => <const>{
21
21
  ${collection_fields.map((field_info) => {
22
22
  return `${field_info.name}: ${field_info.form_field.generateFieldDeclaration(field_info, {
23
23
  form_field_types: "Fields",
@@ -28,11 +28,11 @@ export const ${toPascalCase(collection_name)}FormFields = <const>{
28
28
  }).join(",\n")}
29
29
  };
30
30
 
31
- const fields = ${toPascalCase(collection_name)}FormFields
31
+ const getFields = get${toPascalCase(collection_name)}FormFields
32
32
 
33
33
  export const get${toPascalCase(
34
34
  collection_name
35
- )}FormControls: () => Controls.FormControl[] = () => [
35
+ )}FormControls: (fields: ReturnType<typeof getFields>) => Controls.FormControl[] = (fields) => [
36
36
  ${collection_fields.map(({ name }) => `fields${wrapAttribute(name)}.getControl()`).join(",\n")}
37
37
  ];
38
38
  `;
@@ -36,6 +36,9 @@ for (const field_name in c.fields){
36
36
  }
37
37
  if(type == "single-reference"){
38
38
  target = field.target_collection;
39
+ }
40
+ if(type == "item-draft"){
41
+ target = field.target_collection_name;
39
42
  }
40
43
  if(type == "deep-reverse-single-reference" || type =="reverse-single-reference"){
41
44
  target = field.target_collection;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.19.5",
3
+ "version": "0.19.6",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -42,8 +42,8 @@ describe("checkboxed-list", () => {
42
42
  router,
43
43
  "/",
44
44
  new (class extends Form<typeof fields, unknown> {
45
- fields = fields;
46
- controls = [new CheckboxedListInput(fields.syllables)];
45
+ getFields = () => fields;
46
+ getControls = () => [new CheckboxedListInput(fields.syllables)];
47
47
 
48
48
  async onSubmit(ctx: Context) {
49
49
  received_values = await this.getParsedValues(ctx);
@@ -91,8 +91,8 @@ describe("checkboxed-list", () => {
91
91
  router,
92
92
  "/",
93
93
  new (class extends Form<typeof fields, unknown> {
94
- fields = fields;
95
- controls = [new CheckboxedListInput(fields.syllables)];
94
+ getFields = () => fields;
95
+ getControls = () => [new CheckboxedListInput(fields.syllables)];
96
96
 
97
97
  async onSubmit(ctx: Context) {
98
98
  submit_called = true;
@@ -138,8 +138,8 @@ describe("checkboxed-list", () => {
138
138
  router,
139
139
  "/",
140
140
  new (class extends Form<typeof fields, unknown> {
141
- fields = fields;
142
- controls = [new CheckboxedListInput(fields.syllables)];
141
+ getFields = () => fields;
142
+ getControls = () => [new CheckboxedListInput(fields.syllables)];
143
143
 
144
144
  async onSubmit(ctx: Context) {
145
145
  submit_called = true;
@@ -186,8 +186,8 @@ describe("checkboxed-list", () => {
186
186
  router,
187
187
  "/",
188
188
  new (class extends Form<typeof fields, unknown> {
189
- fields = fields;
190
- controls = [new CheckboxedListInput(fields.syllables)];
189
+ getFields = () => fields;
190
+ getControls = () => [new CheckboxedListInput(fields.syllables)];
191
191
 
192
192
  async onSubmit(ctx: Context) {
193
193
  submit_called = true;
@@ -232,8 +232,10 @@ describe("checkboxed-list", () => {
232
232
  router,
233
233
  "/",
234
234
  new (class extends Form<typeof fields, unknown> {
235
- fields = fields;
236
- controls = [new CheckboxedListInput(fields.syllables, { label: "ABC" })];
235
+ getFields = () => fields;
236
+ getControls = () => [
237
+ new CheckboxedListInput(fields.syllables, { label: "ABC" }),
238
+ ];
237
239
 
238
240
  async onSubmit(ctx: Context) {
239
241
  // noop
@@ -283,8 +285,10 @@ describe("checkboxed-list", () => {
283
285
  router,
284
286
  "/",
285
287
  new (class extends Form<typeof fields, unknown> {
286
- fields = fields;
287
- controls = [new CheckboxedListInput(fields.syllables, { label: "Letters" })];
288
+ getFields = () => fields;
289
+ getControls = () => [
290
+ new CheckboxedListInput(fields.syllables, { label: "Letters" }),
291
+ ];
288
292
 
289
293
  async onSubmit(ctx: Context) {
290
294
  // noop
@@ -41,8 +41,8 @@ describe("file-control", () => {
41
41
  router,
42
42
  "/",
43
43
  new (class extends Form<typeof fields, unknown> {
44
- fields = fields;
45
- controls = [new Controls.File(fields.file)];
44
+ getFields = () => fields;
45
+ getControls = () => [new Controls.File(fields.file)];
46
46
 
47
47
  async onSubmit(_ctx: Context) {
48
48
  submit_called = true;
@@ -41,8 +41,8 @@ describe("photo-control", () => {
41
41
  router,
42
42
  "/",
43
43
  new (class extends Form<typeof fields, unknown> {
44
- fields = fields;
45
- controls = [new Image(fields.photo)];
44
+ getFields = () => fields;
45
+ getControls = () => [new Image(fields.photo)];
46
46
 
47
47
  async onSubmit(ctx: Context) {
48
48
  const data = await this.getParsedValues(ctx);
@@ -94,8 +94,8 @@ describe("photo-control", () => {
94
94
  router,
95
95
  "/",
96
96
  new (class extends Form<typeof fields, unknown> {
97
- fields = fields;
98
- controls = [new Image(fields.photo)];
97
+ getFields = () => fields;
98
+ getControls = () => [new Image(fields.photo)];
99
99
 
100
100
  async onSubmit(ctx: Context) {
101
101
  submit_called = true;
@@ -41,8 +41,8 @@ describe("single-reference.test", () => {
41
41
  form_file_path,
42
42
  await formTemplate("SomeForm", {
43
43
  postimport: `import {Comments} from "../collections/collections.js"; import {Fields} from "@sealcode/sealgen";`,
44
- fields: `article: new Fields.CollectionField(true, Comments.fields.article),`,
45
- controls: `new Controls.SingleReferenceDropdown(fields.article, {getLabel: item=>item.get("title")}),`,
44
+ get_fields: `()=>(<const>{article: new Fields.CollectionField(true, Comments.fields.article)})`,
45
+ get_controls: `(fields)=>[new Controls.SingleReferenceDropdown(fields.article, {getLabel: item=>item.get("title")})]`,
46
46
  validate_method: "",
47
47
  success_message: "Submit OK",
48
48
  })
@@ -39,8 +39,8 @@ describe("basic field", () => {
39
39
  router,
40
40
  "/",
41
41
  new (class extends Form<typeof fields, unknown> {
42
- fields = fields;
43
- controls = [new SimpleInput(fields.text)];
42
+ getFields = () => fields;
43
+ getControls = () => [new SimpleInput(fields.text)];
44
44
 
45
45
  async onSubmit(ctx: Context, data: FormData) {
46
46
  received_values = await this.getParsedValues(ctx);
@@ -93,8 +93,8 @@ describe("basic field", () => {
93
93
  router,
94
94
  "/",
95
95
  new (class extends Form<typeof fields, unknown> {
96
- fields = fields;
97
- controls = [new SimpleInput(fields.text)];
96
+ getFields = () => fields;
97
+ getControls = () => [new SimpleInput(fields.text)];
98
98
 
99
99
  async onSubmit() {
100
100
  //noop
@@ -135,8 +135,11 @@ describe("basic field", () => {
135
135
  router,
136
136
  "/",
137
137
  new (class extends Form<typeof fields, unknown> {
138
- fields = fields;
139
- controls = [new SimpleInput(fields.text), new Checkbox(fields.checkbox)];
138
+ getFields = () => fields;
139
+ getControls = () => [
140
+ new SimpleInput(fields.text),
141
+ new Checkbox(fields.checkbox),
142
+ ];
140
143
 
141
144
  async onSubmit() {
142
145
  submit_called = true;
@@ -25,9 +25,8 @@ export type FormFieldValidationFn<ValueType> = (
25
25
  export type FieldParsedValue<T> = T extends FormField<infer R> ? R : never;
26
26
 
27
27
  export type GetPredicate<F extends FormField> =
28
- F extends FormField<true>
29
- ? F["predicate"]
30
- : F["predicate"] | typeof predicates.undefined;
28
+ F extends FormField<true> ? F["predicate"]
29
+ : F["predicate"] | typeof predicates.undefined;
31
30
 
32
31
  // for quickly testing GetPredicate conditonally applying the undefined predicate:
33
32
  // const f = new SimpleFormField(true);
@@ -156,9 +155,8 @@ export abstract class FormField<
156
155
  if (raw == undefined || raw == null || raw == "") {
157
156
  return {
158
157
  parsed: null,
159
- message: this.required
160
- ? "empty value, but field is required"
161
- : "",
158
+ message:
159
+ this.required ? "empty value, but field is required" : "",
162
160
  raw,
163
161
  ...(validate ? { valid: !this.required } : {}),
164
162
  };
@@ -262,13 +260,14 @@ export abstract class FormField<
262
260
  vars.form_fields
263
261
  }${wrapAttribute(field_info.name)}.sealiousValueToForm(ctx, item.get("${
264
262
  field_info.name
265
- }"))`;
263
+ }"), item)`;
266
264
  }
267
265
 
268
266
  generateImportsForFieldList(field_info: ExtractedFieldInfo): {
269
267
  what: string;
270
268
  from: string;
271
269
  as?: string;
270
+ type?: boolean;
272
271
  }[] {
273
272
  return [];
274
273
  }
@@ -15,13 +15,14 @@ export * from "./float.js";
15
15
  export * from "./html.js";
16
16
  export * from "./image.js";
17
17
  export * from "./int.js";
18
+ export * from "./item-draft.js";
18
19
  export * from "./json.js";
19
20
  export * from "./markdown.js";
20
21
  export * from "./multiple-files.js";
21
22
  export * from "./number.js";
22
23
  export * from "./password.js";
23
- export * from "./phone-number.js";
24
24
  export * from "./phone-number-with-country-code.js";
25
+ export * from "./phone-number.js";
25
26
  export * from "./pick-from-list-sealious.js";
26
27
  export * from "./pick-from-list.js";
27
28
  export * from "./regexp.js";
@@ -41,8 +41,8 @@ describe("file field", () => {
41
41
  router,
42
42
  "/",
43
43
  new (class extends Form<typeof fields, unknown> {
44
- fields = fields;
45
- controls = [new Image(fields.file)];
44
+ getFields = () => fields;
45
+ getControls = () => [new Image(fields.file)];
46
46
 
47
47
  async onSubmit(ctx: Context) {
48
48
  received_values = await this.getParsedValues(ctx);
@@ -38,6 +38,7 @@ import { EnumMultipleSealious } from "./enum-multiple-sealious.js";
38
38
  import { URLField } from "./url.js";
39
39
  import { Image } from "./image.js";
40
40
  import { PhoneNumberWithCountryCode } from "./phone-number-with-country-code.js";
41
+ import { ItemDraft } from "./item-draft.js";
41
42
 
42
43
  export type SealiousToFormField<T> =
43
44
  T extends SealiousFieldTypes.Boolean ? BooleanField<false>
@@ -61,6 +62,8 @@ export type SealiousToFormField<T> =
61
62
  : T extends SealiousFieldTypes.HTML ? HTML<false>
62
63
  : T extends SealiousFieldTypes.Image ? Image<false>
63
64
  : T extends SealiousFieldTypes.Int ? Int<false>
65
+ : T extends SealiousFieldTypes.ItemDraft<infer C extends Collection> ?
66
+ ItemDraft<C>
64
67
  : T extends SealiousFieldTypes.JsonObject ? JSONField<false>
65
68
  : T extends SealiousFieldTypes.JDD ?
66
69
  TextBasedSimpleField<false, SimpleInput>
@@ -170,6 +173,9 @@ export function sealiousToFormField<SF extends SealiousField<any>>(
170
173
  if (sealious_field instanceof SealiousFieldTypes.Int) {
171
174
  return new Int(sealious_field.required) as SealiousToFormField<SF>;
172
175
  }
176
+ if (sealious_field instanceof SealiousFieldTypes.ItemDraft) {
177
+ return new ItemDraft(sealious_field) as SealiousToFormField<SF>;
178
+ }
173
179
  if (sealious_field instanceof SealiousFieldTypes.JsonObject) {
174
180
  return new JSONField(
175
181
  sealious_field.required
@@ -260,6 +266,8 @@ export async function sealiousFieldInfoToFormField(
260
266
  return new Image(field_info.is_required);
261
267
  case "int":
262
268
  return new Int(field_info.is_required);
269
+ case "item-draft":
270
+ return new ItemDraft({ target_collection: { fields: {} } } as any);
263
271
  case "json-object":
264
272
  return new JSONField(field_info.is_required);
265
273
  case "password":
@@ -0,0 +1,47 @@
1
+ /* eslint @typescript-eslint/no-unsafe-argument: off, @typescript-eslint/no-explicit-any: off, @typescript-eslint/no-unsafe-member-access: off */
2
+ import { Context } from "koa";
3
+ import { Hybrid as HybridControl } from "../controls/hybrid.js";
4
+ import { FormDataValue } from "../form-types.js";
5
+ import { FieldParseResult, FormField } from "./field.js";
6
+
7
+ export class Hybrid<Fields extends Record<string, FormField>> extends FormField<
8
+ true,
9
+ Record<string, unknown>
10
+ > {
11
+ constructor(public subfields: Fields) {
12
+ super(true);
13
+ }
14
+
15
+ public getEmptyValue(): Record<string, unknown> {
16
+ return {};
17
+ }
18
+
19
+ async parse(
20
+ ctx: Context,
21
+ raw_value: FormDataValue
22
+ ): Promise<FieldParseResult<Record<string, unknown>>> {
23
+ return {
24
+ parsable: true,
25
+ error: null,
26
+ parsed_value: Object.fromEntries(
27
+ await Promise.all(
28
+ Object.entries(this.subfields).map(
29
+ async ([key, subfield]) => [
30
+ key,
31
+ (
32
+ await subfield.getParsedValue(
33
+ ctx,
34
+ (raw_value as any)[key]
35
+ )
36
+ ).parsed as FormDataValue,
37
+ ]
38
+ )
39
+ )
40
+ ) as Record<string, unknown>,
41
+ };
42
+ }
43
+
44
+ getControl(): HybridControl {
45
+ return new HybridControl(this, this.subfields);
46
+ }
47
+ }