vanjs-jsf 0.0.5 → 0.0.8
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/dist/JsfUtils.d.ts +0 -1
- package/dist/JsfUtils.js +10 -0
- package/dist/VanJSComponent.d.ts +0 -1
- package/dist/VanJSComponent.js +2 -0
- package/dist/VanJsfField.d.ts +5 -1
- package/dist/VanJsfField.js +152 -0
- package/dist/VanJsfForm.d.ts +0 -1
- package/dist/VanJsfForm.js +117 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -0
- package/dist/index.js.map +7 -0
- package/dist/main.d.ts +0 -1
- package/dist/main.js +78 -0
- package/package.json +29 -21
- package/dist/JsfUtils.d.ts.map +0 -1
- package/dist/VanJSComponent.d.ts.map +0 -1
- package/dist/VanJsfField.d.ts.map +0 -1
- package/dist/VanJsfForm.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/main.d.ts.map +0 -1
- package/dist/vanjs-jsf.js +0 -267
- package/dist/vanjs-jsf.umd.cjs +0 -1
package/dist/JsfUtils.d.ts
CHANGED
package/dist/JsfUtils.js
ADDED
package/dist/VanJSComponent.d.ts
CHANGED
package/dist/VanJsfField.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export declare class VanJsfField extends VanJSComponent {
|
|
|
16
16
|
constructor(field: Record<string, unknown>, initVal: string, handleChange: (field: VanJsfField, value: MultiType) => void);
|
|
17
17
|
get inputType(): string;
|
|
18
18
|
get label(): string;
|
|
19
|
+
get class(): string;
|
|
20
|
+
get containerClass(): string;
|
|
21
|
+
get titleClass(): string;
|
|
22
|
+
get descriptionClass(): string;
|
|
19
23
|
get description(): string;
|
|
20
24
|
get options(): Option[];
|
|
21
25
|
get isVisible(): boolean;
|
|
@@ -23,5 +27,5 @@ export declare class VanJsfField extends VanJSComponent {
|
|
|
23
27
|
get error(): string;
|
|
24
28
|
set error(val: string);
|
|
25
29
|
render(): Element;
|
|
30
|
+
isVanJsfFieldArray(fields: any): fields is VanJsfField[];
|
|
26
31
|
}
|
|
27
|
-
//# sourceMappingURL=VanJsfField.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import van from "vanjs-core";
|
|
2
|
+
import { VanJSComponent } from "./VanJSComponent";
|
|
3
|
+
import pikaday from "pikaday";
|
|
4
|
+
const { div, p, input, label, textarea, legend, link, fieldset, span } = van.tags;
|
|
5
|
+
var FieldType;
|
|
6
|
+
(function (FieldType) {
|
|
7
|
+
FieldType["text"] = "text";
|
|
8
|
+
FieldType["number"] = "number";
|
|
9
|
+
FieldType["textarea"] = "textarea";
|
|
10
|
+
FieldType["radio"] = "radio";
|
|
11
|
+
FieldType["date"] = "date";
|
|
12
|
+
FieldType["fieldset"] = "fieldset";
|
|
13
|
+
})(FieldType || (FieldType = {}));
|
|
14
|
+
export class VanJsfField extends VanJSComponent {
|
|
15
|
+
name;
|
|
16
|
+
field;
|
|
17
|
+
iniVal;
|
|
18
|
+
handleChange;
|
|
19
|
+
isVisibleState;
|
|
20
|
+
errorState;
|
|
21
|
+
constructor(field, initVal, handleChange) {
|
|
22
|
+
super();
|
|
23
|
+
this.field = field;
|
|
24
|
+
this.name = field.name;
|
|
25
|
+
this.iniVal = initVal;
|
|
26
|
+
this.handleChange = handleChange;
|
|
27
|
+
this.isVisibleState = van.state(this.field.isVisible);
|
|
28
|
+
this.errorState = van.state("");
|
|
29
|
+
van.derive(() => console.log(`Field ${this.name} isVisible: ${this.isVisibleState.val}`));
|
|
30
|
+
}
|
|
31
|
+
get inputType() {
|
|
32
|
+
return this.field.inputType;
|
|
33
|
+
}
|
|
34
|
+
get label() {
|
|
35
|
+
return this.field.label;
|
|
36
|
+
}
|
|
37
|
+
get class() {
|
|
38
|
+
return this.field.class;
|
|
39
|
+
}
|
|
40
|
+
get containerClass() {
|
|
41
|
+
return this.field.containerClass;
|
|
42
|
+
}
|
|
43
|
+
get titleClass() {
|
|
44
|
+
return this.field.titleClass;
|
|
45
|
+
}
|
|
46
|
+
get descriptionClass() {
|
|
47
|
+
return this.field.descriptionClass;
|
|
48
|
+
}
|
|
49
|
+
get description() {
|
|
50
|
+
return this.field.description;
|
|
51
|
+
}
|
|
52
|
+
get options() {
|
|
53
|
+
return this.field.options;
|
|
54
|
+
}
|
|
55
|
+
get isVisible() {
|
|
56
|
+
return this.isVisibleState.val;
|
|
57
|
+
}
|
|
58
|
+
set isVisible(val) {
|
|
59
|
+
this.isVisibleState.val = val;
|
|
60
|
+
}
|
|
61
|
+
get error() {
|
|
62
|
+
return this.errorState.val;
|
|
63
|
+
}
|
|
64
|
+
set error(val) {
|
|
65
|
+
this.errorState.val = val;
|
|
66
|
+
}
|
|
67
|
+
render() {
|
|
68
|
+
let el;
|
|
69
|
+
const props = {
|
|
70
|
+
style: () => (this.isVisible ? "display: block" : "display: none"),
|
|
71
|
+
class: this.containerClass ? this.containerClass : ''
|
|
72
|
+
};
|
|
73
|
+
switch (this.inputType) {
|
|
74
|
+
case FieldType.text:
|
|
75
|
+
el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
|
|
76
|
+
div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), input({
|
|
77
|
+
id: this.name,
|
|
78
|
+
type: "text",
|
|
79
|
+
class: this.class ? this.class : '',
|
|
80
|
+
value: this.iniVal,
|
|
81
|
+
oninput: (e) => this.handleChange(this, e.target.value),
|
|
82
|
+
}), p(() => this.error));
|
|
83
|
+
break;
|
|
84
|
+
case FieldType.textarea:
|
|
85
|
+
el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
|
|
86
|
+
div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), textarea({
|
|
87
|
+
id: this.name,
|
|
88
|
+
name: this.name,
|
|
89
|
+
class: this.class ? this.class : null,
|
|
90
|
+
rows: this.field.rows,
|
|
91
|
+
cols: this.field.columns,
|
|
92
|
+
oninput: (e) => this.handleChange(this, e.target.value),
|
|
93
|
+
}));
|
|
94
|
+
break;
|
|
95
|
+
case FieldType.date:
|
|
96
|
+
const calendarInput = input({
|
|
97
|
+
id: this.name,
|
|
98
|
+
type: "text",
|
|
99
|
+
class: this.class ? this.class : null,
|
|
100
|
+
value: this.iniVal,
|
|
101
|
+
onchange: (e) => this.handleChange(this, e.target.value),
|
|
102
|
+
});
|
|
103
|
+
el =
|
|
104
|
+
div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
|
|
105
|
+
div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), calendarInput, link({ rel: "stylesheet", type: "text/css", href: "https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css" }));
|
|
106
|
+
new pikaday({
|
|
107
|
+
field: calendarInput,
|
|
108
|
+
format: 'D/M/YYYY',
|
|
109
|
+
toString(date, format) {
|
|
110
|
+
// you should do formatting based on the passed format,
|
|
111
|
+
// but we will just return 'D/M/YYYY' for simplicity
|
|
112
|
+
const day = date.getDate();
|
|
113
|
+
const month = date.getMonth() + 1;
|
|
114
|
+
const year = date.getFullYear();
|
|
115
|
+
return `${day}/${month}/${year}`;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
case FieldType.number:
|
|
120
|
+
el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
|
|
121
|
+
div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), input({
|
|
122
|
+
id: this.name,
|
|
123
|
+
type: "number",
|
|
124
|
+
class: this.class ? this.class : null,
|
|
125
|
+
value: this.iniVal,
|
|
126
|
+
oninput: (e) => this.handleChange(this, e.target.value),
|
|
127
|
+
}));
|
|
128
|
+
break;
|
|
129
|
+
case FieldType.fieldset:
|
|
130
|
+
console.log(this.field);
|
|
131
|
+
el = div(props, fieldset(legend({ class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
|
|
132
|
+
span({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), this.isVanJsfFieldArray(this.field.fields) ? this.field.fields.map((field) => field.render()) : null));
|
|
133
|
+
break;
|
|
134
|
+
case FieldType.radio:
|
|
135
|
+
el = div(legend({ class: this.titleClass ? this.titleClass : '' }, this.label), this.description && div(this.description), div(this.options?.map((opt) => label(input({
|
|
136
|
+
type: "radio",
|
|
137
|
+
name: this.name,
|
|
138
|
+
class: this.class ? this.class : null,
|
|
139
|
+
value: opt.value,
|
|
140
|
+
checked: this.iniVal === opt.value,
|
|
141
|
+
onchange: (e) => this.handleChange(this, e.target.value),
|
|
142
|
+
}), opt.label, opt.description))));
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
el = div({ style: "border: 1px dashed gray; padding: 8px;" }, `Field "${this.name}" unsupported: The type "${this.inputType}" has no UI component built yet.`);
|
|
146
|
+
}
|
|
147
|
+
return el;
|
|
148
|
+
}
|
|
149
|
+
isVanJsfFieldArray(fields) {
|
|
150
|
+
return Array.isArray(fields) && fields.every(field => field instanceof VanJsfField);
|
|
151
|
+
}
|
|
152
|
+
}
|
package/dist/VanJsfForm.d.ts
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import van from "vanjs-core";
|
|
2
|
+
import { createHeadlessForm, } from "@remoteoss/json-schema-form";
|
|
3
|
+
import { VanJsfField } from "./VanJsfField";
|
|
4
|
+
const { form } = van.tags;
|
|
5
|
+
class VanJsfForm {
|
|
6
|
+
schema;
|
|
7
|
+
config;
|
|
8
|
+
headlessForm;
|
|
9
|
+
formFields;
|
|
10
|
+
formValues;
|
|
11
|
+
constructor(jsonSchema, config) {
|
|
12
|
+
// Bind methods to instance. Needed to pass functions as props to child components
|
|
13
|
+
//this.handleSubmit = this.handleSubmit.bind(this);
|
|
14
|
+
this.handleFieldChange = this.handleFieldChange.bind(this);
|
|
15
|
+
// Receive parameters
|
|
16
|
+
this.schema = jsonSchema;
|
|
17
|
+
this.config = config;
|
|
18
|
+
// Working with parameters
|
|
19
|
+
this.headlessForm = createHeadlessForm(jsonSchema, config);
|
|
20
|
+
// Read documentation about `getFieldsAndValuedFromJsf` method below
|
|
21
|
+
const { vanJsfFields, formValues } = this.getFieldsAndValuesFromJsf(this.headlessForm, this.config.initialValues);
|
|
22
|
+
this.formFields = vanJsfFields;
|
|
23
|
+
this.formValues = formValues;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generates fields and their initial values from a headless JSON Schema Form (JSF).
|
|
27
|
+
* This method processes the fields provided by the headless form, maps them to `VanJsfField` instances,
|
|
28
|
+
* and initializes the corresponding form values.
|
|
29
|
+
*
|
|
30
|
+
* @param headlessForm - The output of the `createHeadlessForm` function, containing metadata and configuration for the form fields.
|
|
31
|
+
* @param initialValues - A record object where the keys represent field names, and the values are the initial values for the fields.
|
|
32
|
+
*
|
|
33
|
+
* @returns An object containing:
|
|
34
|
+
* - `vanJsfFields`: An array of `VanJsfField` instances representing the fields in the form.
|
|
35
|
+
* - `formValues`: A record object mapping field names to their respective initial values.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* - **Field Sets**: The method currently does not support field sets recursively. This needs to be implemented as part of future enhancements.
|
|
39
|
+
* - **Default Values**:
|
|
40
|
+
* - The default values are determined based on the following precedence:
|
|
41
|
+
* 1. Value in `initialValues`.
|
|
42
|
+
* 2. The `field.default` property.
|
|
43
|
+
* 3. An empty string (`""`) if neither is present.
|
|
44
|
+
* - Note: The `field.default` property is not clearly documented in the JSF API. The documentation mentions `defaultValue` instead, but this is not observed in practice.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const { vanJsfFields, formValues } = getFieldsFromJsf(headlessForm, initialValues);
|
|
48
|
+
* console.log(vanJsfFields); // Array of VanJsfField instances
|
|
49
|
+
* console.log(formValues); // Record of field names and their initial values
|
|
50
|
+
*/
|
|
51
|
+
getFieldsAndValuesFromJsf(headlessForm, initialValues) {
|
|
52
|
+
const fields = headlessForm.fields;
|
|
53
|
+
console.log(fields);
|
|
54
|
+
const formValues = {};
|
|
55
|
+
const vanJsfFields = this.processFields(fields, initialValues, formValues);
|
|
56
|
+
return { vanJsfFields, formValues };
|
|
57
|
+
}
|
|
58
|
+
handleFieldChange(field, value) {
|
|
59
|
+
console.log(`Field ${field.name} changed to ${value}`);
|
|
60
|
+
this.formValues[field.name] = value;
|
|
61
|
+
const { formErrors } = this.headlessForm.handleValidation(this.formValues);
|
|
62
|
+
console.log("formErrors", formErrors);
|
|
63
|
+
this.formFields.forEach((f) => {
|
|
64
|
+
f.isVisible = f.field.isVisible;
|
|
65
|
+
f.error = formErrors?.[f.name] ?? "";
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
processFields(fields, initialValues, formValues, parentPath = "") {
|
|
69
|
+
return fields.map((field) => {
|
|
70
|
+
// Construct the full path for the field
|
|
71
|
+
const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
|
|
72
|
+
// Determine the initial value for the field
|
|
73
|
+
const initVal = initialValues[fieldPath] || field.default || "";
|
|
74
|
+
// Store the initial value in the form values map
|
|
75
|
+
formValues[fieldPath] = initVal;
|
|
76
|
+
console.log(formValues);
|
|
77
|
+
console.log(initialValues);
|
|
78
|
+
// Check if the field has nested fields and process them recursively
|
|
79
|
+
if (field.fields && field.fields.length > 0) {
|
|
80
|
+
field.fields = this.processFields(field.fields, initialValues, formValues, fieldPath);
|
|
81
|
+
}
|
|
82
|
+
// Create and return a new VanJsfField instance for this field
|
|
83
|
+
return new VanJsfField(field, initVal, this.handleFieldChange);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function jsform(attributes, ...children) {
|
|
88
|
+
if (!attributes.schema) {
|
|
89
|
+
throw new Error("JSON Schema is required");
|
|
90
|
+
}
|
|
91
|
+
let config = attributes.config;
|
|
92
|
+
if (!config) {
|
|
93
|
+
config = { initialValues: {}, formValues: {} };
|
|
94
|
+
}
|
|
95
|
+
else if (!config.initialValues) {
|
|
96
|
+
config.initialValues = {};
|
|
97
|
+
}
|
|
98
|
+
else if (!config.formValues) {
|
|
99
|
+
config.formValues = {};
|
|
100
|
+
}
|
|
101
|
+
const vanJsfForm = new VanJsfForm(attributes.schema, config);
|
|
102
|
+
console.log(vanJsfForm);
|
|
103
|
+
const fields = vanJsfForm.formFields.map((field) => field.render());
|
|
104
|
+
const childrenWithFields = [...fields, ...children]; // Concatenate fields with other children
|
|
105
|
+
const originalOnSubmit = attributes.onsubmit;
|
|
106
|
+
const handleSubmit = (e) => {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
config.formValues = vanJsfForm.formValues;
|
|
109
|
+
originalOnSubmit && originalOnSubmit(e);
|
|
110
|
+
};
|
|
111
|
+
const handleChange = (e) => {
|
|
112
|
+
config.formValues = vanJsfForm.formValues;
|
|
113
|
+
};
|
|
114
|
+
attributes.onsubmit = handleSubmit;
|
|
115
|
+
attributes.onchange = handleChange;
|
|
116
|
+
return form(attributes, ...childrenWithFields);
|
|
117
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { jsform } from "./VanJsfForm";
|