@modernman00/shared-js-lib 1.2.2

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/FormHelper.js ADDED
@@ -0,0 +1,268 @@
1
+ 'use strict'
2
+ export default class FormHelper {
3
+ constructor(data) {
4
+ if (!Array.isArray(data)) throwError('data must be an array of form elements');
5
+ this.data = data;
6
+ this.error = [];
7
+ this.result = 0;
8
+ }
9
+
10
+ id(x) {
11
+ return document.getElementById(x)
12
+ }
13
+
14
+ /**
15
+ * general validation; check empty status, at least a single input, mobile length, white space
16
+ */
17
+
18
+ getData() {
19
+ return this.data;
20
+ }
21
+
22
+
23
+ /**
24
+ * Validate a single form field
25
+ * @param {string} value - the value of the field to validate
26
+ * @param {string} [type='general'] - the type of validation to perform. Currently only 'email' is supported
27
+ * @returns {boolean} - true if the field is valid, false otherwise
28
+ */
29
+ validateField(value, type = 'general') {
30
+ if (type === 'email') {
31
+ const emailRegex = /^[\w\-\.\+]+\@[a-zA-Z0-9\.\-]+\.[a-zA-Z0-9]{2,4}$/;
32
+ return emailRegex.test(value);
33
+ }
34
+ return value.trim().length > 0;
35
+ }
36
+
37
+
38
+ /**
39
+ * Loop through all the elements in the form and validate each one.
40
+ * If element is empty, add error message to the error array and set result to false
41
+ * If element is not empty, check if it validates according to its type (email or general)
42
+ * If it does not validate, add error message to the error array and set result to false
43
+ * @returns {void}
44
+ */
45
+ massValidate() {
46
+ const invalidElements = this.data.flatMap(et => et.filter(post => {
47
+ const postName = post.name.replace('_', ' ');
48
+ let errMsg = this.id(`${post.name}_error`);
49
+
50
+ // rid it off the submit and token
51
+ if (['submit', 'button', 'showPassword_id', 'g-recaptcha-response', 'cancel', 'token', 'checkbox_id'].includes(post.name) ||
52
+ ['button'].includes(post.id) || ['button'].includes(post.type)) return false;
53
+
54
+ // check if there is no value
55
+ if (['spouseName', 'spouseMobile', 'spouseEmail', 'fatherMobile', 'fatherEmail', 'motherMobile', 'maidenName', 'motherEmail'].includes(post.name)) {
56
+ // post.value is not prpvided if it is not provided
57
+ post.value = post.value === "" ? "Not Provided" : post.value
58
+ }
59
+
60
+ if (post.value === '' || post.value === 'select') {
61
+ errMsg.innerHTML = `${post.placeholder ?? "*"} cannot be left empty`;
62
+ errMsg.style.color = 'red';
63
+ this.error.push(`${postName.toUpperCase()} cannot be left empty`);
64
+ this.result = false;
65
+ return true;
66
+ }
67
+
68
+ if (post.name === 'email' && !this.validateField(post.value, 'email')) {
69
+ errMsg.innerHTML = '* Please enter a valid email';
70
+ errMsg.style.color = 'red';
71
+ this.error.push('<li style="color: red;">Please enter a valid email</li>');
72
+ this.result = false;
73
+ return true;
74
+ }
75
+
76
+ return false;
77
+ }));
78
+
79
+ if (invalidElements.length > 0) this.result = false;
80
+ }
81
+
82
+ emailVal() {
83
+ const emailExp = /^[\w\-\.\+]+\@[a-zA-Z0-9\.\-]+\.[a-zA-z0-9]{2,4}$/;
84
+ let msg = `<li style=color:'red';> Please enter a valid email</li>`
85
+ const email = this.id('email_id').value
86
+ if (email.match(emailExp) === null) {
87
+ this.id('email_error').innerHTML = msg
88
+ this.id('email_error').style.color = "red"
89
+ this.error.push(msg)
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Clears all error messages and empties the error array.
95
+ * Sets up event listeners to clear error messages for each input element.
96
+ * Keyup event listener: for non-select inputs
97
+ * Change event listener: for all inputs
98
+ * @returns {void}
99
+ */
100
+ clearError() {
101
+ this.error = []; // Empty the error array
102
+
103
+ // Function to clear error messages for a given input element
104
+ const clearErrorForElement = (elementName) => {
105
+ const errorElement = this.id(`${elementName}_error`);
106
+ if (errorElement) errorElement.innerHTML = '';
107
+ };
108
+
109
+ this.data.flat().forEach(({ id, name, value }) => {
110
+ if (['submit', 'button', 'token', 'checkbox'].includes(id) || ['token', 'submit'].includes(name)) return;
111
+
112
+ const element = this.id(id);
113
+ if (!element) {
114
+ console.error(`Element with ID '${id}' and post name '${name}' not found.`);
115
+ return;
116
+ }
117
+
118
+ // Add event listeners to clear errors
119
+ const clearErrorHandler = () => clearErrorForElement(name);
120
+ if (value !== 'select') element.addEventListener('keyup', clearErrorHandler);
121
+ element.addEventListener('change', clearErrorHandler);
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Clears the values of all input elements in the form, excluding checkboxes and the submit button
127
+ */
128
+ clearHtml() {
129
+ this.data.flat().forEach(post => {
130
+ if (!['submit', 'checkbox'].includes(post.name) && post.type !== 'submit') {
131
+ post.value = "";
132
+ }
133
+ });
134
+ }
135
+ /**
136
+ * Check the length of the input in real time and display an error message if it exceeds the maximum length
137
+ * @param {array of strings} input - the IDs of the input elements to check
138
+ * @param {array of numbers} maxi - the maximum lengths for each input element
139
+ */
140
+
141
+ realTimeCheckLen(input, maxi) {
142
+ input.forEach((id, i) => {
143
+ const theData = this.id(`${id}_id`);
144
+ if (!theData) {
145
+ console.error(`Element with ID '${id}_id' not found`);
146
+ return;
147
+ }
148
+ const max = maxi[i];
149
+ theData.maxLength = parseInt(max) + 1;
150
+ theData.addEventListener('input', () => {
151
+ const error = this.id(`${id}_error`);
152
+ error.innerHTML = (theData.value.length > max) ? `You have reached the maximum limit` : "";
153
+ this.id(`${id}_help`).style.display = theData.value.length > max ? '' : 'none';
154
+ });
155
+ });
156
+ }
157
+
158
+
159
+
160
+ /**
161
+ * Match the value of the second input to the value of the first input in real time
162
+ * @param {string} first - the id of the first input
163
+ * @param {string} second - the id of the second input
164
+ */
165
+ matchInput(first, second) {
166
+ const firstInput = this.id(first + '_id');
167
+ const secondInput = this.id(second + '_id');
168
+ const error = this.id(`${second}_error`);
169
+
170
+ const checkMatch = () => error.innerHTML = (firstInput.value !== secondInput.value) ? 'Your passwords do not match' : "";
171
+
172
+ firstInput.addEventListener('input', checkMatch);
173
+ secondInput.addEventListener('input', checkMatch);
174
+ }
175
+
176
+
177
+ /**
178
+ * Injects the values in the html array to the elements with the IDs in the idArray
179
+ * @param {array of strings} idArray - the IDs of the elements to inject the values to
180
+ * @param {array of strings} html - the values to inject to the elements
181
+ */
182
+ injectData(idArray, html) {
183
+ idArray.forEach((id, i) => this.id(id).innerHTML = html[i]);
184
+ }
185
+
186
+ /**
187
+ *
188
+ * @param {this is an id and its value is for duplication} firstInput
189
+ * @param {* another id that accepts the value of the firstInput} takeFirstInput
190
+ */
191
+ duplicate(giveInput, takeInput) {
192
+ let giver, taker;
193
+ giver = this.id(giveInput)
194
+ taker = this.id(takeInput)
195
+ giver.addEventListener('keyup', () => {
196
+ taker.value = giver.value;
197
+ })
198
+ }
199
+
200
+
201
+
202
+
203
+ /**
204
+ * Sends a get request to the server as the user types in the specified input element
205
+ * and updates the content of the specified output element with the response from the server
206
+ * @param {string} input - the id of the input element to listen to
207
+ * @param {string} url - the url of the get request to send, the value of the input element will be appended to the end of the url
208
+ * @param {string} outputId - the id of the element to update with the response from the server
209
+ */
210
+ realTimeServer(input, url, outputId) {
211
+ const theInput = this.id(input)
212
+ const output = this.id(outputId)
213
+ theInput.addEventListener('keyup', async () => {
214
+ const inputVal = theInput.value
215
+
216
+ if (inputVal === "") {
217
+ output.innerHTML = "";
218
+ return;
219
+ }
220
+
221
+ try {
222
+ const response = await axios.get(`${url}=${inputVal}`)
223
+ output.innerHTML = response.data
224
+ } catch (error) {
225
+ console.error(error)
226
+ }
227
+ })
228
+ }
229
+
230
+ /**
231
+ * Check if a yes/no radio button is checked and display "checked" in a hidden input field.
232
+ * @param {string} yesId - the id of the yes radio button
233
+ * @param {string} noId - the id of the no radio button
234
+ * @param {string} hiddenInput - the id of the hidden input field to display the result
235
+ */
236
+ isChecked(yesId, noId, hiddenInput) {
237
+ const checked = () => {
238
+ if (this.id(yesId).checked) {
239
+ alert('check')
240
+ this.id(hiddenInput).innerHTML = 'checked';
241
+ } else if (this.id(noId).checked) {
242
+ this.id(hiddenInput).innerHTML = 'checked';
243
+ }
244
+ }
245
+
246
+ this.id(yesId).addEventListener('click', checked)
247
+ this.id(noId).addEventListener('click', checked)
248
+
249
+ }
250
+
251
+ previousAddress() {
252
+ const timeAddy = this.id('time_at_address_id')
253
+ const prevAddy = this.id('previous_address_class')
254
+ const showPrev = () => {
255
+ if (timeAddy.value != '3 years+') {
256
+ prevAddy.style.display = 'block'
257
+ this.id('previous_address_help').innerHTML = "Please enter your full address: House No, Street Name, Town/City and Post Code"
258
+ } else {
259
+ prevAddy.style.display = 'none'
260
+ }
261
+
262
+ }
263
+ timeAddy.addEventListener('change', showPrev)
264
+
265
+ }
266
+
267
+
268
+ }
@@ -0,0 +1,22 @@
1
+ import { qSelAll, id } from './UtilityHtml.js';
2
+
3
+ export const convertFormData = (formId) => {
4
+ const formInput = qSelAll(formId)
5
+ const formInputArr = Array.from(formInput)
6
+ return new FormHelper(formInputArr)
7
+
8
+ }
9
+
10
+ /**
11
+ * Sets up an event listener for the given button element that triggers a file upload click event.
12
+ * Also sets up an event listener for the given input element that displays the name of the selected files.
13
+ * @param {string} uploadBtn - the id of a button element that will trigger the file upload dialog box.
14
+ * @param {string} inputId - the id of the input element with the file type.
15
+ * @param {string} fileName - the id of the element where the selected file names will be displayed.
16
+ */
17
+ export const showImageFileUploadFn = (uploadBtn, inputId, fileName) => {
18
+ const input = id(inputId);
19
+ const fileNamesEl = id(fileName);
20
+ id(uploadBtn).addEventListener('click', () => input.click());
21
+ input.addEventListener('change', () => fileNamesEl.innerText = Array.from(input.files).map(file => file.name).join(', '));
22
+ };
package/Http.js ADDED
@@ -0,0 +1,150 @@
1
+ import { id, log } from './UtilityHtml.js';
2
+ import { clearLoader } from './Loader.js';
3
+ import axios from 'axios'
4
+ import axiosRetry from 'axios-retry';
5
+
6
+ axiosRetry(axios, { retries: 3 });
7
+
8
+
9
+ export const postFormData = async (url, formId, redirect = null, css = null) => {
10
+ const notificationForm = `${formId}_notification`;
11
+ const notificationId = id(notificationForm);
12
+
13
+ if (!notificationId) {
14
+ throw new Error('Notification element not found');
15
+ }
16
+
17
+ // Cleanup previous notification styles
18
+ notificationId.style.display = 'none';
19
+ notificationId.className = notificationId.className.replace(/is-danger|is-success|w3-red|w3-green|bg-danger|bg-success/g, '');
20
+
21
+ const form = id(formId);
22
+ if (!form) {
23
+ throw new Error('Form element not found');
24
+ }
25
+
26
+ const formEntries = new FormData(form);
27
+ formEntries.delete('submit');
28
+ formEntries.delete('checkbox_id');
29
+
30
+ const options = {
31
+ baseURL: '/',
32
+ xsrfCookieName: 'XSRF-TOKEN',
33
+ xsrfHeaderName: 'X-XSRF-TOKEN',
34
+ withCredentials: true,
35
+ };
36
+
37
+ try {
38
+ const response = await axios.post(url, formEntries, options);
39
+ if (!(response.status >= 200 && response.status < 300)) {
40
+ throw new Error(response.data?.message || 'Request failed');
41
+ }
42
+
43
+ const successClass = getNotificationClassByCSS(css || 'bulma', 'green');
44
+ const { message } = response.data || {};
45
+ const { id: idSetFromHttp, famCode: famCodeSetFromHttp, outcome: dbHttpResult } = typeof message === 'object' ? message : { outcome: message };
46
+
47
+ if (!idSetFromHttp || !dbHttpResult || !famCodeSetFromHttp) {
48
+ throw new Error('Response data is missing required fields');
49
+ }
50
+
51
+ sessionStorage.setItem('idSetFromHttp', sessionStorage.getItem('idSetFromHttp') || idSetFromHttp);
52
+ sessionStorage.setItem('famCodeSetFromHttp', sessionStorage.getItem('famCodeSetFromHttp') || famCodeSetFromHttp);
53
+
54
+ processFormDataAction(successClass, dbHttpResult, notificationId);
55
+
56
+ if (redirect) {
57
+ setTimeout(() => window.location.assign(redirect), 2000);
58
+ }
59
+ } catch (error) {
60
+ const errorClass = getNotificationClassByCSS(css || 'bulma', 'red');
61
+ const errorMessage = error.response?.data?.error || error.request || 'An unknown error occurred';
62
+ processFormDataAction(errorClass, errorMessage, notificationId);
63
+ }
64
+ };
65
+
66
+
67
+ /**
68
+ * Displays a notification message and handles the loading indicator.
69
+ * @param {string} cssClass - The CSS class to add to the notification element.
70
+ * @param {string} message - The message to display in the notification element.
71
+ * @param {HTMLElement} formNotificationId - The notification element to display the message in.
72
+ * @returns {void}
73
+ */
74
+ const processFormDataAction = (cssClass, message, formNotificationId) => {
75
+ if (!formNotificationId) {
76
+ return log('Notification element not found');
77
+ }
78
+
79
+ formNotificationId.style.display = 'block';
80
+ formNotificationId.classList.add(cssClass);
81
+
82
+ const errorElement = id('error');
83
+ if (errorElement) {
84
+ errorElement.innerHTML = message;
85
+ errorElement.scrollIntoView({ behavior: 'smooth' });
86
+ }
87
+
88
+ clearLoader();
89
+ };
90
+
91
+ const getNotificationClassByCSS = (css, color) => (css === 'bulma' ? `is-${color}` : color === 'green' ? `${css}-${color}` : `${css}-danger`);
92
+
93
+
94
+ /**
95
+ * Fetches data from a specified API endpoint using a GET request.
96
+ *
97
+ * @param {string} URL - The API endpoint to fetch data from.
98
+ * @param {string|null} [token=null] - Optional authorization token for the request.
99
+ * @returns {Promise<Object>} - A promise that resolves with the response data or rejects with an error.
100
+ */
101
+
102
+ export const getApiData = async (URL, token = null) => {
103
+ const headers = {
104
+ 'X-Requested-With': 'XMLHttpRequest',
105
+ 'Content-Type': 'application/json',
106
+ 'Accept': 'application/json',
107
+ };
108
+ if (token) {
109
+ headers['Authorization'] = `Bearer ${token}`;
110
+ }
111
+
112
+ try {
113
+ const response = await axios.get(URL, { headers });
114
+ return response.data;
115
+ } catch (error) {
116
+ return Promise.reject(error);
117
+ }
118
+ }
119
+
120
+
121
+ export const getMultipleApiData = async (url1, url2, token = null) => {
122
+ const config = {
123
+ headers: {
124
+ 'X-Requested-With': 'XMLHttpRequest',
125
+ 'Content-Type': 'application/json',
126
+ 'Accept': 'application/json',
127
+ Authorization: `Bearer ${token}`,
128
+ },
129
+ };
130
+
131
+ return await axios.all([axios.get(url1, config), axios.get(url2, config)]).then(axios.spread((res1, res2) => [res1.data, res2.data]));
132
+ };
133
+
134
+ export const postMultipleApiData = async (url1, url2, formData, token = null) => {
135
+ const config = {
136
+ headers: {
137
+ 'X-Requested-With': 'XMLHttpRequest',
138
+ 'Content-Type': 'application/json',
139
+ 'Accept': 'application/json',
140
+ 'Authorization': 'Bearer ' + token
141
+ },
142
+ };
143
+
144
+ const [res1, res2] = await Promise.all([
145
+ axios.post(url1, formData, config),
146
+ axios.post(url2, formData, config),
147
+ ]);
148
+
149
+ return [res1.data, res2.data];
150
+ };
package/Loader.js ADDED
@@ -0,0 +1,35 @@
1
+ export const loaderIconBootstrap = () => {
2
+
3
+ return `<div class="spinner-grow text-primary" role="status">
4
+ <span class="sr-only">Loading...</span>
5
+ </div>`
6
+ }
7
+
8
+ export const loaderIcon = () => {
9
+
10
+ return `<div class="loader"></div>`
11
+ }
12
+
13
+ export const loaderIconBulma = () => {
14
+
15
+ return `<div class="is-loading"></div>`
16
+ }
17
+
18
+ export const clearLoader = (elementId = 'setLoader', loaderClass = "loader") => {
19
+ const loader = id(elementId);
20
+ if (!loader) {
21
+ throw new Error(`Element not found`);
22
+ }
23
+
24
+ loader.style.display = "none";
25
+ loader.classList.remove(loaderClass);
26
+
27
+ }
28
+ export const showLoader = (elementId ='setLoader', loaderClass = "loader") => {
29
+ const loader = id(elementId);
30
+ if (!loader) {
31
+ throw new Error(`Element not found`);
32
+ }
33
+ loader.classList.add(loaderClass);
34
+ loader.style.display = "block";
35
+ };
package/Login.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ import { convertFormData } from './FormProcessing.js';
3
+ import { postFormData } from "./Http.js";
4
+ import { showLoader, clearLoader } from './Loader.js';
5
+ import { showError } from "./ShowResponse.js";
6
+
7
+ // block the setLoader div
8
+
9
+ /**
10
+ * Handles the submission of the login form.
11
+ * @param {string} formId - The ID of the form to submit.
12
+ * @param {string} loginURL - The URL to make the POST request to.
13
+ * @param {string} redirect - The URL to redirect the user to after the submission is complete.
14
+ * @param {string} [css=null] - The CSS class to add to the notification element if the submission is successful.
15
+ * @returns {void}
16
+ * @throws {Error} - If there is an error with the submission
17
+ */
18
+ export const loginSubmission = async (formId, loginURL, redirect, css = null) => {
19
+ const formData = convertFormData(formId);
20
+ formData.clearError();
21
+
22
+ try {
23
+ formData.emailVal();
24
+ formData.massValidate();
25
+
26
+ if (!formData.error.length) {
27
+
28
+ showLoader();
29
+ localStorage.setItem('redirect', redirect);
30
+
31
+ await postFormData(loginURL, formId, redirect, css);
32
+ } else {
33
+ alert('The form cannot be submitted. Please check the errors');
34
+ }
35
+ } catch (err) {
36
+ showError(err);
37
+ } finally {
38
+ clearLoader();
39
+ }
40
+
41
+ }
42
+
43
+
@@ -0,0 +1,93 @@
1
+ import { id } from './UtilityHtml.js';
2
+ import axios from "axios"
3
+ export const showError = (e) => console.error(`${e.name} at ${e.fileName}:${e.lineNumber} - ${e.message}\n${e.stack}`);
4
+
5
+ export const msgException = (errorMessage) => {
6
+
7
+ throw new Error(errorMessage)
8
+ }
9
+
10
+
11
+ /**
12
+ *
13
+ * @param {*} elementId - element id
14
+ * @param {*} addClass either a success or danger class (green or red)
15
+ * @param {*} message - html message to convey success or failure
16
+ * @param {*} timer - timer for the message to disappear- default is 5 secs
17
+ */
18
+ export const showNotification = (elementId, addClass, message, timer = 5000) => {
19
+ // display the success information for 10sec
20
+ id(`${elementId}`).style.display = "block" // unblock the notification
21
+ id(`${elementId}`).classList.add(addClass) // add the success class
22
+ id(`${elementId}`).innerHTML = message // error element
23
+ id('loader').classList.remove('loader') // remove loader
24
+
25
+ setTimeout(() => {
26
+ id(`${elementId}`).style.backgroundColor = ""
27
+ id(`${elementId}`).style.color = ""
28
+ id(`${elementId}`).innerHTML = ""
29
+ }, timer)
30
+ }
31
+
32
+ /**
33
+ *
34
+ * @param {*} elementId - element id
35
+ * @returns {Promise<void>}
36
+ * @description This is a function that deletes a notification. It takes in the elementId of the delete button
37
+ * and uses that to get the user ID and the notification ID. It makes a put request to the server to
38
+ * mark the notification as read. If the request is successful, it removes the notification from the page
39
+ * and decrements the notification count by 1. If the request is unsuccessful, it logs an error message.
40
+ */
41
+ export const deleteNotification = async (elementId) => {
42
+
43
+ // Extract the user ID from the target ID
44
+ const senderId = elementId.replace("deleteNotification", "notificationBar");
45
+
46
+ const elementData = id(elementId)
47
+ const data = elementData.getAttribute("data-id");
48
+
49
+ // change the background of the clicked element
50
+
51
+ const notificationHTML = id(senderId);
52
+
53
+ // Make sure required variables are defined before using them
54
+ if (
55
+ typeof yourId === 'undefined' ||
56
+ typeof famCode === 'undefined'
57
+ ) {
58
+ msgException("Required parameters (yourId or famCode) are not defined");
59
+ }
60
+
61
+ const url = `/removeNotification/${yourId}/${famCode}/${data}`
62
+
63
+
64
+ const response = await axios.put(url)
65
+
66
+
67
+ if (response.data.message === "Notification marked as read") {
68
+
69
+ // remove a html element with notificationBar after 2 mins
70
+ notificationHTML.remove()
71
+
72
+ // reduce the notification count as you have deleted the notification
73
+
74
+ const newValues = parseInt(sessionStorage.getItem('notificationCount') - 1)
75
+ id('notification_count').innerHTML = newValues;
76
+ } else {
77
+ msgException("Error removing notification" + " " + response.data.message);
78
+ }
79
+ }
80
+
81
+ export const showResponse = (theId, message, status) => {
82
+ const responseEl = id(theId)
83
+ const col = status ? 'green' : 'red'
84
+
85
+ responseEl.innerHTML = message
86
+ responseEl.style.color = 'green'
87
+ responseEl.style.backgroundColor = col
88
+ responseEl.style.color = 'white';
89
+ setTimeout(() => {
90
+ responseEl.innerHTML = '';
91
+ }, 5000); // Disappear after 5 seconds
92
+
93
+ }