vanjs-jsf 0.0.10 → 0.0.12

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/README.md CHANGED
@@ -15,6 +15,16 @@ This library aims to provide a robust and flexible solution for dynamically gene
15
15
  - [ ] **Validation Support**: Easily integrate JSON Schema-based validation for seamless user input handling.
16
16
  - [ ] **Extensible Architecture**: Add custom widgets, field types, and behaviours as needed.
17
17
 
18
+ ### Available components
19
+ The currently supported form element types are:
20
+ - text = "text",
21
+ - number = "number",
22
+ - textarea = "textarea",
23
+ - select = "select",
24
+ - radio = "radio",
25
+ - date = "date",
26
+ - fieldset = "fieldset"
27
+
18
28
  ### Use Cases
19
29
 
20
30
  - Quickly generate forms for dashboards, admin panels, and dynamic web applications.
@@ -3,7 +3,8 @@ import { VanJSComponent } from "./VanJSComponent";
3
3
  export interface Option {
4
4
  label: string;
5
5
  value: string;
6
- description: string;
6
+ description?: string;
7
+ img?: string;
7
8
  }
8
9
  export type MultiType = string | number | boolean;
9
10
  export declare class VanJsfField extends VanJSComponent {
@@ -17,7 +18,10 @@ export declare class VanJsfField extends VanJSComponent {
17
18
  get inputType(): string;
18
19
  get label(): string;
19
20
  get class(): string;
21
+ get errorClass(): string;
22
+ get codemirrorExtension(): Array<any>;
20
23
  get containerClass(): string;
24
+ get containerId(): string;
21
25
  get titleClass(): string;
22
26
  get descriptionClass(): string;
23
27
  get description(): string;
@@ -1,16 +1,39 @@
1
1
  import van from "vanjs-core";
2
2
  import { VanJSComponent } from "./VanJSComponent";
3
3
  import pikaday from "pikaday";
4
- const { div, p, input, label, textarea, legend, link, fieldset, span } = van.tags;
4
+ import { basicSetup, EditorView } from "codemirror";
5
+ import { javascript, esLint } from "@codemirror/lang-javascript";
6
+ import { json, jsonParseLinter } from "@codemirror/lang-json";
7
+ import { lintGutter, linter, forEachDiagnostic } from "@codemirror/lint";
8
+ import * as eslint from "eslint-linter-browserify";
9
+ import globals from "globals";
10
+ const { div, p, input, label, textarea, legend, link, fieldset, span, select, option } = van.tags;
5
11
  var FieldType;
6
12
  (function (FieldType) {
7
13
  FieldType["text"] = "text";
14
+ FieldType["code"] = "code";
8
15
  FieldType["number"] = "number";
9
16
  FieldType["textarea"] = "textarea";
17
+ FieldType["select"] = "select";
10
18
  FieldType["radio"] = "radio";
11
19
  FieldType["date"] = "date";
12
20
  FieldType["fieldset"] = "fieldset";
13
21
  })(FieldType || (FieldType = {}));
22
+ const eslintConfig = {
23
+ // eslint configuration
24
+ languageOptions: {
25
+ globals: {
26
+ ...globals.node,
27
+ },
28
+ parserOptions: {
29
+ ecmaVersion: 2022,
30
+ sourceType: "module",
31
+ },
32
+ },
33
+ rules: {
34
+ semi: ["error", "never"],
35
+ },
36
+ };
14
37
  export class VanJsfField extends VanJSComponent {
15
38
  name;
16
39
  field;
@@ -37,9 +60,58 @@ export class VanJsfField extends VanJSComponent {
37
60
  get class() {
38
61
  return this.field.class;
39
62
  }
63
+ get errorClass() {
64
+ return this.field.errorClass;
65
+ }
66
+ get codemirrorExtension() {
67
+ const theme = EditorView.theme({
68
+ '.cm-content, .cm-gutter': {
69
+ "min-height": "150px",
70
+ },
71
+ '.cm-content': {
72
+ "min-height": "150px",
73
+ },
74
+ '.cm-gutters': {
75
+ margin: '1px',
76
+ },
77
+ '.cm-scroller': {
78
+ overflow: 'auto',
79
+ },
80
+ '.cm-wrap': {
81
+ border: '1px solid silver',
82
+ },
83
+ });
84
+ const extensions = [theme, EditorView.updateListener.of((e) => {
85
+ this.field.error = null;
86
+ forEachDiagnostic(e.state, (diag) => {
87
+ if (diag.severity === "error") {
88
+ this.field.error = diag.message;
89
+ }
90
+ });
91
+ this.handleChange(this, e.state.doc.toString());
92
+ }), basicSetup, lintGutter()];
93
+ switch (this.field.codemirrorType) {
94
+ case "json":
95
+ extensions.push(json(), linter(jsonParseLinter()));
96
+ break;
97
+ case "javascript":
98
+ extensions.push(javascript(), linter(esLint(new eslint.Linter(), eslintConfig)));
99
+ break;
100
+ case "typescript":
101
+ extensions.push(javascript({ typescript: true }), linter(esLint(new eslint.Linter(), eslintConfig)));
102
+ break;
103
+ default:
104
+ extensions.push(javascript(), linter(esLint(new eslint.Linter(), eslintConfig)));
105
+ break;
106
+ }
107
+ return extensions;
108
+ }
40
109
  get containerClass() {
41
110
  return this.field.containerClass;
42
111
  }
112
+ get containerId() {
113
+ return this.field.containerId;
114
+ }
43
115
  get titleClass() {
44
116
  return this.field.titleClass;
45
117
  }
@@ -79,7 +151,7 @@ export class VanJsfField extends VanJSComponent {
79
151
  class: this.class ? this.class : '',
80
152
  value: this.iniVal,
81
153
  oninput: (e) => this.handleChange(this, e.target.value),
82
- }), p(() => this.error));
154
+ }), p({ class: this.errorClass }, () => this.error));
83
155
  break;
84
156
  case FieldType.textarea:
85
157
  el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
@@ -90,7 +162,25 @@ export class VanJsfField extends VanJSComponent {
90
162
  rows: this.field.rows,
91
163
  cols: this.field.columns,
92
164
  oninput: (e) => this.handleChange(this, e.target.value),
93
- }));
165
+ }), p({ class: this.errorClass }, () => this.error));
166
+ break;
167
+ case FieldType.code:
168
+ el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
169
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description));
170
+ new EditorView({
171
+ doc: new String(this.iniVal).toString(),
172
+ parent: el,
173
+ extensions: this.codemirrorExtension
174
+ });
175
+ break;
176
+ case FieldType.select:
177
+ el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
178
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), select({
179
+ id: this.name,
180
+ name: this.name,
181
+ class: this.class ? this.class : null,
182
+ oninput: (e) => this.handleChange(this, e.target.value),
183
+ }, this.options?.map((opt) => option({ class: this.class ? this.class : null, value: opt.value }, opt.label, opt.description))), p({ class: this.errorClass }, () => this.error));
94
184
  break;
95
185
  case FieldType.date:
96
186
  const calendarInput = input({
@@ -102,10 +192,11 @@ export class VanJsfField extends VanJSComponent {
102
192
  });
103
193
  el =
104
194
  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" }));
195
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), calendarInput, p({ class: this.errorClass }, () => this.error), link({ rel: "stylesheet", type: "text/css", href: "https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css" }));
106
196
  new pikaday({
107
197
  field: calendarInput,
108
198
  format: 'YYYY/MM/DD',
199
+ container: el,
109
200
  firstDay: 1,
110
201
  toString(date) {
111
202
  // you should do formatting based on the passed format,
@@ -133,7 +224,7 @@ export class VanJsfField extends VanJSComponent {
133
224
  class: this.class ? this.class : null,
134
225
  value: this.iniVal,
135
226
  oninput: (e) => this.handleChange(this, e.target.value),
136
- }));
227
+ }), p({ class: this.errorClass }, () => this.error));
137
228
  break;
138
229
  case FieldType.fieldset:
139
230
  console.log(this.field);
@@ -148,7 +239,7 @@ export class VanJsfField extends VanJSComponent {
148
239
  value: opt.value,
149
240
  checked: this.iniVal === opt.value,
150
241
  onchange: (e) => this.handleChange(this, e.target.value),
151
- }), opt.label, opt.description))));
242
+ }), opt.label, opt.description), p({ class: this.errorClass }, () => this.error))));
152
243
  break;
153
244
  default:
154
245
  el = div({ style: "border: 1px dashed gray; padding: 8px;" }, `Field "${this.name}" unsupported: The type "${this.inputType}" has no UI component built yet.`);
@@ -5,18 +5,22 @@ const { form } = van.tags;
5
5
  class VanJsfForm {
6
6
  schema;
7
7
  config;
8
+ isValid;
8
9
  headlessForm;
9
10
  formFields;
10
11
  formValues;
11
- constructor(jsonSchema, config) {
12
+ constructor(jsonSchema, config, isValid) {
12
13
  // Bind methods to instance. Needed to pass functions as props to child components
13
14
  //this.handleSubmit = this.handleSubmit.bind(this);
14
15
  this.handleFieldChange = this.handleFieldChange.bind(this);
15
16
  // Receive parameters
16
17
  this.schema = jsonSchema;
17
18
  this.config = config;
19
+ this.isValid = isValid || undefined;
18
20
  // Working with parameters
21
+ const initialValues = { ...config?.initialValues };
19
22
  this.headlessForm = createHeadlessForm(jsonSchema, config);
23
+ this.config.initialValues = initialValues;
20
24
  // Read documentation about `getFieldsAndValuedFromJsf` method below
21
25
  const { vanJsfFields, formValues } = this.getFieldsAndValuesFromJsf(this.headlessForm, this.config.initialValues);
22
26
  this.formFields = vanJsfFields;
@@ -50,20 +54,35 @@ class VanJsfForm {
50
54
  */
51
55
  getFieldsAndValuesFromJsf(headlessForm, initialValues) {
52
56
  const fields = headlessForm.fields;
53
- console.log(fields);
54
57
  const formValues = {};
58
+ const values = { ...initialValues };
59
+ console.log(values);
55
60
  const vanJsfFields = this.processFields(fields, initialValues, formValues);
56
61
  return { vanJsfFields, formValues };
57
62
  }
58
63
  handleFieldChange(field, value) {
59
- console.log(`Field ${field.name} changed to ${value}`);
64
+ console.log(value);
65
+ console.log(field.name);
60
66
  this.formValues[field.name] = value;
61
67
  const { formErrors } = this.headlessForm.handleValidation(this.formValues);
68
+ let extraError = false;
62
69
  console.log("formErrors", formErrors);
63
70
  this.formFields.forEach((f) => {
64
71
  f.isVisible = f.field.isVisible;
65
72
  f.error = formErrors?.[f.name] ?? "";
73
+ console.log(f.field.error);
74
+ if (f.field.error) {
75
+ extraError = true;
76
+ }
66
77
  });
78
+ if (this.isValid) {
79
+ if (formErrors || extraError) {
80
+ this.isValid.val = false;
81
+ }
82
+ else {
83
+ this.isValid.val = true;
84
+ }
85
+ }
67
86
  }
68
87
  processFields(fields, initialValues, formValues, parentPath = "") {
69
88
  return fields.map((field) => {
@@ -73,8 +92,6 @@ class VanJsfForm {
73
92
  const initVal = initialValues[fieldPath] || field.default || "";
74
93
  // Store the initial value in the form values map
75
94
  formValues[fieldPath] = initVal;
76
- console.log(formValues);
77
- console.log(initialValues);
78
95
  // Check if the field has nested fields and process them recursively
79
96
  if (field.fields && field.fields.length > 0) {
80
97
  field.fields = this.processFields(field.fields, initialValues, formValues, fieldPath);
@@ -89,6 +106,7 @@ export function jsform(attributes, ...children) {
89
106
  throw new Error("JSON Schema is required");
90
107
  }
91
108
  let config = attributes.config;
109
+ let isValid = attributes.isValid;
92
110
  if (!config) {
93
111
  config = { initialValues: {}, formValues: {} };
94
112
  }
@@ -98,7 +116,7 @@ export function jsform(attributes, ...children) {
98
116
  else if (!config.formValues) {
99
117
  config.formValues = {};
100
118
  }
101
- const vanJsfForm = new VanJsfForm(attributes.schema, config);
119
+ const vanJsfForm = new VanJsfForm(attributes.schema, config, isValid);
102
120
  console.log(vanJsfForm);
103
121
  const fields = vanJsfForm.formFields.map((field) => field.render());
104
122
  const childrenWithFields = [...fields, ...children]; // Concatenate fields with other children
@@ -108,8 +126,11 @@ export function jsform(attributes, ...children) {
108
126
  config.formValues = vanJsfForm.formValues;
109
127
  originalOnSubmit && originalOnSubmit(e);
110
128
  };
129
+ const originalOnChange = attributes.onchange;
111
130
  const handleChange = (e) => {
131
+ e.preventDefault();
112
132
  config.formValues = vanJsfForm.formValues;
133
+ originalOnChange && originalOnChange(vanJsfForm, e);
113
134
  };
114
135
  attributes.onsubmit = handleSubmit;
115
136
  attributes.onchange = handleChange;