powerpagestoolkit 2.221.12 → 2.701.4
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 +421 -230
- package/dist/import_map.json +6 -0
- package/dist/src/constants/eventMapping.d.ts +7 -0
- package/dist/src/constants/symbols.d.ts +14 -0
- package/dist/{API.d.ts → src/core/API.d.ts} +16 -8
- package/dist/src/core/DOMNodeReference.d.ts +227 -0
- package/dist/src/core/DOMNodeReferenceArray.d.ts +12 -0
- package/dist/src/core/List.d.ts +13 -0
- package/dist/src/core/bindForm.d.ts +28 -0
- package/dist/src/core/createDOMNodeReferences.d.ts +88 -0
- package/dist/src/core/waitFor.d.ts +9 -0
- package/dist/{errors.d.ts → src/errors/errors.d.ts} +5 -1
- package/dist/src/globals.d.ts +152 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +1251 -0
- package/dist/src/index.js.LEGAL.txt +0 -0
- package/dist/src/index.js.map +7 -0
- package/dist/src/managers/ReferenceManager.d.ts +6 -0
- package/dist/src/utils/createInfoElement.d.ts +8 -0
- package/dist/src/utils/enhanceArray.d.ts +13 -0
- package/dist/src/utils/safeAjax.d.ts +5 -0
- package/package.json +42 -29
- package/dist/DOMNodeReference.d.ts +0 -197
- package/dist/bundle.js +0 -704
- package/dist/createDOMNodeReferences.d.ts +0 -15
- package/dist/createInfoElement.d.ts +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/safeAjax.d.ts +0 -1
- package/dist/waitFor.d.ts +0 -1
|
@@ -0,0 +1,1251 @@
|
|
|
1
|
+
/* @ts-self-types="./index.d.ts" */
|
|
2
|
+
// src/utils/safeAjax.ts
|
|
3
|
+
function safeAjax(ajaxOptions) {
|
|
4
|
+
const deferredAjax = $.Deferred();
|
|
5
|
+
shell.getTokenDeferred().done(function(token) {
|
|
6
|
+
if (!ajaxOptions.headers) {
|
|
7
|
+
$.extend(ajaxOptions, {
|
|
8
|
+
headers: {
|
|
9
|
+
__RequestVerificationToken: token
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
} else {
|
|
13
|
+
ajaxOptions.headers["__RequestVerificationToken"] = token;
|
|
14
|
+
}
|
|
15
|
+
$.ajax(ajaxOptions).done(function(data, textStatus, jqXHR) {
|
|
16
|
+
validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
|
|
17
|
+
}).fail(deferredAjax.reject);
|
|
18
|
+
}).fail(function() {
|
|
19
|
+
deferredAjax.rejectWith(this, arguments);
|
|
20
|
+
});
|
|
21
|
+
return deferredAjax.promise();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/core/API.ts
|
|
25
|
+
var API = class {
|
|
26
|
+
/**
|
|
27
|
+
* @param tableSetName The dataverse set name for the table that you are updating a record in
|
|
28
|
+
* @param data The JSON of the fields and data that are to be updated on the targeted record
|
|
29
|
+
* @returns a Promise resolving the successful results *[record id]* of the POST request, or rejecting the failed results *[error]* of the POST request.
|
|
30
|
+
*/
|
|
31
|
+
static createRecord(tableSetName, data) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
safeAjax({
|
|
34
|
+
type: "POST",
|
|
35
|
+
url: `/_api/${tableSetName}`,
|
|
36
|
+
data: JSON.stringify(data),
|
|
37
|
+
contentType: "application/json",
|
|
38
|
+
success: function(_response, _status, xhr) {
|
|
39
|
+
resolve(xhr.getResponseHeader("entityid"));
|
|
40
|
+
},
|
|
41
|
+
error: (error) => {
|
|
42
|
+
reject(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param tableSetName The DataVerse SET name of the table being queried
|
|
50
|
+
* @param recordID the GUID of the records to be retrieved
|
|
51
|
+
* @param selectColumns *OPTIONAL* if desired, enter your own custom OData query for advanced GET results. Format = select=column1,column2,column3...
|
|
52
|
+
* @returns a Promise resolving the successful results of the GET request, or rejecting the failed results of the GET request
|
|
53
|
+
*/
|
|
54
|
+
static getRecord(tableSetName, recordID, selectColumns) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const url = `/_api/${tableSetName}(${recordID})${selectColumns ? `?$${selectColumns}` : ""}`;
|
|
57
|
+
safeAjax({
|
|
58
|
+
type: "GET",
|
|
59
|
+
url,
|
|
60
|
+
success: resolve,
|
|
61
|
+
error: reject
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param tableSetName The dataverse set name of the table being queried
|
|
68
|
+
* @param queryParameters *OPTIONAL* the OData query parameters for refining search results: *format = $filter=filters&$select=columns*
|
|
69
|
+
* @returns a Promise resolving the successful results of the GET request, or rejecting the failed results of the GET request
|
|
70
|
+
*/
|
|
71
|
+
static getMultiple(tableSetName, queryParameters) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const url = `/_api/${tableSetName}${queryParameters ? `?${queryParameters}` : ""}`;
|
|
74
|
+
safeAjax({
|
|
75
|
+
type: "GET",
|
|
76
|
+
url,
|
|
77
|
+
success: function(response) {
|
|
78
|
+
resolve(response.value);
|
|
79
|
+
},
|
|
80
|
+
error: reject
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @param tableSetName The dataverse set name for the table that you are updating a record in
|
|
87
|
+
* @param recordId The GUID of the record that is being updated
|
|
88
|
+
* @param data The JSON of the fields and data that are to be updated on the targeted record
|
|
89
|
+
* @returns A Promise with the results of the API execution
|
|
90
|
+
*/
|
|
91
|
+
static updateRecord(tableSetName, recordId, data) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const url = `/_api/${tableSetName}(${recordId})`;
|
|
94
|
+
safeAjax({
|
|
95
|
+
type: "PATCH",
|
|
96
|
+
url,
|
|
97
|
+
data: JSON.stringify(data),
|
|
98
|
+
success: resolve,
|
|
99
|
+
error: reject
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var API_default = API;
|
|
105
|
+
|
|
106
|
+
// src/core/waitFor.ts
|
|
107
|
+
function waitFor(target, root = document, multiple = false, debounceTime2) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
if (multiple) {
|
|
110
|
+
let timeout;
|
|
111
|
+
const observedElements = [];
|
|
112
|
+
const observedSet = /* @__PURE__ */ new Set();
|
|
113
|
+
if (debounceTime2 < 1) {
|
|
114
|
+
return resolve(
|
|
115
|
+
Array.from(root.querySelectorAll(target))
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const observer = new MutationObserver(() => {
|
|
119
|
+
const found = Array.from(root.querySelectorAll(target));
|
|
120
|
+
found.forEach((element) => {
|
|
121
|
+
if (!observedSet.has(element)) {
|
|
122
|
+
observedSet.add(element);
|
|
123
|
+
observedElements.push(element);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
timeout = setTimeout(() => {
|
|
128
|
+
if (observedElements.length > 0) {
|
|
129
|
+
observer.disconnect();
|
|
130
|
+
resolve(observedElements);
|
|
131
|
+
} else {
|
|
132
|
+
reject(
|
|
133
|
+
new Error(
|
|
134
|
+
`No elements found with target: "${target}" within ${debounceTime2 / 1e3} seconds. If the element you are expecting has not loaded yet, consider raising your timeout.`
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}, debounceTime2);
|
|
139
|
+
});
|
|
140
|
+
observer.observe(root, {
|
|
141
|
+
childList: true,
|
|
142
|
+
subtree: true,
|
|
143
|
+
attributes: false
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
const observer = new MutationObserver(() => {
|
|
147
|
+
const observedElement = root.querySelector(target);
|
|
148
|
+
if (observedElement) {
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
observer.disconnect();
|
|
151
|
+
resolve(observedElement);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const timeout = setTimeout(() => {
|
|
155
|
+
observer.disconnect();
|
|
156
|
+
reject(
|
|
157
|
+
new Error(
|
|
158
|
+
`Element not found by target: "${target}" within ${debounceTime2 / 1e3} second. If the element you are expecting has not loaded yet, consider raising your timeout.`
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
}, debounceTime2);
|
|
162
|
+
const element = root.querySelector(target);
|
|
163
|
+
if (element) {
|
|
164
|
+
clearTimeout(timeout);
|
|
165
|
+
return resolve(element);
|
|
166
|
+
}
|
|
167
|
+
observer.observe(root, {
|
|
168
|
+
subtree: true,
|
|
169
|
+
attributes: true,
|
|
170
|
+
childList: true
|
|
171
|
+
// Detects added/removed child elements
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/utils/createInfoElement.ts
|
|
178
|
+
function CreateInfoEl(titleString, iconStyle) {
|
|
179
|
+
if (typeof titleString !== "string") {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`argument "titleString" must be of type "string". Received: "${typeof titleString}"`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (iconStyle && typeof iconStyle !== "object") {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`argument "iconStyle" must be of type "object". Received: "${typeof iconStyle}"`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const span = document.createElement("span");
|
|
190
|
+
span.classList.add("info-icon");
|
|
191
|
+
const icon = document.createElement("i");
|
|
192
|
+
icon.classList.add("fa", "fa-solid", "fa-info-circle");
|
|
193
|
+
icon.setAttribute("aria-label", "Info");
|
|
194
|
+
icon.style.cursor = "pointer";
|
|
195
|
+
const flyoutContent = document.createElement("div");
|
|
196
|
+
flyoutContent.innerHTML = titleString;
|
|
197
|
+
flyoutContent.classList.add("flyout-content");
|
|
198
|
+
span.appendChild(icon);
|
|
199
|
+
span.appendChild(flyoutContent);
|
|
200
|
+
if (iconStyle) {
|
|
201
|
+
Object.assign(icon.style, iconStyle);
|
|
202
|
+
}
|
|
203
|
+
const positionFlyout = () => {
|
|
204
|
+
flyoutContent.style.display = "block";
|
|
205
|
+
const flyoutRect = flyoutContent.getBoundingClientRect();
|
|
206
|
+
const viewportWidth = window.innerWidth;
|
|
207
|
+
if (flyoutRect.right > viewportWidth) {
|
|
208
|
+
const overflowAmount = flyoutRect.right - viewportWidth;
|
|
209
|
+
flyoutContent.style.left = `calc(50% - ${overflowAmount}px)`;
|
|
210
|
+
}
|
|
211
|
+
if (flyoutRect.left < 0) {
|
|
212
|
+
const overflowAmount = Math.abs(flyoutRect.left);
|
|
213
|
+
flyoutContent.style.left = `calc(50% + ${overflowAmount}px)`;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
span.addEventListener("mouseenter", () => {
|
|
217
|
+
positionFlyout();
|
|
218
|
+
});
|
|
219
|
+
span.addEventListener("mouseleave", (event) => {
|
|
220
|
+
const relatedTarget = event.relatedTarget;
|
|
221
|
+
if (!span.contains(relatedTarget)) {
|
|
222
|
+
flyoutContent.style.display = "none";
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
icon.addEventListener("touchstart", (event) => {
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
flyoutContent.style.display = flyoutContent.style.display === "block" ? "none" : "block";
|
|
228
|
+
if (flyoutContent.style.display === "block") {
|
|
229
|
+
positionFlyout();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
document.body.addEventListener("click", (event) => {
|
|
233
|
+
if (!span.contains(event.target)) {
|
|
234
|
+
flyoutContent.style.display = "none";
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
flyoutContent.style.display = "none";
|
|
238
|
+
return span;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/constants/symbols.ts
|
|
242
|
+
var init = Symbol("__I");
|
|
243
|
+
var destroy = Symbol("__D");
|
|
244
|
+
var valueSync = Symbol("__VS");
|
|
245
|
+
var dateSync = Symbol("__DS");
|
|
246
|
+
var getElementValue = Symbol("__GEV");
|
|
247
|
+
var attachVisibilityController = Symbol("__AVC");
|
|
248
|
+
var attachRadioButtons = Symbol("__ARB");
|
|
249
|
+
var bindMethods = Symbol("__B");
|
|
250
|
+
var debounceTime = Symbol("__DT");
|
|
251
|
+
var observers = Symbol("__O");
|
|
252
|
+
var boundEventListeners = Symbol("__BEV");
|
|
253
|
+
var isValidFormElement = Symbol("__VFE");
|
|
254
|
+
var registerEventListener = Symbol("__REV");
|
|
255
|
+
|
|
256
|
+
// src/errors/errors.ts
|
|
257
|
+
var DOMNodeInitializationError = class extends Error {
|
|
258
|
+
constructor(instance, error) {
|
|
259
|
+
super(
|
|
260
|
+
`There was an error initializing a DOMNodeReference for target: ${instance.target}, :: ${error}`
|
|
261
|
+
);
|
|
262
|
+
this.name = "DOMNodeInitializationError";
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var DOMNodeNotFoundError = class extends Error {
|
|
266
|
+
constructor(instance) {
|
|
267
|
+
super(`The targeted DOM element was not found: ${instance.target}`);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var ValidationConfigError = class extends Error {
|
|
271
|
+
constructor(node, message) {
|
|
272
|
+
super(`Validation configuration error for ${node.target}: ${message}`);
|
|
273
|
+
this.name = "ValidationConfigError";
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/core/DOMNodeReference.ts
|
|
278
|
+
var EventTypes = {
|
|
279
|
+
CHECKBOX: "click",
|
|
280
|
+
RADIO: "click",
|
|
281
|
+
SELECT: "change",
|
|
282
|
+
TEXT: "keyup",
|
|
283
|
+
DEFAULT: "input"
|
|
284
|
+
};
|
|
285
|
+
var DOMNodeReference = class _DOMNodeReference {
|
|
286
|
+
// properties initialized in the constructor
|
|
287
|
+
target;
|
|
288
|
+
logicalName;
|
|
289
|
+
root;
|
|
290
|
+
[debounceTime];
|
|
291
|
+
isLoaded;
|
|
292
|
+
defaultDisplay;
|
|
293
|
+
[observers] = [];
|
|
294
|
+
[boundEventListeners] = [];
|
|
295
|
+
isRadio = false;
|
|
296
|
+
radioType = null;
|
|
297
|
+
/**
|
|
298
|
+
* The value of the element that this node represents
|
|
299
|
+
* stays in syncs with the live DOM elements?.,m via event handler
|
|
300
|
+
*/
|
|
301
|
+
value;
|
|
302
|
+
/**
|
|
303
|
+
* Creates an instance of DOMNodeReference.
|
|
304
|
+
* @param target - The CSS selector to find the desired DOM element.
|
|
305
|
+
* @param root - Optionally specify the element within to search for the element targeted by 'target'
|
|
306
|
+
* Defaults to 'document.body'
|
|
307
|
+
*/
|
|
308
|
+
/******/
|
|
309
|
+
/******/
|
|
310
|
+
constructor(target, root = document.body, debounceTime2) {
|
|
311
|
+
this.target = target;
|
|
312
|
+
this.logicalName = this.extractLogicalName(target);
|
|
313
|
+
this.root = root;
|
|
314
|
+
this[debounceTime] = debounceTime2;
|
|
315
|
+
this.isLoaded = false;
|
|
316
|
+
this.defaultDisplay = "";
|
|
317
|
+
this.value = null;
|
|
318
|
+
this[bindMethods]();
|
|
319
|
+
}
|
|
320
|
+
extractLogicalName(target) {
|
|
321
|
+
if (typeof target !== "string") return "";
|
|
322
|
+
const bracketMatch = target.match(/\[([^\]]+)\]/);
|
|
323
|
+
if (!bracketMatch) return target.replace(/[#\[\]]/g, "");
|
|
324
|
+
const content = bracketMatch[1];
|
|
325
|
+
const quoteMatch = content.match(/["']([^"']+)["']/);
|
|
326
|
+
return (quoteMatch?.[1] || content).replace(/[#\[\]]/g, "");
|
|
327
|
+
}
|
|
328
|
+
async [init]() {
|
|
329
|
+
try {
|
|
330
|
+
if (this.target instanceof HTMLElement) {
|
|
331
|
+
this.element = this.target;
|
|
332
|
+
} else {
|
|
333
|
+
this.element = await waitFor(
|
|
334
|
+
this.target,
|
|
335
|
+
this.root,
|
|
336
|
+
false,
|
|
337
|
+
this[debounceTime]
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (!this.element) {
|
|
341
|
+
throw new DOMNodeNotFoundError(this);
|
|
342
|
+
}
|
|
343
|
+
if (this.element.id && this.element.querySelectorAll(
|
|
344
|
+
`#${this.element.id} > input[type="radio"]`
|
|
345
|
+
).length > 0) {
|
|
346
|
+
await this[attachRadioButtons]();
|
|
347
|
+
}
|
|
348
|
+
this[valueSync]();
|
|
349
|
+
this[attachVisibilityController]();
|
|
350
|
+
this.defaultDisplay = this.visibilityController.style.display;
|
|
351
|
+
const observer = new MutationObserver((mutations) => {
|
|
352
|
+
for (const mutation of mutations) {
|
|
353
|
+
if (Array.from(mutation.removedNodes).includes(this.element)) {
|
|
354
|
+
this[destroy]();
|
|
355
|
+
observer.disconnect();
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
observer.observe(document.body, {
|
|
361
|
+
childList: true,
|
|
362
|
+
subtree: true
|
|
363
|
+
});
|
|
364
|
+
this.isLoaded = true;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
367
|
+
throw new DOMNodeInitializationError(this, errorMessage);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Initializes value synchronization with appropriate event listeners
|
|
372
|
+
* based on element type.
|
|
373
|
+
*/
|
|
374
|
+
[valueSync]() {
|
|
375
|
+
if (!this[isValidFormElement](this.element)) return;
|
|
376
|
+
this.updateValue();
|
|
377
|
+
const eventType = this.determineEventType();
|
|
378
|
+
this[registerEventListener](this.element, eventType, this.updateValue);
|
|
379
|
+
if (this.isDateInput()) {
|
|
380
|
+
this[dateSync](this.element);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
determineEventType() {
|
|
384
|
+
if (this.element instanceof HTMLSelectElement) return "change";
|
|
385
|
+
if (this.element instanceof HTMLTextAreaElement) return "keyup";
|
|
386
|
+
if (!(this.element instanceof HTMLInputElement)) return EventTypes.DEFAULT;
|
|
387
|
+
return EventTypes[this.element.type.toUpperCase()] || EventTypes.DEFAULT;
|
|
388
|
+
}
|
|
389
|
+
isDateInput() {
|
|
390
|
+
return this.element instanceof HTMLInputElement && this.element.dataset.type === "date";
|
|
391
|
+
}
|
|
392
|
+
[isValidFormElement](element) {
|
|
393
|
+
return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSpanElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement;
|
|
394
|
+
}
|
|
395
|
+
[registerEventListener](element, eventType, handler) {
|
|
396
|
+
element.addEventListener(eventType, handler);
|
|
397
|
+
this[boundEventListeners].push({
|
|
398
|
+
element,
|
|
399
|
+
handler,
|
|
400
|
+
event: eventType
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async [dateSync](element) {
|
|
404
|
+
const parentElement = element.parentElement;
|
|
405
|
+
if (!parentElement) {
|
|
406
|
+
throw new Error("Date input must have a parent element");
|
|
407
|
+
}
|
|
408
|
+
const dateNode = await waitFor(
|
|
409
|
+
"[data-date-format]",
|
|
410
|
+
parentElement,
|
|
411
|
+
false,
|
|
412
|
+
1500
|
|
413
|
+
);
|
|
414
|
+
this[registerEventListener](dateNode, "select", this.updateValue);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Gets the current value of the element based on its type
|
|
418
|
+
* @protected
|
|
419
|
+
* @returns Object containing value and optional checked state
|
|
420
|
+
*/
|
|
421
|
+
[getElementValue]() {
|
|
422
|
+
const input = this.element;
|
|
423
|
+
const select = this.element;
|
|
424
|
+
if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
|
|
425
|
+
return {
|
|
426
|
+
value: this.yesRadio.checked,
|
|
427
|
+
checked: this.yesRadio.checked
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
let returnValue;
|
|
431
|
+
switch (input.type) {
|
|
432
|
+
case "checkbox":
|
|
433
|
+
case "radio":
|
|
434
|
+
return {
|
|
435
|
+
value: input.checked,
|
|
436
|
+
checked: input.checked
|
|
437
|
+
};
|
|
438
|
+
case "select-multiple":
|
|
439
|
+
return {
|
|
440
|
+
value: Array.from(select.selectedOptions).map(
|
|
441
|
+
(option) => option.value
|
|
442
|
+
)
|
|
443
|
+
};
|
|
444
|
+
case "select-one":
|
|
445
|
+
return {
|
|
446
|
+
value: select.value
|
|
447
|
+
};
|
|
448
|
+
case "number":
|
|
449
|
+
return {
|
|
450
|
+
value: input.value !== "" ? Number(input.value) : null
|
|
451
|
+
};
|
|
452
|
+
default: {
|
|
453
|
+
let cleanValue = input.value;
|
|
454
|
+
if (this.element.classList.contains("decimal") || this.element.classList.contains("money")) {
|
|
455
|
+
cleanValue = input.value.replace(/[$,]/g, "");
|
|
456
|
+
}
|
|
457
|
+
returnValue = {
|
|
458
|
+
value: this.element.classList.contains("decimal") || this.element.classList.contains("money") ? parseFloat(cleanValue) : cleanValue
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
returnValue = {
|
|
463
|
+
...returnValue,
|
|
464
|
+
value: this.validateValue(returnValue.value)
|
|
465
|
+
};
|
|
466
|
+
return returnValue;
|
|
467
|
+
}
|
|
468
|
+
[attachVisibilityController]() {
|
|
469
|
+
this.visibilityController = this.element;
|
|
470
|
+
if (this.element.tagName === "TABLE") {
|
|
471
|
+
const fieldset = this.element.closest("fieldset");
|
|
472
|
+
if (fieldset) {
|
|
473
|
+
this.visibilityController = fieldset;
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const tagsRequiringTdParent = [
|
|
478
|
+
"SPAN",
|
|
479
|
+
"INPUT",
|
|
480
|
+
"TEXTAREA",
|
|
481
|
+
"SELECT",
|
|
482
|
+
"TABLE"
|
|
483
|
+
];
|
|
484
|
+
if (tagsRequiringTdParent.includes(this.element.tagName)) {
|
|
485
|
+
const tdParent = this.element.closest("td");
|
|
486
|
+
if (tdParent) {
|
|
487
|
+
this.visibilityController = tdParent;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async [attachRadioButtons]() {
|
|
492
|
+
if (!this.element) {
|
|
493
|
+
console.error(
|
|
494
|
+
"'this.element' not found: cannot attach radio buttons for ",
|
|
495
|
+
this.target
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
this.yesRadio = await createDOMNodeReference('input[type="radio"][value="1"]', {
|
|
500
|
+
root: this.element
|
|
501
|
+
});
|
|
502
|
+
this.yesRadio.isRadio = true;
|
|
503
|
+
this.yesRadio.radioType = "truthy";
|
|
504
|
+
this.noRadio = await createDOMNodeReference('input[type="radio"][value="0"]', {
|
|
505
|
+
root: this.element
|
|
506
|
+
});
|
|
507
|
+
this.noRadio.isRadio = true;
|
|
508
|
+
this.noRadio.radioType = "falsy";
|
|
509
|
+
}
|
|
510
|
+
[bindMethods]() {
|
|
511
|
+
const prototype = Object.getPrototypeOf(this);
|
|
512
|
+
for (const key of Object.getOwnPropertyNames(prototype)) {
|
|
513
|
+
const value = this[key];
|
|
514
|
+
if (key !== "constructor" && typeof value === "function") {
|
|
515
|
+
this[key] = value.bind(this);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
[destroy]() {
|
|
520
|
+
this[boundEventListeners]?.forEach((binding) => {
|
|
521
|
+
binding.element?.removeEventListener(binding.event, binding.handler);
|
|
522
|
+
});
|
|
523
|
+
this[observers]?.forEach((observer) => {
|
|
524
|
+
observer.disconnect();
|
|
525
|
+
});
|
|
526
|
+
this.yesRadio?.[destroy]();
|
|
527
|
+
this.noRadio?.[destroy]();
|
|
528
|
+
this.yesRadio = null;
|
|
529
|
+
this.noRadio = null;
|
|
530
|
+
this.isLoaded = false;
|
|
531
|
+
this.value = null;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Updates the value and checked state based on element type
|
|
535
|
+
* @public
|
|
536
|
+
*/
|
|
537
|
+
updateValue(e) {
|
|
538
|
+
if (e && !e.isTrusted) return;
|
|
539
|
+
if (e) {
|
|
540
|
+
e.stopPropagation();
|
|
541
|
+
}
|
|
542
|
+
if (this.yesRadio && this.noRadio) {
|
|
543
|
+
this.yesRadio.updateValue();
|
|
544
|
+
this.noRadio.updateValue();
|
|
545
|
+
}
|
|
546
|
+
const elementValue = this[getElementValue]();
|
|
547
|
+
this.value = elementValue.value;
|
|
548
|
+
if (elementValue.checked !== void 0) {
|
|
549
|
+
this.checked = elementValue.checked;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
validateValue(value) {
|
|
553
|
+
if (value === null || value === "") {
|
|
554
|
+
return value;
|
|
555
|
+
}
|
|
556
|
+
if (!isNaN(Number(value))) {
|
|
557
|
+
return Number(value);
|
|
558
|
+
}
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Sets up an event listener based on the specified event type, executing the specified
|
|
563
|
+
* event handler
|
|
564
|
+
* @param eventType - The DOM event to watch for
|
|
565
|
+
* @param eventHandler - The callback function that runs when the
|
|
566
|
+
* specified event occurs.
|
|
567
|
+
* @returns - Instance of this [provides option to method chain]
|
|
568
|
+
*/
|
|
569
|
+
on(eventType, eventHandler) {
|
|
570
|
+
if (typeof eventHandler !== "function") {
|
|
571
|
+
throw new Error(
|
|
572
|
+
`Argument "eventHandler" must be a Function. Received: ${typeof eventHandler}`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
this[registerEventListener](
|
|
576
|
+
this.element,
|
|
577
|
+
eventType,
|
|
578
|
+
eventHandler.bind(this)
|
|
579
|
+
);
|
|
580
|
+
return this;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Hides the element by setting its display style to "none".
|
|
584
|
+
* @returns - Instance of this [provides option to method chain]
|
|
585
|
+
*/
|
|
586
|
+
hide() {
|
|
587
|
+
this.visibilityController.style.display = "none";
|
|
588
|
+
return this;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Shows the element by restoring its default display style.
|
|
592
|
+
* @returns - Instance of this [provides option to method chain]
|
|
593
|
+
*/
|
|
594
|
+
show() {
|
|
595
|
+
this.visibilityController.style.display = this.defaultDisplay;
|
|
596
|
+
return this;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* @param shouldShow - Either a function that returns true or false,
|
|
600
|
+
* or a natural boolean to determine the visibility of this
|
|
601
|
+
* @returns - Instance of this [provides option to method chain]
|
|
602
|
+
*/
|
|
603
|
+
toggleVisibility(shouldShow) {
|
|
604
|
+
if (shouldShow instanceof Function) {
|
|
605
|
+
shouldShow(this) ? this.show() : this.hide();
|
|
606
|
+
} else {
|
|
607
|
+
shouldShow ? this.show() : this.hide();
|
|
608
|
+
}
|
|
609
|
+
return this;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Sets the value of the HTML element.
|
|
613
|
+
* @param value - The value to set for the HTML element.
|
|
614
|
+
* for parents of boolean radios, pass true or false as value, or
|
|
615
|
+
* an expression returning a boolean
|
|
616
|
+
* @returns - Instance of this [provides option to method chain]
|
|
617
|
+
*/
|
|
618
|
+
setValue(value) {
|
|
619
|
+
if (value instanceof Function) {
|
|
620
|
+
value = value();
|
|
621
|
+
}
|
|
622
|
+
if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
|
|
623
|
+
this.yesRadio.element.checked = value;
|
|
624
|
+
this.noRadio.element.checked = !value;
|
|
625
|
+
this.value = value;
|
|
626
|
+
this.checked = value;
|
|
627
|
+
this.element.checked = value;
|
|
628
|
+
} else if (this.isRadio || this.element.type === "radio") {
|
|
629
|
+
this.checked = value;
|
|
630
|
+
this.element.checked = value;
|
|
631
|
+
} else {
|
|
632
|
+
this.element.value = value;
|
|
633
|
+
}
|
|
634
|
+
this.value = this.validateValue(value);
|
|
635
|
+
return this;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Disables the element so that users cannot input any data
|
|
639
|
+
* @returns - Instance of this [provides option to method chain]
|
|
640
|
+
*/
|
|
641
|
+
disable() {
|
|
642
|
+
try {
|
|
643
|
+
this.element.disabled = true;
|
|
644
|
+
} catch (error) {
|
|
645
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
646
|
+
throw new Error(
|
|
647
|
+
`There was an error trying to disable the target: ${this.target}: "${errorMessage}"`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
return this;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Clears all values and states of the element.
|
|
654
|
+
* Handles different input types appropriately, and can be called
|
|
655
|
+
* on an element containing N child inputs to clear all
|
|
656
|
+
*
|
|
657
|
+
* @returns - Instance of this [provides option to method chain]
|
|
658
|
+
* @throws If clearing values fails
|
|
659
|
+
*/
|
|
660
|
+
async clearValue() {
|
|
661
|
+
try {
|
|
662
|
+
const element = this.element;
|
|
663
|
+
if (element instanceof HTMLInputElement) {
|
|
664
|
+
switch (element.type.toLowerCase()) {
|
|
665
|
+
case "checkbox":
|
|
666
|
+
case "radio":
|
|
667
|
+
element.checked = false;
|
|
668
|
+
this.checked = false;
|
|
669
|
+
this.value = false;
|
|
670
|
+
break;
|
|
671
|
+
case "number":
|
|
672
|
+
element.value = "";
|
|
673
|
+
this.value = "";
|
|
674
|
+
break;
|
|
675
|
+
default:
|
|
676
|
+
element.value = "";
|
|
677
|
+
this.value = "";
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
} else if (element instanceof HTMLSelectElement) {
|
|
681
|
+
if (element.multiple) {
|
|
682
|
+
Array.from(element.options).forEach(
|
|
683
|
+
(option) => option.selected = false
|
|
684
|
+
);
|
|
685
|
+
this.value = [];
|
|
686
|
+
} else {
|
|
687
|
+
element.selectedIndex = -1;
|
|
688
|
+
this.value = "";
|
|
689
|
+
}
|
|
690
|
+
} else if (element instanceof HTMLTextAreaElement) {
|
|
691
|
+
element.value = "";
|
|
692
|
+
this.value = "";
|
|
693
|
+
} else {
|
|
694
|
+
this.value = "";
|
|
695
|
+
const childInputs = Array.from(
|
|
696
|
+
this.element.querySelectorAll("input, select, textarea")
|
|
697
|
+
);
|
|
698
|
+
if (childInputs.length > 0) {
|
|
699
|
+
const promises = childInputs.map(async (input) => {
|
|
700
|
+
const inputRef = await createDOMNodeReference(
|
|
701
|
+
input
|
|
702
|
+
);
|
|
703
|
+
return inputRef.clearValue();
|
|
704
|
+
});
|
|
705
|
+
await Promise.all(promises);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
|
|
709
|
+
await this.yesRadio.clearValue();
|
|
710
|
+
await this.noRadio.clearValue();
|
|
711
|
+
}
|
|
712
|
+
const events = [
|
|
713
|
+
new Event("input", { bubbles: true }),
|
|
714
|
+
new Event("change", { bubbles: true }),
|
|
715
|
+
new Event("click", { bubbles: true })
|
|
716
|
+
];
|
|
717
|
+
events.forEach((event) => this.element.dispatchEvent(event));
|
|
718
|
+
return this;
|
|
719
|
+
} catch (error) {
|
|
720
|
+
const errorMessage = `Failed to clear values for element with target "${this.target}": ${error instanceof Error ? error.message : String(error)}`;
|
|
721
|
+
throw new Error(errorMessage);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Enables the element so that users can input data
|
|
726
|
+
* @returns - Instance of this [provides option to method chain]
|
|
727
|
+
*/
|
|
728
|
+
enable() {
|
|
729
|
+
try {
|
|
730
|
+
this.element.disabled = false;
|
|
731
|
+
} catch (_error) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
`There was an error trying to disable the target: ${this.target}`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
return this;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* @param elements - The elements to prepend to the element targeted by this.
|
|
740
|
+
* @returns - Instance of this [provides option to method chain]
|
|
741
|
+
*/
|
|
742
|
+
prepend(...elements) {
|
|
743
|
+
this.element.prepend(...elements);
|
|
744
|
+
return this;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Appends child elements to the HTML element.
|
|
748
|
+
* @param elements - The elements to append to the element targeted by this.
|
|
749
|
+
* @returns - Instance of this [provides option to method chain]
|
|
750
|
+
*/
|
|
751
|
+
append(...elements) {
|
|
752
|
+
this.element.append(...elements);
|
|
753
|
+
return this;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Inserts elements before the HTML element.
|
|
757
|
+
* @param elements - The elements to insert before the HTML element.
|
|
758
|
+
* @returns - Instance of this [provides option to method chain]
|
|
759
|
+
*/
|
|
760
|
+
before(...elements) {
|
|
761
|
+
this.element.before(...elements);
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Inserts elements after the HTML element.
|
|
766
|
+
* @param elements - The elements to insert after the HTML element.
|
|
767
|
+
* @returns - Instance of this [provides option to method chain]
|
|
768
|
+
*/
|
|
769
|
+
after(...elements) {
|
|
770
|
+
this.element.after(...elements);
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Retrieves the label associated with the HTML element.
|
|
775
|
+
* @returns {HTMLElement} The label element associated with this element.
|
|
776
|
+
*/
|
|
777
|
+
getLabel() {
|
|
778
|
+
return document.querySelector(`#${this.element.id}_label`) || null;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Adds a tooltip with specified text to the label associated with the HTML element.
|
|
782
|
+
* @param innerHTML - The innerHTML to append into the tooltip.
|
|
783
|
+
* @param containerStyle - Optional object with CSS Styles to apply to the info element
|
|
784
|
+
* @returns - Instance of this [provides option to method chain]
|
|
785
|
+
*/
|
|
786
|
+
addLabelTooltip(innerHTML, containerStyle) {
|
|
787
|
+
this.getLabel()?.append(
|
|
788
|
+
CreateInfoEl(innerHTML, containerStyle || void 0)
|
|
789
|
+
);
|
|
790
|
+
return this;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Adds a tooltip with the specified text to the element
|
|
794
|
+
* @param innerHTML - The innerHTML to append into the tooltip
|
|
795
|
+
* @param containerStyle - Optional object with CSS Styles to apply to the info element
|
|
796
|
+
* @returns - Instance of this [provides option to method chain]
|
|
797
|
+
*/
|
|
798
|
+
addTooltip(innerHTML, containerStyle) {
|
|
799
|
+
this.append(CreateInfoEl(innerHTML, containerStyle || void 0));
|
|
800
|
+
return this;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Sets the inner HTML content of the HTML element.
|
|
804
|
+
* @param {string} string - The text to set as the inner HTML of the element.
|
|
805
|
+
* @returns - Instance of this [provides option to method chain]
|
|
806
|
+
*/
|
|
807
|
+
setInnerHTML(string) {
|
|
808
|
+
this.element.innerHTML = string;
|
|
809
|
+
return this;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Removes this element from the DOM
|
|
813
|
+
* @returns - Instance of this [provides option to method chain]
|
|
814
|
+
*/
|
|
815
|
+
remove() {
|
|
816
|
+
this.element.remove();
|
|
817
|
+
return this;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* @param options and object containing the styles you want to set : {key: value} e.g.: {'display': 'block'}
|
|
821
|
+
* @returns - Instance of this [provides option to method chain]
|
|
822
|
+
*/
|
|
823
|
+
setStyle(options) {
|
|
824
|
+
if (Object.prototype.toString.call(options) !== "[object Object]") {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`powerpagestoolkit: 'DOMNodeReference.setStyle' required options to be in the form of an object. Argument passed was of type: ${typeof options}`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
for (const _key in options) {
|
|
830
|
+
const key = _key;
|
|
831
|
+
this.element.style[key] = options[key];
|
|
832
|
+
}
|
|
833
|
+
return this;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Unchecks both the yes and no radio buttons if they exist.
|
|
837
|
+
* @returns - Instance of this [provides option to method chain]
|
|
838
|
+
*/
|
|
839
|
+
uncheckRadios() {
|
|
840
|
+
if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
|
|
841
|
+
this.yesRadio.element.checked = false;
|
|
842
|
+
this.noRadio.element.checked = false;
|
|
843
|
+
} else {
|
|
844
|
+
console.error(
|
|
845
|
+
"[SYNACT] Attempted to uncheck radios for an element that has no radios"
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
return this;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
|
|
852
|
+
* @see {@link BusinessRule}
|
|
853
|
+
* @param rule The business rule containing conditions for various actions.
|
|
854
|
+
* @param dependencies For re-evaluation conditions when the state of the dependencies change
|
|
855
|
+
* @returns Instance of this for method chaining.
|
|
856
|
+
*/
|
|
857
|
+
applyBusinessRule(rule, dependencies) {
|
|
858
|
+
try {
|
|
859
|
+
const clearValuesOnHide = rule.setVisibility && rule.setVisibility.length > 1 ? rule.setVisibility[1] : true;
|
|
860
|
+
if (rule.setVisibility) {
|
|
861
|
+
const [condition] = rule.setVisibility;
|
|
862
|
+
const initialState = condition.call(this);
|
|
863
|
+
this.toggleVisibility(initialState);
|
|
864
|
+
}
|
|
865
|
+
if (rule.setRequired) {
|
|
866
|
+
const [isRequired, isValid] = rule.setRequired;
|
|
867
|
+
const fieldDisplayName = (() => {
|
|
868
|
+
let label = this.getLabel();
|
|
869
|
+
if (!label) {
|
|
870
|
+
throw new Error(
|
|
871
|
+
`There was an error accessing the label for this element: ${this.target}`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
label = label.innerHTML;
|
|
875
|
+
if (label.length > 50) {
|
|
876
|
+
label = label.substring(0, 50) + "...";
|
|
877
|
+
}
|
|
878
|
+
return label;
|
|
879
|
+
})();
|
|
880
|
+
if (typeof Page_Validators === "undefined") {
|
|
881
|
+
throw new ValidationConfigError(this, "Page_Validators not found");
|
|
882
|
+
}
|
|
883
|
+
const validatorId = `${this.element.id}Validator`;
|
|
884
|
+
const newValidator = document.createElement("span");
|
|
885
|
+
newValidator.style.display = "none";
|
|
886
|
+
newValidator.id = validatorId;
|
|
887
|
+
Object.assign(newValidator, {
|
|
888
|
+
controltovalidate: this.element.id,
|
|
889
|
+
errormessage: `<a href='#${this.element.id}_label'>${fieldDisplayName} is a required field</a>`,
|
|
890
|
+
evaluationfunction: () => {
|
|
891
|
+
const isFieldRequired = isRequired.call(this);
|
|
892
|
+
const isFieldVisible = window.getComputedStyle(this.visibilityController).display !== "none";
|
|
893
|
+
return !isFieldRequired || !isFieldVisible || isValid.call(this, isFieldRequired);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
Page_Validators.push(newValidator);
|
|
897
|
+
this.setRequiredLevel(isRequired.call(this));
|
|
898
|
+
}
|
|
899
|
+
if (rule.setValue) {
|
|
900
|
+
let [condition, value] = rule.setValue;
|
|
901
|
+
if (value instanceof Function) value = value();
|
|
902
|
+
if (condition.call(this)) {
|
|
903
|
+
this.setValue.call(this, value);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (rule.setDisabled) {
|
|
907
|
+
const condition = rule.setDisabled;
|
|
908
|
+
condition.call(this) ? this.disable() : this.enable();
|
|
909
|
+
}
|
|
910
|
+
if (dependencies.length) {
|
|
911
|
+
let visibilityCondition, isRequired, valueCondition, value, disabledCondition;
|
|
912
|
+
const aggregateHandler = (rule2) => {
|
|
913
|
+
if (rule2.setVisibility) {
|
|
914
|
+
[visibilityCondition] = rule2.setVisibility;
|
|
915
|
+
this.toggleVisibility(visibilityCondition.call(this));
|
|
916
|
+
}
|
|
917
|
+
if (rule2.setRequired) {
|
|
918
|
+
[isRequired] = rule2.setRequired;
|
|
919
|
+
this.setRequiredLevel(isRequired.call(this));
|
|
920
|
+
}
|
|
921
|
+
if (rule2.setValue) {
|
|
922
|
+
[valueCondition, value] = rule2.setValue;
|
|
923
|
+
if (valueCondition.call(this)) this.setValue.call(this, value);
|
|
924
|
+
}
|
|
925
|
+
if (rule2.setDisabled) {
|
|
926
|
+
disabledCondition = rule2.setDisabled;
|
|
927
|
+
disabledCondition.call(this) ? this.disable() : this.enable;
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
this._configDependencyTracking(
|
|
931
|
+
() => aggregateHandler(rule),
|
|
932
|
+
dependencies,
|
|
933
|
+
{
|
|
934
|
+
clearValuesOnHide,
|
|
935
|
+
observeVisibility: true,
|
|
936
|
+
trackInputEvents: false,
|
|
937
|
+
trackRadioButtons: false
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
return this;
|
|
942
|
+
} catch (error) {
|
|
943
|
+
throw new ValidationConfigError(
|
|
944
|
+
this,
|
|
945
|
+
`Failed to apply business rule: ${error}`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Sets up tracking for dependencies using both event listeners and mutation observers.
|
|
951
|
+
* @protected
|
|
952
|
+
* @param handler The function to execute when dependencies change
|
|
953
|
+
* @param dependencies Array of dependent DOM nodes to track
|
|
954
|
+
* @param options Additional configuration options. clearValuesOnHide defaults to false.
|
|
955
|
+
* all other options defaults to true
|
|
956
|
+
*/
|
|
957
|
+
_configDependencyTracking(handler, dependencies, options = {
|
|
958
|
+
clearValuesOnHide: false,
|
|
959
|
+
observeVisibility: true,
|
|
960
|
+
trackInputEvents: true,
|
|
961
|
+
trackRadioButtons: true
|
|
962
|
+
}) {
|
|
963
|
+
const {
|
|
964
|
+
clearValuesOnHide = false,
|
|
965
|
+
observeVisibility = true,
|
|
966
|
+
trackInputEvents = true,
|
|
967
|
+
trackRadioButtons = true
|
|
968
|
+
} = options;
|
|
969
|
+
if (!dependencies?.length) {
|
|
970
|
+
console.warn(
|
|
971
|
+
`powerpagestoolkit: No dependencies specified for ${this.element.id}. Include all referenced nodes in the dependency array for proper tracking.`
|
|
972
|
+
);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
dependencies.forEach((dep) => {
|
|
976
|
+
if (!dep || !(dep instanceof _DOMNodeReference)) {
|
|
977
|
+
throw new TypeError(
|
|
978
|
+
"Each dependency must be a valid DOMNodeReference instance"
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const handleChange = () => {
|
|
982
|
+
handler();
|
|
983
|
+
if (clearValuesOnHide && window.getComputedStyle(this.visibilityController).display === "none") {
|
|
984
|
+
this.clearValue();
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
this[registerEventListener](dep.element, "change", handleChange);
|
|
988
|
+
if (trackInputEvents) {
|
|
989
|
+
this[registerEventListener](dep.element, "input", handleChange);
|
|
990
|
+
}
|
|
991
|
+
if (observeVisibility) {
|
|
992
|
+
const observer = new MutationObserver(() => {
|
|
993
|
+
const display = window.getComputedStyle(
|
|
994
|
+
dep.visibilityController
|
|
995
|
+
).display;
|
|
996
|
+
if (display !== "none") {
|
|
997
|
+
handler();
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
observer.observe(dep.visibilityController, {
|
|
1001
|
+
attributes: true,
|
|
1002
|
+
attributeFilter: ["style"],
|
|
1003
|
+
subtree: false
|
|
1004
|
+
});
|
|
1005
|
+
this[observers].push(observer);
|
|
1006
|
+
}
|
|
1007
|
+
if (trackRadioButtons && dep.yesRadio && dep.noRadio) {
|
|
1008
|
+
[dep.yesRadio, dep.noRadio].forEach((radio) => {
|
|
1009
|
+
radio.on("change", handleChange);
|
|
1010
|
+
this[boundEventListeners].push({
|
|
1011
|
+
element: radio.element,
|
|
1012
|
+
event: "change",
|
|
1013
|
+
handler: handleChange
|
|
1014
|
+
});
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Sets the required level for the field by adding or removing the "required-field" class on the label.
|
|
1021
|
+
*
|
|
1022
|
+
* @param isRequired Determines whether the field should be marked as required.
|
|
1023
|
+
* If true, the "required-field" class is added to the label; if false, it is removed.
|
|
1024
|
+
* @returns Instance of this [provides option to method chain]
|
|
1025
|
+
*/
|
|
1026
|
+
setRequiredLevel(isRequired) {
|
|
1027
|
+
if (isRequired instanceof Function) {
|
|
1028
|
+
isRequired() ? this.getLabel()?.classList.add("required-field") : this.getLabel()?.classList.remove("required-field");
|
|
1029
|
+
return this;
|
|
1030
|
+
} else {
|
|
1031
|
+
isRequired ? this.getLabel()?.classList.add("required-field") : this.getLabel()?.classList.remove("required-field");
|
|
1032
|
+
return this;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Executes a callback function once the element is fully loaded.
|
|
1037
|
+
* If the element is already loaded, the callback is called immediately.
|
|
1038
|
+
* Otherwise, a MutationObserver is used to detect when the element is added to the DOM.
|
|
1039
|
+
* @param callback A callback function to execute once the element is loaded.
|
|
1040
|
+
* Receives instance of 'this' as an argument
|
|
1041
|
+
*/
|
|
1042
|
+
onceLoaded(callback) {
|
|
1043
|
+
if (this.isLoaded) {
|
|
1044
|
+
callback(this);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (this.target instanceof HTMLElement) {
|
|
1048
|
+
callback(this);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const observer = new MutationObserver(() => {
|
|
1052
|
+
if (document.querySelector(this.target)) {
|
|
1053
|
+
observer.disconnect();
|
|
1054
|
+
this.isLoaded = true;
|
|
1055
|
+
callback(this);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
observer.observe(document.body, {
|
|
1059
|
+
subtree: true,
|
|
1060
|
+
childList: true
|
|
1061
|
+
});
|
|
1062
|
+
this[observers].push(observer);
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
// src/core/DOMNodeReferenceArray.ts
|
|
1067
|
+
var DOMNodeReferenceArray = class extends Array {
|
|
1068
|
+
/**
|
|
1069
|
+
* Hides all the containers of the DOMNodeReference instances in the array.
|
|
1070
|
+
*/
|
|
1071
|
+
hideAll() {
|
|
1072
|
+
this.forEach((instance) => instance.hide());
|
|
1073
|
+
return this;
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Shows all the containers of the DOMNodeReference instances in the array.
|
|
1077
|
+
*/
|
|
1078
|
+
showAll() {
|
|
1079
|
+
this.forEach((instance) => instance.show());
|
|
1080
|
+
return this;
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
// src/utils/enhanceArray.ts
|
|
1085
|
+
function enhanceArray(array) {
|
|
1086
|
+
const enhancedArray = new DOMNodeReferenceArray(...array);
|
|
1087
|
+
return new Proxy(enhancedArray, {
|
|
1088
|
+
get(target, prop, receiver) {
|
|
1089
|
+
if (prop in target) {
|
|
1090
|
+
return Reflect.get(target, prop, receiver);
|
|
1091
|
+
}
|
|
1092
|
+
if (typeof prop === "string") {
|
|
1093
|
+
return target.find(
|
|
1094
|
+
(instance) => instance.target.toString().replace(/[#\[\]]/g, "") === prop || instance.logicalName === prop
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/core/createDOMNodeReferences.ts
|
|
1103
|
+
async function createDOMNodeReference(target, options = {
|
|
1104
|
+
multiple: false,
|
|
1105
|
+
root: document.body,
|
|
1106
|
+
timeoutMs: 0
|
|
1107
|
+
}) {
|
|
1108
|
+
try {
|
|
1109
|
+
if (typeof options !== "object") {
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`'options' must be of type 'object'. Received type: '${typeof options}'`
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
validateOptions(options);
|
|
1115
|
+
const { multiple = false, root = document.body, timeoutMs = 0 } = options;
|
|
1116
|
+
const isMultiple = typeof multiple === "function" ? multiple() : multiple;
|
|
1117
|
+
if (isMultiple) {
|
|
1118
|
+
if (typeof target !== "string") {
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
`'target' must be of type 'string' if 'multiple' is set to 'true'. Received type: '${typeof target}'`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
const elements = await waitFor(target, root, true, timeoutMs);
|
|
1124
|
+
const initializedElements = await Promise.all(
|
|
1125
|
+
elements.map(async (element) => {
|
|
1126
|
+
const instance2 = new DOMNodeReference(element, root, timeoutMs);
|
|
1127
|
+
await instance2[init]();
|
|
1128
|
+
return new Proxy(instance2, createProxyHandler());
|
|
1129
|
+
})
|
|
1130
|
+
);
|
|
1131
|
+
return enhanceArray(initializedElements);
|
|
1132
|
+
}
|
|
1133
|
+
const instance = new DOMNodeReference(target, root, timeoutMs);
|
|
1134
|
+
await instance[init]();
|
|
1135
|
+
return new Proxy(instance, createProxyHandler());
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
throw new Error(e);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function validateOptions(options) {
|
|
1141
|
+
const { multiple = false, root = document.body, timeoutMs = 0 } = options;
|
|
1142
|
+
if (typeof multiple !== "boolean" && typeof multiple !== "function") {
|
|
1143
|
+
throw new Error(
|
|
1144
|
+
`'multiple' must be of type 'boolean' or 'function'. Received type: '${typeof multiple}'`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
if (typeof multiple === "function") {
|
|
1148
|
+
const value = multiple();
|
|
1149
|
+
if (typeof value !== "boolean") {
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
`'multiple' function must return a boolean. Received type: '${typeof value}'`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (!(root instanceof HTMLElement)) {
|
|
1156
|
+
throw new Error(
|
|
1157
|
+
`'root' must be of type 'HTMLElement'. Received type: '${typeof root}'`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
if (typeof timeoutMs !== "number") {
|
|
1161
|
+
throw new Error(
|
|
1162
|
+
`'timeout' must be of type 'number'. Received type: '${typeof timeoutMs}'`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
function createProxyHandler() {
|
|
1168
|
+
return {
|
|
1169
|
+
get: (target, prop) => {
|
|
1170
|
+
if (prop.toString().startsWith("_")) return void 0;
|
|
1171
|
+
const value = target[prop];
|
|
1172
|
+
if (typeof value === "function" && prop !== "onceLoaded") {
|
|
1173
|
+
return (...args) => {
|
|
1174
|
+
target.onceLoaded(() => value.apply(target, args));
|
|
1175
|
+
return target;
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
return value;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// src/core/bindForm.ts
|
|
1184
|
+
async function bindForm(formId) {
|
|
1185
|
+
try {
|
|
1186
|
+
const form = await API_default.getRecord("systemforms", formId);
|
|
1187
|
+
const { formxml } = form;
|
|
1188
|
+
const parser = new DOMParser();
|
|
1189
|
+
const xmlDoc = parser.parseFromString(formxml, "application/xml");
|
|
1190
|
+
const controls = processElements(xmlDoc.getElementsByTagName("control"));
|
|
1191
|
+
const sections = processElements(xmlDoc.getElementsByTagName("section"));
|
|
1192
|
+
const tabs = processElements(xmlDoc.getElementsByTagName("tab"));
|
|
1193
|
+
const resolvedRefs = await Promise.all([...controls, ...sections, ...tabs]);
|
|
1194
|
+
return enhanceArray(
|
|
1195
|
+
resolvedRefs.filter((ref) => ref !== null)
|
|
1196
|
+
);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
if (error instanceof Error) {
|
|
1199
|
+
console.error(error.message);
|
|
1200
|
+
throw error;
|
|
1201
|
+
} else {
|
|
1202
|
+
console.error(error);
|
|
1203
|
+
throw new Error(String(error));
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function processElements(element) {
|
|
1208
|
+
return Array.from(element).map((element2) => {
|
|
1209
|
+
const identifyingAttribute = getIdentifyingAttribute(element2.tagName);
|
|
1210
|
+
const datafieldname = element2.getAttribute(identifyingAttribute);
|
|
1211
|
+
if (!datafieldname) return null;
|
|
1212
|
+
const referenceString = createReferenceString(
|
|
1213
|
+
element2.tagName,
|
|
1214
|
+
datafieldname
|
|
1215
|
+
);
|
|
1216
|
+
if (!referenceString) return null;
|
|
1217
|
+
return createDOMNodeReference(referenceString).catch((error) => {
|
|
1218
|
+
console.warn(
|
|
1219
|
+
`Failed to create a reference to the form field: ${datafieldname}`,
|
|
1220
|
+
error
|
|
1221
|
+
);
|
|
1222
|
+
return null;
|
|
1223
|
+
});
|
|
1224
|
+
}).filter(Boolean);
|
|
1225
|
+
}
|
|
1226
|
+
function getIdentifyingAttribute(tagName) {
|
|
1227
|
+
return tagName === "control" ? "id" : tagName === "tab" || tagName === "section" ? "name" : "id";
|
|
1228
|
+
}
|
|
1229
|
+
function createReferenceString(tagName, datafieldname) {
|
|
1230
|
+
if (tagName === "control") return `#${datafieldname}`;
|
|
1231
|
+
if (tagName === "tab" || tagName === "section") {
|
|
1232
|
+
return `[data-name="${datafieldname}"]`;
|
|
1233
|
+
}
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
export {
|
|
1237
|
+
API_default as API,
|
|
1238
|
+
bindForm,
|
|
1239
|
+
createDOMNodeReference as createRef,
|
|
1240
|
+
waitFor
|
|
1241
|
+
};
|
|
1242
|
+
/*! For license information please see index.js.LEGAL.txt */
|
|
1243
|
+
//# sourceMappingURL=index.js.map
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
if (typeof document !== 'undefined') {
|
|
1247
|
+
const style = document.createElement('style');
|
|
1248
|
+
style.textContent = ".info-icon {\r\n position: relative;\r\n display: inline-block;\r\n}\r\n\r\n.info-icon .fa-info-circle {\r\n cursor: pointer; /* Ensures the icon is recognized as interactive */\r\n}\r\n\r\n.info-icon .flyout-content {\r\n max-width: calc(100vw - 20px); /* 20px margin on both sides */\r\n display: none; /* Initially hidden */\r\n position: absolute;\r\n left: 50%; /* Center horizontally */\r\n transform: translateX(-50%); /* Adjust positioning */\r\n color: #000;\r\n background-color: #f9f9f9;\r\n padding: 10px;\r\n border: 1px solid #ddd;\r\n z-index: 1;\r\n min-width: 200px; /* Minimum width for better readability */\r\n box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);\r\n border-radius: 4px; /* Rounded corners */\r\n}\r\n\r\n@media (max-width: 600px) {\r\n .info-icon .flyout-content {\r\n max-width: 95vw;\r\n padding: 12px;\r\n font-size: 0.9em;\r\n display: block;\r\n right: auto;\r\n }\r\n}\r\n\r\n.required-field::after {\r\n content: \" *\" !important;\r\n color: #f00 !important;\r\n}\r\n";
|
|
1249
|
+
document.head.appendChild(style);
|
|
1250
|
+
}
|
|
1251
|
+
|