@sealcode/sealgen 0.15.18 → 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.
- package/@types/src/forms/controls/checkbox.d.ts +1 -1
- package/@types/src/forms/controls/tickable.d.ts +4 -3
- package/@types/src/forms/fields/field.d.ts +7 -2
- package/@types/src/page/list.d.ts +2 -2
- package/lib/forms/controls/checkbox-group.js +3 -2
- package/lib/forms/controls/checkboxed-list-input.js +1 -1
- package/lib/forms/controls/file.js +5 -2
- package/lib/forms/controls/radio.js +1 -1
- package/lib/forms/controls/simple-input.js +4 -2
- package/lib/forms/controls/tickable.js +7 -5
- package/lib/forms/fields/field.js +19 -15
- package/lib/forms/fields/field.test.js +7 -1
- package/lib/forms/fields/file.js +1 -1
- package/lib/forms/form.test.js +1 -0
- package/lib/page/list.js +11 -3
- package/lib/page/mountable-with-fields.js +3 -3
- package/package.json +1 -1
- package/src/forms/controls/checkbox-group.ts +2 -1
- package/src/forms/controls/checkbox.ts +1 -1
- package/src/forms/controls/checkboxed-list-input.ts +1 -1
- package/src/forms/controls/file.ts +5 -2
- package/src/forms/controls/radio.ts +1 -1
- package/src/forms/controls/simple-input.ts +4 -2
- package/src/forms/controls/tickable.ts +10 -6
- package/src/forms/fields/field.test.ts +8 -1
- package/src/forms/fields/field.ts +40 -17
- package/src/forms/fields/file.ts +1 -1
- package/src/forms/form.test.ts +1 -0
- package/src/page/list.ts +13 -4
- package/src/page/mountable-with-fields.ts +11 -12
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
42
|
+
required: original.required && !parsed?.old,
|
|
40
43
|
value: false
|
|
41
44
|
};
|
|
42
45
|
}
|
|
@@ -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 {
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
@@ -35,16 +35,16 @@ class FormField {
|
|
|
35
35
|
async isValueValid(_ctx, _value) {
|
|
36
36
|
return { valid: true, message: "" };
|
|
37
37
|
}
|
|
38
|
-
async getValue(ctx, all_form_values) {
|
|
39
|
-
|
|
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 == "") {
|
|
40
41
|
return {
|
|
41
|
-
parsed:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
parsed: null,
|
|
43
|
+
message: "empty value",
|
|
44
|
+
raw,
|
|
45
|
+
...validate ? { valid: !this.required } : {}
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
|
-
const raw = all_form_values[this.name];
|
|
48
48
|
const {
|
|
49
49
|
parsable,
|
|
50
50
|
parsed_value,
|
|
@@ -52,19 +52,23 @@ class FormField {
|
|
|
52
52
|
} = await this.parse(ctx, raw);
|
|
53
53
|
if (!parsable) {
|
|
54
54
|
return {
|
|
55
|
-
valid: false,
|
|
55
|
+
...validate ? { valid: false } : {},
|
|
56
56
|
parsed: this.getEmptyValue(),
|
|
57
57
|
raw,
|
|
58
58
|
message: parse_error || "Error"
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|
|
68
72
|
}
|
|
69
73
|
setMapToFilter(fn) {
|
|
70
74
|
this.mapToFilter = fn;
|
|
@@ -54,18 +54,23 @@ describe("basic field", () => {
|
|
|
54
54
|
page = await context.newPage();
|
|
55
55
|
await page.goto(`http://localhost:${port}`);
|
|
56
56
|
});
|
|
57
|
-
it("Doesn't
|
|
57
|
+
it("Doesn't call `isValueValid` and `parse` if the value is empty", async () => {
|
|
58
58
|
const port = await getPort();
|
|
59
59
|
console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
|
|
60
60
|
const app = new Koa();
|
|
61
61
|
const router = new Router();
|
|
62
62
|
let is_value_valid_called = false;
|
|
63
|
+
let parse_called = false;
|
|
63
64
|
const fields = {
|
|
64
65
|
text: new class MockField extends SimpleFormField {
|
|
65
66
|
async isValueValid() {
|
|
66
67
|
is_value_valid_called = true;
|
|
67
68
|
return true;
|
|
68
69
|
}
|
|
70
|
+
async parse() {
|
|
71
|
+
parse_called = true;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
69
74
|
}(false)
|
|
70
75
|
};
|
|
71
76
|
mount(
|
|
@@ -93,6 +98,7 @@ describe("basic field", () => {
|
|
|
93
98
|
await page.goto(`http://localhost:${port}`);
|
|
94
99
|
await page.getByRole("button", { name: "Wy\u015Blij" }).click();
|
|
95
100
|
assert(is_value_valid_called == false);
|
|
101
|
+
assert(parse_called == false);
|
|
96
102
|
});
|
|
97
103
|
});
|
|
98
104
|
});
|
package/lib/forms/fields/file.js
CHANGED
package/lib/forms/form.test.js
CHANGED
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
|
+
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
|
-
|
|
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(
|
|
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.
|
|
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
|
@@ -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
|
-
|
|
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
|
|
59
|
+
required: original.required && !parsed?.old,
|
|
57
60
|
value: false,
|
|
58
61
|
};
|
|
59
62
|
}
|
|
@@ -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 {
|
|
69
|
-
|
|
70
|
-
|
|
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 =
|
|
@@ -65,13 +65,14 @@ describe("basic field", () => {
|
|
|
65
65
|
await page.goto(`http://localhost:${port}`);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it("Doesn't
|
|
68
|
+
it("Doesn't call `isValueValid` and `parse` if the value is empty", async () => {
|
|
69
69
|
const port = await getPort();
|
|
70
70
|
console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
|
|
71
71
|
const app = new Koa();
|
|
72
72
|
const router = new Router();
|
|
73
73
|
|
|
74
74
|
let is_value_valid_called = false;
|
|
75
|
+
let parse_called = false;
|
|
75
76
|
|
|
76
77
|
const fields = {
|
|
77
78
|
text: new (class MockField extends SimpleFormField<false> {
|
|
@@ -79,6 +80,11 @@ describe("basic field", () => {
|
|
|
79
80
|
is_value_valid_called = true;
|
|
80
81
|
return true as any;
|
|
81
82
|
}
|
|
83
|
+
|
|
84
|
+
public async parse() {
|
|
85
|
+
parse_called = true;
|
|
86
|
+
return true as any;
|
|
87
|
+
}
|
|
82
88
|
})(false),
|
|
83
89
|
};
|
|
84
90
|
|
|
@@ -108,6 +114,7 @@ describe("basic field", () => {
|
|
|
108
114
|
await page.goto(`http://localhost:${port}`);
|
|
109
115
|
await page.getByRole("button", { name: "Wyślij" }).click();
|
|
110
116
|
assert(is_value_valid_called == false);
|
|
117
|
+
assert(parse_called == false);
|
|
111
118
|
});
|
|
112
119
|
});
|
|
113
120
|
});
|
|
@@ -99,23 +99,42 @@ export abstract class FormField<
|
|
|
99
99
|
|
|
100
100
|
async getValue(
|
|
101
101
|
ctx: Context,
|
|
102
|
-
all_form_values: Record<string, FormDataValue
|
|
102
|
+
all_form_values: Record<string, FormDataValue>,
|
|
103
|
+
validate: true
|
|
103
104
|
): Promise<{
|
|
104
105
|
valid: boolean;
|
|
105
106
|
message: string;
|
|
106
|
-
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;
|
|
107
127
|
raw: FormDataValue;
|
|
108
128
|
}> {
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
const raw = all_form_values[this.name];
|
|
130
|
+
if (raw == undefined || raw == null || raw == "") {
|
|
111
131
|
return {
|
|
112
|
-
parsed:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
parsed: null,
|
|
133
|
+
message: "empty value",
|
|
134
|
+
raw,
|
|
135
|
+
...(validate ? { valid: !this.required } : {}),
|
|
116
136
|
};
|
|
117
137
|
}
|
|
118
|
-
const raw = all_form_values[this.name];
|
|
119
138
|
const {
|
|
120
139
|
parsable,
|
|
121
140
|
parsed_value,
|
|
@@ -123,19 +142,23 @@ export abstract class FormField<
|
|
|
123
142
|
} = await this.parse(ctx, raw);
|
|
124
143
|
if (!parsable) {
|
|
125
144
|
return {
|
|
126
|
-
valid: false,
|
|
145
|
+
...(validate ? { valid: false } : {}),
|
|
127
146
|
parsed: this.getEmptyValue(),
|
|
128
147
|
raw,
|
|
129
148
|
message: parse_error || "Error",
|
|
130
149
|
};
|
|
131
150
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
}
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
setMapToFilter(fn: (parsed: ParsedValue) => unknown) {
|
package/src/forms/fields/file.ts
CHANGED
|
@@ -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
|
|
79
|
+
return parsed?.new || undefined;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
getEmptyValue() {
|
package/src/forms/form.test.ts
CHANGED
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,
|
|
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
|
-
|
|
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(
|
|
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.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
);
|