@sealcode/sealgen 0.15.17 → 0.15.19

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.
@@ -10,6 +10,6 @@ export declare class Checkbox extends Tickable<boolean> {
10
10
  export declare class CheckboxWithValue extends Checkbox {
11
11
  field: FormField<boolean, boolean>;
12
12
  value: string;
13
- constructor(field: FormField<boolean, boolean>, options: TickableOptions, value: string);
13
+ constructor(field: FormField<boolean, boolean>, options: TickableOptions<boolean>, value: string);
14
14
  getValueAttribute(): string;
15
15
  }
@@ -4,17 +4,18 @@ import { FormField } from "../fields/field.js";
4
4
  import { FormDataValue } from "../form-types.js";
5
5
  import { FormControlContext } from "./form-control.js";
6
6
  import { FormFieldControl } from "./form-field-control.js";
7
- export type TickableOptions = {
7
+ export type TickableOptions<T> = {
8
8
  id?: string;
9
9
  name?: string;
10
10
  label?: string;
11
11
  hide_errors?: boolean;
12
12
  readonly?: boolean;
13
+ default_value: T;
13
14
  };
14
15
  export declare abstract class Tickable<T> extends FormFieldControl {
15
16
  field: FormField<boolean, T>;
16
- options: TickableOptions;
17
- constructor(field: FormField<boolean, T>, options?: TickableOptions);
17
+ options: TickableOptions<T>;
18
+ constructor(field: FormField<boolean, T>, options: TickableOptions<T>);
18
19
  type: "checkbox" | "radio";
19
20
  abstract getValueAttribute(ctx: Context, data: Record<string, FormDataValue>, value: T): string;
20
21
  abstract isChecked(ctx: Context, data: Record<string, FormDataValue>, value: T): boolean;
@@ -32,10 +32,15 @@ export declare abstract class FormField<Required extends boolean = boolean, Pars
32
32
  abstract getEmptyValue(): ParsedValue;
33
33
  isValueValid(_ctx: Context, _value: ParsedValue): Promise<FormFieldValidationResponse>;
34
34
  abstract parse(ctx: Context, raw_value: FormDataValue): Promise<FieldParseResult<ParsedValue>>;
35
- getValue(ctx: Context, all_form_values: Record<string, FormDataValue>): Promise<{
35
+ getValue(ctx: Context, all_form_values: Record<string, FormDataValue>, validate: true): Promise<{
36
36
  valid: boolean;
37
37
  message: string;
38
- parsed: ParsedValue;
38
+ parsed: ParsedValue | null;
39
+ raw: FormDataValue;
40
+ }>;
41
+ getValue(ctx: Context, all_form_values: Record<string, FormDataValue>, validate?: false): Promise<{
42
+ message: string;
43
+ parsed: ParsedValue | null;
39
44
  raw: FormDataValue;
40
45
  }>;
41
46
  setMapToFilter(fn: (parsed: ParsedValue) => unknown): this;
@@ -19,7 +19,7 @@ export type ListSort = {
19
19
  order: "asc" | "desc";
20
20
  };
21
21
  export declare abstract class ListPage<ItemType, F extends typeof BaseListPageFields> extends Page<F> {
22
- abstract getItems(ctx: Context, page: number, itemsPerPage: number, values: Record<string, FormDataValue>): Promise<ItemType[]>;
22
+ abstract getItems(ctx: Context, page: number, itemsPerPage: number | null, values: Record<string, FormDataValue>): Promise<ItemType[]>;
23
23
  abstract getTotalPages(ctx: Context, itemsPerPage: number, values: Record<string, FormDataValue>): Promise<number>;
24
24
  abstract renderItem(ctx: Context, item: ItemType, index: number): Promise<FlatTemplatable>;
25
25
  filterFields: Record<string, FormField>;
@@ -28,7 +28,7 @@ export declare abstract class ListPage<ItemType, F extends typeof BaseListPageFi
28
28
  renderListContainer(_: Context, content: Templatable): FlatTemplatable;
29
29
  getPaginationConfig(ctx: Context): Promise<{
30
30
  page: number;
31
- itemsPerPage: number;
31
+ itemsPerPage: number | null;
32
32
  }>;
33
33
  renderItems(ctx: Context, values?: Record<string, FormDataValue>, items?: ItemType[]): Promise<FlatTemplatable>;
34
34
  render(ctx: Context): Promise<FlatTemplatable>;
@@ -28,11 +28,12 @@ class CheckboxGroup extends FormFieldControl {
28
28
  false,
29
29
  this.field.name,
30
30
  false,
31
- values.includes(option)
31
+ (values || []).includes(option)
32
32
  ),
33
33
  {
34
34
  id: this.field.name + "__" + option,
35
- label: (this.options.checkboxNameToLabel || ((s) => s))(option)
35
+ label: (this.options.checkboxNameToLabel || ((s) => s))(option),
36
+ default_value: false
36
37
  },
37
38
  option
38
39
  )
@@ -12,7 +12,7 @@ class CheckboxedListInput extends FormFieldControl {
12
12
  fctx.ctx,
13
13
  fctx.data
14
14
  );
15
- const pickedValues = Object.entries(currentValue).filter(([, is_on]) => is_on).map(([key]) => key);
15
+ const pickedValues = Object.entries(currentValue || {}).filter(([, is_on]) => is_on).map(([key]) => key);
16
16
  const [options, isVisible] = await Promise.all([
17
17
  this.field.generateOptions(fctx.ctx),
18
18
  this.field.isVisible(fctx.ctx)
@@ -16,7 +16,10 @@ class File extends SimpleInput {
16
16
  </div>`;
17
17
  }
18
18
  async preInput(fctx, data) {
19
- const { parsed } = await this.field.getValue(fctx.ctx, data);
19
+ let { parsed } = await this.field.getValue(fctx.ctx, data);
20
+ if (parsed === null) {
21
+ parsed = { old: null, new: null };
22
+ }
20
23
  return tempstream`${this.renderFilePreview(
21
24
  fctx.ctx,
22
25
  parsed.old
@@ -36,7 +39,7 @@ class File extends SimpleInput {
36
39
  return {
37
40
  ...original,
38
41
  name: this.field.name + "[new]",
39
- required: original.required && !parsed.old,
42
+ required: original.required && !parsed?.old,
40
43
  value: false
41
44
  };
42
45
  }
@@ -1,7 +1,7 @@
1
1
  import { Tickable } from "./tickable.js";
2
2
  class Radio extends Tickable {
3
3
  constructor(field, value, options) {
4
- super(field, options);
4
+ super(field, { default_value: "", ...options });
5
5
  this.field = field;
6
6
  this.value = value;
7
7
  this.type = "radio";
@@ -28,7 +28,8 @@ class SimpleInput extends FormFieldControl {
28
28
  const required = this.field.required;
29
29
  const { parsed, raw, valid } = await this.field.getValue(
30
30
  fctx.ctx,
31
- fctx.data
31
+ fctx.data,
32
+ true
32
33
  );
33
34
  return Object.fromEntries([
34
35
  ["id", this.getID()],
@@ -68,7 +69,8 @@ class SimpleInput extends FormFieldControl {
68
69
  const label = this.getLabel();
69
70
  const { valid, message } = await this.field.getValue(
70
71
  fctx.ctx,
71
- fctx.data
72
+ fctx.data,
73
+ true
72
74
  );
73
75
  return inputWrapper(
74
76
  this.getWrapperModifiers(),
@@ -2,7 +2,7 @@ import { attribute } from "../../sanitize.js";
2
2
  import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper.js";
3
3
  import { FormFieldControl } from "./form-field-control.js";
4
4
  class Tickable extends FormFieldControl {
5
- constructor(field, options = {}) {
5
+ constructor(field, options) {
6
6
  super([field]);
7
7
  this.field = field;
8
8
  this.options = options;
@@ -21,10 +21,12 @@ class Tickable extends FormFieldControl {
21
21
  ];
22
22
  }
23
23
  async render(fctx) {
24
- const { parsed, valid, message } = await this.field.getValue(
25
- fctx.ctx,
26
- fctx.data
27
- );
24
+ const {
25
+ parsed: _parsed,
26
+ valid,
27
+ message
28
+ } = await this.field.getValue(fctx.ctx, fctx.data, true);
29
+ const parsed = _parsed === null ? this.options.default_value : _parsed;
28
30
  const value = this.getValueAttribute(fctx.ctx, fctx.data, parsed);
29
31
  const id = this.makeInputID(fctx.ctx, fctx.data, parsed);
30
32
  const label = this.options.label != void 0 ? this.options.label : this.field.name;
@@ -15,32 +15,36 @@ class FormField {
15
15
  this.name = fieldname;
16
16
  }
17
17
  async _validate(ctx, value) {
18
- if (this.required && (value === "" || value === null || value === void 0)) {
19
- return {
20
- valid: false,
21
- message: t(
22
- ctx,
23
- "field_is_required",
24
- [],
25
- "This field is required"
26
- )
27
- };
18
+ if (value === "" || value === null || value === void 0) {
19
+ if (this.required) {
20
+ return {
21
+ valid: false,
22
+ message: t(
23
+ ctx,
24
+ "field_is_required",
25
+ [],
26
+ "This field is required"
27
+ )
28
+ };
29
+ } else {
30
+ return { valid: true, message: "Empty, but not required" };
31
+ }
28
32
  }
29
33
  return this.isValueValid(ctx, value);
30
34
  }
31
35
  async isValueValid(_ctx, _value) {
32
36
  return { valid: true, message: "" };
33
37
  }
34
- async getValue(ctx, all_form_values) {
35
- if (Object.keys(all_form_values || {}).length === 0) {
38
+ async getValue(ctx, all_form_values, validate = false) {
39
+ const raw = all_form_values[this.name];
40
+ if (raw == void 0 || raw == null || raw == "") {
36
41
  return {
37
- parsed: this.getEmptyValue(),
38
- valid: true,
39
- message: "",
40
- raw: ""
42
+ parsed: null,
43
+ message: "empty value",
44
+ raw,
45
+ ...validate ? { valid: !this.required } : {}
41
46
  };
42
47
  }
43
- const raw = all_form_values[this.name];
44
48
  const {
45
49
  parsable,
46
50
  parsed_value,
@@ -48,19 +52,23 @@ class FormField {
48
52
  } = await this.parse(ctx, raw);
49
53
  if (!parsable) {
50
54
  return {
51
- valid: false,
55
+ ...validate ? { valid: false } : {},
52
56
  parsed: this.getEmptyValue(),
53
57
  raw,
54
58
  message: parse_error || "Error"
55
59
  };
56
60
  }
57
- const { valid, message } = await this._validate(ctx, parsed_value);
58
- return {
59
- parsed: parsed_value,
60
- valid,
61
- message,
62
- raw: all_form_values[this.name]
63
- };
61
+ if (validate) {
62
+ const { valid, message } = await this._validate(ctx, parsed_value);
63
+ return {
64
+ parsed: parsed_value,
65
+ ...validate ? { valid } : {},
66
+ message,
67
+ raw: all_form_values[this.name]
68
+ };
69
+ } else {
70
+ return { parsed: parsed_value, raw, message: "parsed" };
71
+ }
64
72
  }
65
73
  setMapToFilter(fn) {
66
74
  this.mapToFilter = fn;
@@ -4,6 +4,7 @@ import Koa from "koa";
4
4
  import { mount } from "../../mount.js";
5
5
  import { Form } from "../form.js";
6
6
  import { getBrowser } from "../../utils/browser-creator.js";
7
+ import assert from "assert";
7
8
  import { SimpleFormField } from "./simple-form-field.js";
8
9
  import { SimpleInput } from "../controls/simple-input.js";
9
10
  describe("basic field", () => {
@@ -53,6 +54,52 @@ describe("basic field", () => {
53
54
  page = await context.newPage();
54
55
  await page.goto(`http://localhost:${port}`);
55
56
  });
57
+ it("Doesn't call `isValueValid` and `parse` if the value is empty", async () => {
58
+ const port = await getPort();
59
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
60
+ const app = new Koa();
61
+ const router = new Router();
62
+ let is_value_valid_called = false;
63
+ let parse_called = false;
64
+ const fields = {
65
+ text: new class MockField extends SimpleFormField {
66
+ async isValueValid() {
67
+ is_value_valid_called = true;
68
+ return true;
69
+ }
70
+ async parse() {
71
+ parse_called = true;
72
+ return true;
73
+ }
74
+ }(false)
75
+ };
76
+ mount(
77
+ router,
78
+ "/",
79
+ new class extends Form {
80
+ constructor() {
81
+ super(...arguments);
82
+ this.fields = fields;
83
+ this.controls = [new SimpleInput(fields.text)];
84
+ }
85
+ async onSubmit() {
86
+ }
87
+ async canAccess(_) {
88
+ return { canAccess: true, message: "" };
89
+ }
90
+ }(),
91
+ true
92
+ );
93
+ app.use(router.routes()).use(router.allowedMethods());
94
+ server = app.listen(port);
95
+ browser = await getBrowser();
96
+ const context = await browser.newContext();
97
+ page = await context.newPage();
98
+ await page.goto(`http://localhost:${port}`);
99
+ await page.getByRole("button", { name: "Wy\u015Blij" }).click();
100
+ assert(is_value_valid_called == false);
101
+ assert(parse_called == false);
102
+ });
56
103
  });
57
104
  });
58
105
  //# sourceMappingURL=field.test.js.map
@@ -50,7 +50,7 @@ class File extends FormField {
50
50
  }
51
51
  async getDatabaseValue(ctx, data) {
52
52
  const { parsed } = await this.getValue(ctx, data);
53
- return parsed.new || void 0;
53
+ return parsed?.new || void 0;
54
54
  }
55
55
  getEmptyValue() {
56
56
  return { old: null, new: null };
@@ -81,6 +81,7 @@ describe("form test", () => {
81
81
  }
82
82
  }
83
83
  );
84
+ console.log(res_axios.data);
84
85
  if (!res_axios.data.includes("Some fields are invalid")) {
85
86
  throw new Error("when sending a empty request with axios, the error didnt appear");
86
87
  }
package/lib/page/list.js CHANGED
@@ -6,9 +6,10 @@ import qs from "qs";
6
6
  import { NumberField } from "../forms/fields/number.js";
7
7
  import { SimpleFormField } from "../forms/fields/simple-form-field.js";
8
8
  const BasePagePropsShape = {};
9
+ const DEFAULT_ITEMS_PER_PAGE = 12;
9
10
  const BaseListPageFields = {
10
11
  page: new NumberField(false, 1),
11
- itemsPerPage: new NumberField(false, 12),
12
+ itemsPerPage: new NumberField(false, DEFAULT_ITEMS_PER_PAGE),
12
13
  sort: new SimpleFormField(false)
13
14
  };
14
15
  const SORT_SEPARATOR = ":";
@@ -48,7 +49,10 @@ class ListPage extends Page {
48
49
  }
49
50
  async getPaginationConfig(ctx) {
50
51
  const values = await this.extractRawValues(ctx);
51
- const { parsed: page } = await this.fields.page.getValue(ctx, values);
52
+ let { parsed: page } = await this.fields.page.getValue(ctx, values);
53
+ if (!page) {
54
+ page = 1;
55
+ }
52
56
  const { parsed: itemsPerPage } = await this.fields.itemsPerPage.getValue(ctx, values);
53
57
  return { page, itemsPerPage };
54
58
  }
@@ -70,7 +74,11 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
70
74
  }
71
75
  async renderPagination(ctx, values) {
72
76
  const { itemsPerPage, page } = await this.getPaginationConfig(ctx);
73
- const totalIems = await this.getTotalPages(ctx, itemsPerPage, values);
77
+ const totalIems = await this.getTotalPages(
78
+ ctx,
79
+ itemsPerPage || DEFAULT_ITEMS_PER_PAGE,
80
+ values
81
+ );
74
82
  return tempstream`<div class="list-pagination">
75
83
  <div class="list-pagination__left">
76
84
  ${page > 1 ? this.renderPageButton(ctx, 1, "Pierwsza strona") : ""}
@@ -42,9 +42,9 @@ class MountableWithFields extends Mountable {
42
42
  let valid = true;
43
43
  const form_messages = [];
44
44
  await Promise.all(
45
- Object.entries(values).filter(([fieldname]) => fieldname in this.fields).map(async ([key]) => {
45
+ Object.keys(this.fields).map(async (key) => {
46
46
  const field = this.fields[key];
47
- const { valid: fieldvalid, message: fieldmessage } = await field.getValue(ctx, values);
47
+ const { valid: fieldvalid, message: fieldmessage } = await field.getValue(ctx, values, true);
48
48
  if (!fieldvalid) {
49
49
  valid = false;
50
50
  field_errors[field.name] = fieldmessage;
@@ -81,7 +81,7 @@ class MountableWithFields extends Mountable {
81
81
  const result = {};
82
82
  const promises = Object.entries(this.fields).map(
83
83
  async ([key, field]) => {
84
- const { parsed } = await field.getValue(ctx, raw_values);
84
+ const { parsed } = await field.getValue(ctx, raw_values, false);
85
85
  result[key] = parsed;
86
86
  }
87
87
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.15.17",
3
+ "version": "0.15.19",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -51,7 +51,7 @@ export class CheckboxGroup<
51
51
  false,
52
52
  this.field.name,
53
53
  false,
54
- values.includes(option)
54
+ (values || []).includes(option)
55
55
  ),
56
56
  {
57
57
  id: this.field.name + "__" + option,
@@ -59,6 +59,7 @@ export class CheckboxGroup<
59
59
  this.options.checkboxNameToLabel ||
60
60
  ((s: string) => s)
61
61
  )(option),
62
+ default_value: false,
62
63
  },
63
64
  option
64
65
  )
@@ -22,7 +22,7 @@ export class Checkbox extends Tickable<boolean> {
22
22
  export class CheckboxWithValue extends Checkbox {
23
23
  constructor(
24
24
  public field: FormField<boolean, boolean>,
25
- options: TickableOptions,
25
+ options: TickableOptions<boolean>,
26
26
  public value: string
27
27
  ) {
28
28
  super(field, options);
@@ -17,7 +17,7 @@ export class CheckboxedListInput extends FormFieldControl {
17
17
  fctx.ctx,
18
18
  fctx.data
19
19
  );
20
- const pickedValues = Object.entries(currentValue)
20
+ const pickedValues = Object.entries(currentValue || {})
21
21
  .filter(([, is_on]) => is_on)
22
22
  .map(([key]) => key);
23
23
  const [options, isVisible] = await Promise.all([
@@ -30,7 +30,10 @@ export class File extends SimpleInput<FileContainer> {
30
30
  fctx: FormControlContext,
31
31
  data: Record<string, FormDataValue>
32
32
  ) {
33
- const { parsed } = await this.field.getValue(fctx.ctx, data);
33
+ let { parsed } = await this.field.getValue(fctx.ctx, data);
34
+ if (parsed === null) {
35
+ parsed = { old: null, new: null };
36
+ }
34
37
  return tempstream/* HTML */ `${this.renderFilePreview(
35
38
  fctx.ctx,
36
39
  parsed.old
@@ -53,7 +56,7 @@ export class File extends SimpleInput<FileContainer> {
53
56
  return {
54
57
  ...original,
55
58
  name: this.field.name + "[new]",
56
- required: original.required && !parsed.old,
59
+ required: original.required && !parsed?.old,
57
60
  value: false,
58
61
  };
59
62
  }
@@ -11,7 +11,7 @@ export class Radio extends Tickable<string> {
11
11
  public value: string,
12
12
  options: { label?: string }
13
13
  ) {
14
- super(field, options);
14
+ super(field, { default_value: "", ...options });
15
15
  }
16
16
 
17
17
  makeInputID(): string {
@@ -80,7 +80,8 @@ export class SimpleInput<
80
80
  const required = this.field.required;
81
81
  const { parsed, raw, valid } = await this.field.getValue(
82
82
  fctx.ctx,
83
- fctx.data
83
+ fctx.data,
84
+ true
84
85
  );
85
86
  return Object.fromEntries([
86
87
  <const>["id", this.getID()],
@@ -132,7 +133,8 @@ export class SimpleInput<
132
133
 
133
134
  const { valid, message } = await this.field.getValue(
134
135
  fctx.ctx,
135
- fctx.data
136
+ fctx.data,
137
+ true
136
138
  );
137
139
  return inputWrapper(
138
140
  this.getWrapperModifiers(),
@@ -7,18 +7,19 @@ import { FormDataValue } from "../form-types.js";
7
7
  import { FormControlContext } from "./form-control.js";
8
8
  import { FormFieldControl } from "./form-field-control.js";
9
9
 
10
- export type TickableOptions = {
10
+ export type TickableOptions<T> = {
11
11
  id?: string;
12
12
  name?: string;
13
13
  label?: string;
14
14
  hide_errors?: boolean;
15
15
  readonly?: boolean;
16
+ default_value: T;
16
17
  };
17
18
 
18
19
  export abstract class Tickable<T> extends FormFieldControl {
19
20
  constructor(
20
21
  public field: FormField<boolean, T>,
21
- public options: TickableOptions = {}
22
+ public options: TickableOptions<T>
22
23
  ) {
23
24
  super([field]);
24
25
  }
@@ -65,10 +66,13 @@ export abstract class Tickable<T> extends FormFieldControl {
65
66
 
66
67
  async render(fctx: FormControlContext): Promise<FlatTemplatable> {
67
68
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
68
- const { parsed, valid, message } = await this.field.getValue(
69
- fctx.ctx,
70
- fctx.data
71
- );
69
+ const {
70
+ parsed: _parsed,
71
+ valid,
72
+ message,
73
+ } = await this.field.getValue(fctx.ctx, fctx.data, true);
74
+ const parsed = _parsed === null ? this.options.default_value : _parsed;
75
+
72
76
  const value = this.getValueAttribute(fctx.ctx, fctx.data, parsed);
73
77
  const id = this.makeInputID(fctx.ctx, fctx.data, parsed);
74
78
  const label =
@@ -10,6 +10,7 @@ import assert from "assert";
10
10
  import { SimpleFormField } from "./simple-form-field.js";
11
11
  import { SimpleInput } from "../controls/simple-input.js";
12
12
  import { FormData } from "../form-types.js";
13
+ import { Field } from "sealious";
13
14
 
14
15
  describe("basic field", () => {
15
16
  describe("basic tests", async () => {
@@ -63,5 +64,57 @@ describe("basic field", () => {
63
64
  page = await context.newPage();
64
65
  await page.goto(`http://localhost:${port}`);
65
66
  });
67
+
68
+ it("Doesn't call `isValueValid` and `parse` if the value is empty", async () => {
69
+ const port = await getPort();
70
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
71
+ const app = new Koa();
72
+ const router = new Router();
73
+
74
+ let is_value_valid_called = false;
75
+ let parse_called = false;
76
+
77
+ const fields = {
78
+ text: new (class MockField extends SimpleFormField<false> {
79
+ public async isValueValid() {
80
+ is_value_valid_called = true;
81
+ return true as any;
82
+ }
83
+
84
+ public async parse() {
85
+ parse_called = true;
86
+ return true as any;
87
+ }
88
+ })(false),
89
+ };
90
+
91
+ mount(
92
+ router,
93
+ "/",
94
+ new (class extends Form<typeof fields, unknown> {
95
+ fields = fields;
96
+ controls = [new SimpleInput(fields.text)];
97
+
98
+ async onSubmit() {
99
+ //noop
100
+ }
101
+
102
+ async canAccess(_: Context) {
103
+ return { canAccess: true, message: "" };
104
+ }
105
+ })(),
106
+ true
107
+ );
108
+ app.use(router.routes()).use(router.allowedMethods());
109
+ server = app.listen(port);
110
+
111
+ browser = await getBrowser();
112
+ const context = await browser.newContext();
113
+ page = await context.newPage();
114
+ await page.goto(`http://localhost:${port}`);
115
+ await page.getByRole("button", { name: "Wyślij" }).click();
116
+ assert(is_value_valid_called == false);
117
+ assert(parse_called == false);
118
+ });
66
119
  });
67
120
  });
@@ -64,20 +64,21 @@ export abstract class FormField<
64
64
  ctx: Context,
65
65
  value: ParsedValue
66
66
  ): Promise<FormFieldValidationResponse> {
67
- if (
68
- this.required &&
69
- (value === "" || value === null || value === undefined)
70
- ) {
71
- return {
72
- valid: false,
73
-
74
- message: t(
75
- ctx,
76
- "field_is_required",
77
- [],
78
- "This field is required"
79
- ),
80
- };
67
+ if (value === "" || value === null || value === undefined) {
68
+ if (this.required) {
69
+ return {
70
+ valid: false,
71
+
72
+ message: t(
73
+ ctx,
74
+ "field_is_required",
75
+ [],
76
+ "This field is required"
77
+ ),
78
+ };
79
+ } else {
80
+ return { valid: true, message: "Empty, but not required" };
81
+ }
81
82
  }
82
83
  return this.isValueValid(ctx, value);
83
84
  }
@@ -98,23 +99,42 @@ export abstract class FormField<
98
99
 
99
100
  async getValue(
100
101
  ctx: Context,
101
- all_form_values: Record<string, FormDataValue>
102
+ all_form_values: Record<string, FormDataValue>,
103
+ validate: true
102
104
  ): Promise<{
103
105
  valid: boolean;
104
106
  message: string;
105
- parsed: ParsedValue;
107
+ parsed: ParsedValue | null;
108
+ raw: FormDataValue;
109
+ }>;
110
+ async getValue(
111
+ ctx: Context,
112
+ all_form_values: Record<string, FormDataValue>,
113
+ validate?: false
114
+ ): Promise<{
115
+ message: string;
116
+ parsed: ParsedValue | null;
117
+ raw: FormDataValue;
118
+ }>;
119
+ async getValue(
120
+ ctx: Context,
121
+ all_form_values: Record<string, FormDataValue>,
122
+ validate = false
123
+ ): Promise<{
124
+ valid?: boolean;
125
+ message: string;
126
+ parsed: ParsedValue | null;
106
127
  raw: FormDataValue;
107
128
  }> {
108
- if (Object.keys(all_form_values || {}).length === 0) {
109
- // don't validate on virgin form
129
+ const raw = all_form_values[this.name];
130
+ if (raw == undefined || raw == null || raw == "") {
110
131
  return {
111
- parsed: this.getEmptyValue(),
112
- valid: true,
113
- message: "",
114
- raw: "",
132
+ parsed: null,
133
+ message: "empty value",
134
+ raw,
135
+ ...(validate ? { valid: !this.required } : {}),
115
136
  };
116
137
  }
117
- const raw = all_form_values[this.name];
118
138
  const {
119
139
  parsable,
120
140
  parsed_value,
@@ -122,19 +142,23 @@ export abstract class FormField<
122
142
  } = await this.parse(ctx, raw);
123
143
  if (!parsable) {
124
144
  return {
125
- valid: false,
145
+ ...(validate ? { valid: false } : {}),
126
146
  parsed: this.getEmptyValue(),
127
147
  raw,
128
148
  message: parse_error || "Error",
129
149
  };
130
150
  }
131
- const { valid, message } = await this._validate(ctx, parsed_value);
132
- return {
133
- parsed: parsed_value,
134
- valid,
135
- message,
136
- raw: all_form_values[this.name],
137
- };
151
+ if (validate) {
152
+ const { valid, message } = await this._validate(ctx, parsed_value);
153
+ return {
154
+ parsed: parsed_value,
155
+ ...(validate ? { valid } : {}),
156
+ message,
157
+ raw: all_form_values[this.name],
158
+ };
159
+ } else {
160
+ return { parsed: parsed_value, raw, message: "parsed" };
161
+ }
138
162
  }
139
163
 
140
164
  setMapToFilter(fn: (parsed: ParsedValue) => unknown) {
@@ -76,7 +76,7 @@ export class File<Required extends boolean> extends FormField<
76
76
 
77
77
  async getDatabaseValue(ctx: Context, data: Record<string, FormDataValue>) {
78
78
  const { parsed } = await this.getValue(ctx, data);
79
- return parsed.new || undefined;
79
+ return parsed?.new || undefined;
80
80
  }
81
81
 
82
82
  getEmptyValue() {
@@ -91,6 +91,7 @@ describe("form test", () => {
91
91
  },
92
92
  }
93
93
  );
94
+ console.log(res_axios.data);
94
95
  if (!res_axios.data.includes("Some fields are invalid")) {
95
96
  throw new Error("when sending a empty request with axios, the error didnt appear");
96
97
  }
package/src/page/list.ts CHANGED
@@ -14,9 +14,11 @@ import { FormDataValue } from "../forms/form-types.js";
14
14
  export const BasePagePropsShape = <const>{};
15
15
  export type BasePageProps = ShapeToType<typeof BasePagePropsShape>;
16
16
 
17
+ const DEFAULT_ITEMS_PER_PAGE = 12;
18
+
17
19
  export const BaseListPageFields = <const>{
18
20
  page: new NumberField(false, 1),
19
- itemsPerPage: new NumberField(false, 12),
21
+ itemsPerPage: new NumberField(false, DEFAULT_ITEMS_PER_PAGE),
20
22
  sort: new SimpleFormField(false),
21
23
  };
22
24
 
@@ -51,7 +53,7 @@ export abstract class ListPage<
51
53
  abstract getItems(
52
54
  ctx: Context,
53
55
  page: number,
54
- itemsPerPage: number,
56
+ itemsPerPage: number | null,
55
57
  values: Record<string, FormDataValue>
56
58
  ): Promise<ItemType[]>;
57
59
 
@@ -83,7 +85,10 @@ export abstract class ListPage<
83
85
 
84
86
  async getPaginationConfig(ctx: Context) {
85
87
  const values = await this.extractRawValues(ctx);
86
- const { parsed: page } = await this.fields.page.getValue(ctx, values);
88
+ let { parsed: page } = await this.fields.page.getValue(ctx, values);
89
+ if (!page) {
90
+ page = 1;
91
+ }
87
92
  const { parsed: itemsPerPage } =
88
93
  await this.fields.itemsPerPage.getValue(ctx, values);
89
94
  return { page, itemsPerPage };
@@ -120,7 +125,11 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
120
125
  values: Record<string, FormDataValue>
121
126
  ): Promise<FlatTemplatable> {
122
127
  const { itemsPerPage, page } = await this.getPaginationConfig(ctx);
123
- const totalIems = await this.getTotalPages(ctx, itemsPerPage, values);
128
+ const totalIems = await this.getTotalPages(
129
+ ctx,
130
+ itemsPerPage || DEFAULT_ITEMS_PER_PAGE,
131
+ values
132
+ );
124
133
 
125
134
  return tempstream/* HTML */ `<div class="list-pagination">
126
135
  <div class="list-pagination__left">
@@ -82,18 +82,17 @@ export abstract class MountableWithFields<
82
82
  const field_errors = {} as Record<keyof Fields, string>;
83
83
  let valid = true;
84
84
  const form_messages = [] as FormMessage[];
85
+
85
86
  await Promise.all(
86
- Object.entries(values)
87
- .filter(([fieldname]) => fieldname in this.fields)
88
- .map(async ([key]: [keyof F, FormDataValue]) => {
89
- const field = this.fields[key];
90
- const { valid: fieldvalid, message: fieldmessage } =
91
- await field.getValue(ctx, values);
92
- if (!fieldvalid) {
93
- valid = false;
94
- field_errors[field.name] = fieldmessage;
95
- }
96
- })
87
+ Object.keys(this.fields).map(async (key: keyof F) => {
88
+ const field = this.fields[key];
89
+ const { valid: fieldvalid, message: fieldmessage } =
90
+ await field.getValue(ctx, values, true);
91
+ if (!fieldvalid) {
92
+ valid = false;
93
+ field_errors[field.name] = fieldmessage;
94
+ }
95
+ })
97
96
  );
98
97
  const formValidationResult = await this.validateValues(ctx, values);
99
98
  if (!formValidationResult.valid) {
@@ -143,7 +142,7 @@ export abstract class MountableWithFields<
143
142
  const promises = Object.entries(this.fields).map(
144
143
  async ([key, field]) => {
145
144
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
146
- const { parsed } = await field.getValue(ctx, raw_values);
145
+ const { parsed } = await field.getValue(ctx, raw_values, false);
147
146
  result[key] = parsed;
148
147
  }
149
148
  );