voc-lib-js 1.0.0

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 ADDED
@@ -0,0 +1,19 @@
1
+ # Vocphone JavaScript Library
2
+ This is a JavaScript library for [VocPhone](https://vocphone.com).
3
+
4
+ ## Setup
5
+ ```bash
6
+ npm install
7
+ ```
8
+
9
+ ## Usage
10
+ ### Installation
11
+ ```bash
12
+ npm install voc-lib-js
13
+ ```
14
+
15
+ ### Import
16
+ ```javascript
17
+ import { Vocphone } from "voc-lib-js";
18
+ const vocphone = new Vocphone();
19
+ ```
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "voc-lib-js",
3
+ "version": "1.0.0",
4
+ "description": "A JavaScript library for VocPhone",
5
+ "main": "./dist/main.cjs",
6
+ "module": "./dist/main.mjs",
7
+ "types": "./dist/main.d.ts",
8
+ "scripts": {
9
+ "build": "tsup src/main.ts --format cjs,esm --dts"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://git.vocphone.com/vocphone-voice/voc-lib-js.git"
14
+ },
15
+ "keywords": ["vocphone", "voice", "library", "javascript", "typescript"],
16
+ "author": "VocPhone Team",
17
+ "license": "ISC",
18
+ "devDependencies": {
19
+ "tsup": "^8.5.0",
20
+ "typescript": "^5.9.2"
21
+ }
22
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "id": "SDFSD345634563456",
3
+ "code": "USER_REG_01",
4
+ "form_name": "User Registration",
5
+ "fields": [
6
+ {
7
+ "label": "Name", // optional show the label of the group if exists
8
+ "type": "row",
9
+ "items": [
10
+ {
11
+ "label": "First Name",
12
+ "key": "first_name",
13
+ "type": "text",
14
+ "required": true,
15
+ "validation": {
16
+ "max_length": 30,
17
+ "min_length": 8,
18
+ "regex": "^(?=.*[0-9])(?=.*[a-zA-Z]).*$"
19
+ },
20
+ "hint_text": "Your first name must be at least 8 characters long and contain both letters and numbers."
21
+ },
22
+ {
23
+ "label": "Last Name",
24
+ "key": "last_name",
25
+ "type": "text",
26
+ "required": true,
27
+ "validation": {
28
+ "min_length": 8
29
+ }
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ "label": "Username",
35
+ "key": "username",
36
+ "type": "text",
37
+ "required": true,
38
+ "validation": {
39
+ "min_length": 8
40
+ }
41
+ },
42
+ {
43
+ "label": "Password",
44
+ "key": "password",
45
+ "type": "password",
46
+ "required": true,
47
+ "validation": {
48
+ "min_length": 8,
49
+ "regex": "^(?=.*[0-9])(?=.*[a-zA-Z]).*$"
50
+ }
51
+ },
52
+ {
53
+ "label": "Text Area",
54
+ "key": "text_area",
55
+ "type": "textarea",
56
+ "required": true,
57
+ "validation": {
58
+ "min_length": 8
59
+ }
60
+ },
61
+ {
62
+ "label": "Email",
63
+ "key": "email",
64
+ "type": "email",
65
+ "required": false
66
+ },
67
+ {
68
+ "validation": {
69
+ "min_length": 8
70
+ }
71
+ },
72
+ {
73
+ "label": "Gender",
74
+ "type": "radio",
75
+ "key": "gender",
76
+ "required": false,
77
+ "direction": "horizontal",
78
+ "options": [
79
+ {
80
+ "label": "Male",
81
+ "value": "male"
82
+ },
83
+ {
84
+ "label": "Female",
85
+ "value": "female"
86
+ },
87
+ {
88
+ "label": "Other",
89
+ "value": "other"
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ "label": "Country",
95
+ "type": "select",
96
+ "key": "country",
97
+ "default": "usa",
98
+ "required": false,
99
+ "options": [
100
+ {
101
+ "label": "USA",
102
+ "value": "usa"
103
+ },
104
+ {
105
+ "label": "Canada",
106
+ "value": "canada"
107
+ },
108
+ {
109
+ "label": "UK",
110
+ "value": "uk"
111
+ }
112
+ ]
113
+ }
114
+ ]
115
+ }
@@ -0,0 +1,249 @@
1
+ import { submitForm } from "./form-submit";
2
+
3
+ // ---- Types ----
4
+ type Validation = {
5
+ max_length?: number;
6
+ min_length?: number;
7
+ regex?: string;
8
+ };
9
+
10
+ type Option = {
11
+ label: string;
12
+ value: string;
13
+ };
14
+
15
+ type BaseField = {
16
+ label?: string;
17
+ key?: string;
18
+ type?: string;
19
+ required?: boolean;
20
+ validation?: Validation;
21
+ hint_text?: string;
22
+ direction?: "horizontal" | "vertical";
23
+ options?: Option[];
24
+ default?: string;
25
+ };
26
+
27
+ type RowField = {
28
+ label?: string; // optional group label
29
+ type: "row";
30
+ items: BaseField[];
31
+ };
32
+
33
+ type Field = BaseField | RowField;
34
+
35
+ export type FormData = {
36
+ id: string;
37
+ code: string;
38
+ form_name: string;
39
+ fields: Field[];
40
+ };
41
+
42
+ // ---- Main return type ----
43
+ export interface RenderedForm {
44
+ element: HTMLFormElement;
45
+ validate: () => Array<{ element: HTMLElement, message: string }>;
46
+ submitForm: () => void;
47
+ getValues: () => { [key: string]: FormDataEntryValue | FormDataEntryValue[] };
48
+ }
49
+
50
+ // ---- Implementation ----
51
+ export function renderForm(element: string | HTMLElement, data: FormData): RenderedForm | null {
52
+ let containers: HTMLElement[] = [];
53
+
54
+ if (typeof element === "string") {
55
+ containers = Array.from(document.querySelectorAll<HTMLElement>(element));
56
+ } else {
57
+ containers = [element];
58
+ }
59
+
60
+ if (containers.length === 0) return null;
61
+
62
+ function buildForm(): HTMLFormElement {
63
+ const form = document.createElement("form");
64
+ form.id = data.id;
65
+
66
+ const title = document.createElement("h2");
67
+ title.textContent = data.form_name;
68
+ form.appendChild(title);
69
+
70
+ function applyValidation(
71
+ el: HTMLInputElement | HTMLTextAreaElement,
72
+ validation?: Validation
73
+ ) {
74
+ if (!validation) return;
75
+
76
+ if (validation.min_length !== undefined) {
77
+ el.minLength = validation.min_length;
78
+ }
79
+
80
+ if (validation.max_length !== undefined) {
81
+ el.maxLength = validation.max_length;
82
+ }
83
+
84
+ if (validation.regex) {
85
+ if (el instanceof HTMLInputElement) {
86
+ // pattern only works on inputs
87
+ el.pattern = validation.regex;
88
+ } else {
89
+ // fallback for textarea → use dataset
90
+ el.dataset.pattern = validation.regex;
91
+ }
92
+ }
93
+ }
94
+
95
+ function renderField(field: BaseField): HTMLElement | null {
96
+ if (!field.key && !field.label) return null;
97
+
98
+ const wrapper = document.createElement("div");
99
+ wrapper.className = "form-group";
100
+
101
+ if (field.label) {
102
+ const label = document.createElement("label");
103
+ label.htmlFor = field.key || "";
104
+ label.textContent = field.label + (field.required ? " *" : "");
105
+ wrapper.appendChild(label);
106
+ }
107
+
108
+ let input: HTMLElement | null = null;
109
+
110
+ switch (field.type) {
111
+ case "textarea": {
112
+ const textarea = document.createElement("textarea");
113
+ if (field.key) textarea.name = field.key;
114
+ if (field.required) textarea.required = true;
115
+ applyValidation(textarea, field.validation);
116
+ input = textarea;
117
+ break;
118
+ }
119
+ case "select": {
120
+ const select = document.createElement("select");
121
+ if (field.key) select.name = field.key;
122
+ if (field.required) select.required = true;
123
+ if (field.options) {
124
+ field.options.forEach((opt) => {
125
+ const option = document.createElement("option");
126
+ option.value = opt.value;
127
+ option.textContent = opt.label;
128
+ if (field.default === opt.value) option.selected = true;
129
+ select.appendChild(option);
130
+ });
131
+ }
132
+ input = select;
133
+ break;
134
+ }
135
+ case "radio": {
136
+ const radioGroup = document.createElement("div");
137
+ radioGroup.className =
138
+ "radio-group " + (field.direction || "vertical");
139
+ if (field.options) {
140
+ field.options.forEach((opt) => {
141
+ const radioWrapper = document.createElement("label");
142
+ radioWrapper.style.display =
143
+ field.direction === "horizontal"
144
+ ? "inline-block"
145
+ : "block";
146
+
147
+ const radio = document.createElement("input");
148
+ radio.type = "radio";
149
+ radio.name = field.key || "";
150
+ radio.value = opt.value;
151
+
152
+ radioWrapper.appendChild(radio);
153
+ radioWrapper.appendChild(
154
+ document.createTextNode(opt.label)
155
+ );
156
+ radioGroup.appendChild(radioWrapper);
157
+ });
158
+ }
159
+ input = radioGroup;
160
+ break;
161
+ }
162
+ default: {
163
+ const inp = document.createElement("input");
164
+ inp.type = field.type || "text";
165
+ if (field.key) inp.name = field.key;
166
+ if (field.required) inp.required = true;
167
+ applyValidation(inp, field.validation);
168
+ input = inp;
169
+ break;
170
+ }
171
+ }
172
+
173
+ if (input) wrapper.appendChild(input);
174
+
175
+ if (field.hint_text) {
176
+ const hint = document.createElement("small");
177
+ hint.className = "hint";
178
+ hint.textContent = field.hint_text;
179
+ wrapper.appendChild(hint);
180
+ }
181
+
182
+ return wrapper;
183
+ }
184
+
185
+ data.fields.forEach((field) => {
186
+ if (field.type === "row") {
187
+ const rowWrapper = document.createElement("div");
188
+ rowWrapper.className = "form-row";
189
+
190
+ if (field.label) {
191
+ const rowLabel = document.createElement("h3");
192
+ rowLabel.textContent = field.label;
193
+ rowWrapper.appendChild(rowLabel);
194
+ }
195
+
196
+ //@ts-ignore
197
+ field.items.forEach((item) => {
198
+ const rendered = renderField(item);
199
+ if (rendered) rowWrapper.appendChild(rendered);
200
+ });
201
+
202
+ form.appendChild(rowWrapper);
203
+ } else {
204
+ const rendered = renderField(field as BaseField);
205
+ if (rendered) form.appendChild(rendered);
206
+ }
207
+ });
208
+
209
+ // Add a default submit button
210
+ const submitBtn = document.createElement("button");
211
+ submitBtn.type = "submit";
212
+ submitBtn.textContent = "Submit";
213
+ form.appendChild(submitBtn);
214
+
215
+ return form;
216
+ }
217
+
218
+ // Attach form to the first container
219
+ const formElement = buildForm();
220
+ containers[0].appendChild(formElement);
221
+
222
+ // ---- API to return ----
223
+ return {
224
+ element: formElement,
225
+ // this method validates the form and returns an array of errors
226
+ validate: () => {
227
+ const errors: Array<{ element: HTMLElement, message: string }> = [];
228
+ const invalidElements = formElement.querySelectorAll(":invalid");
229
+ invalidElements.forEach((element) => {
230
+ const message = (element as HTMLInputElement | HTMLTextAreaElement).validationMessage;
231
+ errors.push({ element: element as HTMLElement, message });
232
+ });
233
+ return errors;
234
+ },
235
+ // this method submits the form to the portal vocphone server
236
+ submitForm: () => {
237
+ submitForm(formElement);
238
+ },
239
+ // This method gets the data from the form.
240
+ getValues: () => {
241
+ const formData = new FormData(formElement);
242
+ const values: { [key: string]: FormDataEntryValue | FormDataEntryValue[] } = {};
243
+ formData.forEach((value, key) => {
244
+ values[key] = value;
245
+ });
246
+ return values;
247
+ }
248
+ };
249
+ }
@@ -0,0 +1,3 @@
1
+ export function submitForm(form: HTMLFormElement) {
2
+ console.log("Form submitted:", form);
3
+ }
@@ -0,0 +1,5 @@
1
+ import { renderForm } from "./form-render";
2
+
3
+ export default {
4
+ renderForm
5
+ };
package/src/main.ts ADDED
@@ -0,0 +1,3 @@
1
+ import formsLib from "./forms-lib/index";
2
+
3
+ export const FormsLib = formsLib;
@@ -0,0 +1,141 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Document</title>
8
+ </head>
9
+
10
+ <body>
11
+ <div id="the-form"></div>
12
+
13
+ <script type="module">
14
+ import { FormsLib } from '../../../dist/main.mjs';
15
+
16
+ const form = FormsLib.renderForm('#the-form', {
17
+ "id": "SDFSD345634563456",
18
+ "code": "USER_REG_01",
19
+ "form_name": "User Registration",
20
+ "fields": [
21
+ {
22
+ "label": "Name", // optional show the label of the group if exists
23
+ "type": "row",
24
+ "items": [
25
+ {
26
+ "label": "First Name",
27
+ "key": "first_name",
28
+ "type": "text",
29
+ "required": true,
30
+ "validation": {
31
+ "max_length": 30,
32
+ "min_length": 8,
33
+ },
34
+ "hint_text": "Your first name must be at least 8 characters long and contain both letters and numbers."
35
+ },
36
+ {
37
+ "label": "Last Name",
38
+ "key": "last_name",
39
+ "type": "text",
40
+ "required": true,
41
+ "validation": {
42
+ "min_length": 8
43
+ }
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "label": "Username",
49
+ "key": "username",
50
+ "type": "text",
51
+ "required": true,
52
+ "validation": {
53
+ "min_length": 8
54
+ }
55
+ },
56
+ {
57
+ "label": "Password",
58
+ "key": "password",
59
+ "type": "password",
60
+ "required": true,
61
+ "validation": {
62
+ "min_length": 8
63
+ }
64
+ },
65
+ {
66
+ "label": "Text Area",
67
+ "key": "text_area",
68
+ "type": "textarea",
69
+ "required": true,
70
+ "validation": {
71
+ "min_length": 8
72
+ }
73
+ },
74
+ {
75
+ "label": "Email",
76
+ "key": "email",
77
+ "type": "email",
78
+ "required": false
79
+ },
80
+ {
81
+ "validation": {
82
+ "min_length": 8
83
+ }
84
+ },
85
+ {
86
+ "label": "Gender",
87
+ "type": "radio",
88
+ "key": "gender",
89
+ "required": false,
90
+ "direction": "horizontal",
91
+ "options": [
92
+ {
93
+ "label": "Male",
94
+ "value": "male"
95
+ },
96
+ {
97
+ "label": "Female",
98
+ "value": "female"
99
+ },
100
+ {
101
+ "label": "Other",
102
+ "value": "other"
103
+ }
104
+ ]
105
+ },
106
+ {
107
+ "label": "Country",
108
+ "type": "select",
109
+ "key": "country",
110
+ "default": "usa",
111
+ "required": false,
112
+ "options": [
113
+ {
114
+ "label": "USA",
115
+ "value": "usa"
116
+ },
117
+ {
118
+ "label": "Canada",
119
+ "value": "canada"
120
+ },
121
+ {
122
+ "label": "UK",
123
+ "value": "uk"
124
+ }
125
+ ]
126
+ }
127
+ ]
128
+ });
129
+
130
+ const validate = form.validate();
131
+ console.log(validate);
132
+
133
+
134
+ form.submitForm();
135
+
136
+ const values = form.getValues();
137
+ console.log('Form values:', values);
138
+ </script>
139
+ </body>
140
+
141
+ </html>
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "declaration": true,
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src"]
12
+ }