powerpagestoolkit 2.6.2 → 2.6.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 CHANGED
@@ -27,31 +27,89 @@ A powerful class for managing DOM elements with automatic value synchronization
27
27
 
28
28
  #### Basic Usage
29
29
 
30
- DOMNodeReferences are instantiated with the help of the following factory function
30
+ DOMNodeReferences are instantiated with the help of the following factory function: `createRef`
31
31
 
32
32
  ```typescript
33
33
  createRef(
34
- target: HTMLElement | string, /* You can target an HTMLElement directly,
35
- or use standard querySelector syntax */
36
- multiple: (() => boolean) | boolean = false /* are you targeting a single
37
- element, or multiple? true = multiple. Default is false (single) */
38
- ): Promise<DOMNodeReference | DOMNodeReferenceArray>;
34
+ target: HTMLElement | string,
35
+ options: {
36
+ multiple: (() => boolean) | boolean = false,
37
+ root: HTMLElement,
38
+ timeout: number
39
+ }
40
+ ): Promise<DOMNodeReference | DOMNodeReference[]>;
39
41
  ```
40
42
 
43
+ createRef takes two main arguments:
44
+
45
+ <table style="width: 100%; border-collapse: collapse;">
46
+ <thead>
47
+ <tr>
48
+ <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Property</th>
49
+ <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Type</th>
50
+ <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Details</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ <tr>
55
+ <td style="border: 1px solid #ddd; padding: 8px;">target</td>
56
+ <td style="border: 1px solid #ddd; padding: 8px;">
57
+ <pre><code class="language-javascript">string | HTMLElement</code></pre>
58
+ </td>
59
+ <td style="border: 1px solid #ddd; padding: 8px;">
60
+ Use standard <code>querySelector</code> syntax to target an element, or elements in the DOM, or pass in an instance of the element itself to create a reference.
61
+ </td>
62
+ </tr>
63
+ <tr>
64
+ <td style="border: 1px solid #ddd; padding: 8px;">options</td>
65
+ <td style="border: 1px solid #ddd; padding: 8px;">
66
+ <pre><code class="language-javascript">{
67
+ multiple: () => boolean | boolean,
68
+ root: HTMLElement,
69
+ timeout: number
70
+ }</code></pre>
71
+ </td>
72
+ <td style="border: 1px solid #ddd; padding: 8px;">
73
+ Provides advanced configurations for niche scenarios, such as async DOM element loading, returning arrays of elements, or specifying the parent to search within for the target node.
74
+ </td>
75
+ </tr>
76
+ </tbody>
77
+ </table>
78
+
41
79
  Import the utility function for creating DOMNodeReference(s)
42
80
 
43
81
  ```typescript
44
82
  import { createRef } from "powerpagestoolkit";
45
83
  ```
46
84
 
47
- Instantiate one, or multiple instances of a DOMNodeReference
85
+ Instantiate one, or multiple instances of a DOMNodeReference, and optionally configure advanced options
48
86
 
49
- ```typescript
87
+ ```javascript
50
88
  // Create a single reference
51
- const node = await createRef("#myElement", false);
89
+ const node = await createRef("#myElement");
52
90
 
53
91
  // Create multiple references
54
- const nodes = await createRef(".my-class", true);
92
+ const nodes = await createRef(".my-class", { multiple: true });
93
+
94
+ /******************/
95
+ // ADVANCED OPTIONS
96
+ // in the event that you need to be more granular with how you are targeting
97
+ // and retrieving elements, there are additional options
98
+
99
+ // If the node you are targeting is not available at the initial execution
100
+ // of the script, set a timeout for 2 seconds
101
+ const node2 = await createRef("#target", { timeout: 2000 });
102
+
103
+ // need to target a node within a specific node? use that node as the root
104
+ const otherElement = document.getElementById("id");
105
+ const node3 = await createRef("#target", { root: otherElement });
106
+
107
+ // implement all options:
108
+ const nodes2 = await createRef("#target", {
109
+ multiple: true,
110
+ timeout: 4000,
111
+ root: otherElement,
112
+ });
55
113
  ```
56
114
 
57
115
  #### Properties
@@ -101,7 +159,7 @@ _Method signature:_
101
159
  ```typescript
102
160
  configureConditionalRendering(
103
161
  condition: () => boolean,
104
- dependencies?: Array<DOMNodeReference>,
162
+ dependencies?: DOMNodeReference[],
105
163
  clearValuesOnHide: boolean = true
106
164
  ): DOMNodeReference /* Instance of this returned
107
165
  for optional method chaining */
@@ -159,9 +217,9 @@ node.configureValidationAndRequirements(
159
217
 
160
218
  ##### Element Manipulation
161
219
 
162
- ```typescript
163
- /****/ Value management /****/
220
+ _Value management_
164
221
 
222
+ ```typescript
165
223
  // set a static value
166
224
  node.setValue("new value");
167
225
 
@@ -176,25 +234,31 @@ node.setValue(() => {
176
234
  node.updateValue();
177
235
 
178
236
  // Clear the value for both the instance and the target element
179
- node.clearValue()
237
+ node.clearValue();
238
+ ```
180
239
 
181
- /****/ Content manipulation /****/
240
+ _Content manipulation_
182
241
 
242
+ ```typescript
183
243
  node.setInnerHTML("<span>New content</span>");
184
244
  node.append(childElement);
185
245
  node.prepend(headerElement);
186
246
  node.after(siblingElement);
187
247
  node.before(labelElement);
248
+ ```
188
249
 
189
- /****/ Styling /****/
250
+ _Styling_
190
251
 
252
+ ```typescript
191
253
  node.setStyle({
192
254
  display: "block",
193
255
  color: "red",
194
256
  });
257
+ ```
195
258
 
196
- /****/ State management /****/
259
+ _Enabling/Disabling inputs_
197
260
 
261
+ ```typescript
198
262
  node.disable();
199
263
  node.enable();
200
264
 
package/dist/API.d.ts CHANGED
@@ -12,7 +12,7 @@ declare const API: {
12
12
  * @param selectColumns *OPTIONAL* if desired, enter your own custom OData query for advanced GET results. Format = select=column1,column2,column3...
13
13
  * @returns a Promise resolving the successful results of the GET request, or rejecting the failed results of the GET request
14
14
  */
15
- getRecord(tableSetName: string, recordID: string, selectColumns?: string): Promise<object>;
15
+ getRecord<T>(tableSetName: string, recordID: string, selectColumns?: string): Promise<T>;
16
16
  /**
17
17
  *
18
18
  * @param tableSetName The dataverse set name of the table being queried
@@ -1,3 +1,45 @@
1
+ interface BusinessRule {
2
+ /**
3
+ * @param condition A function that returns a boolean to determine
4
+ * the visibility of the target element. If `condition()` returns true, the element is shown;
5
+ * otherwise, it is hidden.
6
+ * @param dependencies An array of `DOMNodeReference` instances. Event listeners are
7
+ * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
8
+ * the target node.
9
+ * @param clearValuesOnHide Should the values in the targeted field be cleared when hidden? Defaults to true
10
+ */
11
+ setVisibility?: [
12
+ condition: () => boolean,
13
+ dependencies: DOMNodeReference[],
14
+ clearValuesOnHide?: boolean
15
+ ];
16
+ /**
17
+ * @param isRequired Function determining if field is required
18
+ * @param isValid Function validating field input
19
+ * @param fieldDisplayName Display name for error messages
20
+ * @param dependencies Fields that trigger requirement/validation updates
21
+ */
22
+ setRequired?: [
23
+ isRequired: () => boolean,
24
+ isValid: () => boolean,
25
+ fieldDisplayName: string,
26
+ dependencies: DOMNodeReference[]
27
+ ];
28
+ /**
29
+ * @param condition A function to determine the value of this
30
+ * element, given applied logic
31
+ * @param value The value to set for the HTML element.
32
+ * for parents of boolean radios, pass true or false as value, or
33
+ * an expression returning a boolean
34
+ */
35
+ setValue?: [condition: () => boolean, value: any];
36
+ /**
37
+ * @param condition A function to determine if this field
38
+ * should be enabled in a form, or disabled. True || 1 = disabled. False || 0 = enabled
39
+ * @param dependencies
40
+ */
41
+ setDisabled?: [condition: () => boolean, dependencies: DOMNodeReference[]];
42
+ }
1
43
  export declare const _init: unique symbol;
2
44
  declare const _destroy: unique symbol;
3
45
  declare const _valueSync: unique symbol;
@@ -7,12 +49,17 @@ declare const _updateRadioGroup: unique symbol;
7
49
  declare const _attachVisibilityController: unique symbol;
8
50
  declare const _attachRadioButtons: unique symbol;
9
51
  declare const _bindMethods: unique symbol;
52
+ declare const _debounceTime: unique symbol;
53
+ declare const _observers: unique symbol;
54
+ declare const _boundEventListeners: unique symbol;
10
55
  export default class DOMNodeReference {
11
- target: HTMLElement | string;
56
+ target: Element | string;
57
+ root: Element;
58
+ private [_debounceTime];
12
59
  private isLoaded;
13
60
  private defaultDisplay;
14
- private observers;
15
- private boundEventListeners;
61
+ private [_observers];
62
+ private [_boundEventListeners];
16
63
  /**
17
64
  * The value of the element that this node represents
18
65
  * stays in syncs with the live DOM elements?.,m via event handler
@@ -41,8 +88,10 @@ export default class DOMNodeReference {
41
88
  /**
42
89
  * Creates an instance of DOMNodeReference.
43
90
  * @param target - The CSS selector to find the desired DOM element.
91
+ * @param root - Optionally specify the element within to search for the element targeted by 'target'
92
+ * Defaults to 'document.body'
44
93
  */
45
- /******/ /******/ constructor(target: HTMLElement | string);
94
+ /******/ /******/ constructor(target: Element | string, root: Element | undefined, debounceTime: number);
46
95
  [_init](): Promise<void>;
47
96
  /**
48
97
  * Initializes value synchronization with appropriate event listeners
@@ -180,7 +229,7 @@ export default class DOMNodeReference {
180
229
  remove(): this;
181
230
  /**
182
231
  *
183
- * @param {Partial<CSSStyleDeclaration} options and object containing the styles you want to set : {key: value} e.g.: {'display': 'block'}
232
+ * @param options and object containing the styles you want to set : {key: value} e.g.: {'display': 'block'}
184
233
  * @returns - Instance of this [provides option to method chain]
185
234
  */
186
235
  setStyle(options: Partial<CSSStyleDeclaration>): this;
@@ -189,52 +238,59 @@ export default class DOMNodeReference {
189
238
  * @returns - Instance of this [provides option to method chain]
190
239
  */
191
240
  uncheckRadios(): DOMNodeReference;
241
+ /**
242
+ * Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
243
+ *
244
+ * @param rule The business rule containing conditions for various actions.
245
+ * @returns Instance of this for method chaining.
246
+ */
247
+ applyBusinessRule(rule: Partial<BusinessRule>): DOMNodeReference;
192
248
  /**
193
249
  * Configures conditional rendering for the target element based on a condition
194
250
  * and the visibility of one or more trigger elements.
195
- *
196
- * @param condition - A function that returns a boolean to determine
251
+ * @deprecated Use the new 'applyBusinessRule Method
252
+ * @param condition A function that returns a boolean to determine
197
253
  * the visibility of the target element. If `condition()` returns true, the element is shown;
198
254
  * otherwise, it is hidden.
199
255
  * @param dependencies - An array of `DOMNodeReference` instances. Event listeners are
200
256
  * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
201
257
  * the target node.
202
- * @throws - When there's an error in setting up conditional rendering
203
- * @returns - Instance of this [provides option to method chain]
258
+ * @throws When there's an error in setting up conditional rendering
259
+ * @returns Instance of this [provides option to method chain]
204
260
  */
205
261
  configureConditionalRendering(condition: () => boolean, dependencies?: Array<DOMNodeReference>, clearValuesOnHide?: boolean): DOMNodeReference;
206
262
  /**
207
263
  * Sets up validation and requirement rules for the field with enhanced error handling and dynamic updates.
208
- *
209
- * @param isRequired - Function determining if field is required
210
- * @param isValid - Function validating field input
211
- * @param fieldDisplayName - Display name for error messages
212
- * @param dependencies - Fields that trigger requirement/validation updates
213
- * @returns - Instance of this
214
- * @throws - If validation setup fails
264
+ * @deprecated Use the new 'applyBusinessRule Method
265
+ * @param isRequired Function determining if field is required
266
+ * @param isValid Function validating field input
267
+ * @param fieldDisplayName Display name for error messages
268
+ * @param dependencies Fields that trigger requirement/validation updates
269
+ * @returns Instance of this
270
+ * @throws If validation setup fails
215
271
  */
216
272
  configureValidationAndRequirements(isRequired: () => boolean, isValid: () => boolean, fieldDisplayName: string, dependencies: Array<DOMNodeReference>): DOMNodeReference;
217
273
  /**
218
274
  * Sets up tracking for dependencies using both event listeners and mutation observers.
219
275
  * @private
220
- * @param handler - The function to execute when dependencies change
221
- * @param dependencies - Array of dependent DOM nodes to track
222
- * @param options - Additional configuration options
276
+ * @param handler The function to execute when dependencies change
277
+ * @param dependencies Array of dependent DOM nodes to track
278
+ * @param options Additional configuration options
223
279
  */
224
280
  private _configDependencyTracking;
225
281
  /**
226
282
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
227
283
  *
228
- * @param isRequired - Determines whether the field should be marked as required.
284
+ * @param isRequired Determines whether the field should be marked as required.
229
285
  * If true, the "required-field" class is added to the label; if false, it is removed.
230
- * @returns - Instance of this [provides option to method chain]
286
+ * @returns Instance of this [provides option to method chain]
231
287
  */
232
288
  setRequiredLevel(isRequired: (() => boolean) | boolean): DOMNodeReference;
233
289
  /**
234
290
  * Executes a callback function once the element is fully loaded.
235
291
  * If the element is already loaded, the callback is called immediately.
236
292
  * Otherwise, a MutationObserver is used to detect when the element is added to the DOM.
237
- * @param callback - A callback function to execute once the element is loaded.
293
+ * @param callback A callback function to execute once the element is loaded.
238
294
  * Receives instance of 'this' as an argument
239
295
  */
240
296
  onceLoaded(callback: (instance: DOMNodeReference) => any): any;
@@ -0,0 +1,12 @@
1
+ import DOMNodeReference from "./DOMNodeReference.js";
2
+ export declare class DOMNodeReferenceArray extends Array<DOMNodeReference> {
3
+ /**
4
+ * Hides all the containers of the DOMNodeReference instances in the array.
5
+ */
6
+ hideAll(this: DOMNodeReferenceArray): DOMNodeReferenceArray;
7
+ /**
8
+ * Shows all the containers of the DOMNodeReference instances in the array.
9
+ */
10
+ showAll(this: DOMNodeReferenceArray): DOMNodeReferenceArray;
11
+ }
12
+ export declare function enhanceArray<T extends string>(array: DOMNodeReference[]): DOMNodeReferenceArray & Record<T, DOMNodeReference>;
package/dist/List.d.ts CHANGED
@@ -20,6 +20,7 @@ interface ListOptions {
20
20
  interface ListItem extends Array<Element> {
21
21
  }
22
22
  export default class List {
23
+ [x: symbol]: () => Promise<List>;
23
24
  items: ListItem[];
24
25
  private options;
25
26
  private container;
@@ -0,0 +1,11 @@
1
+ import DOMNodeReference from "./DOMNodeReference.js";
2
+ import { DOMNodeReferenceArray } from "./DOMNodeReferenceArray.js";
3
+ /**
4
+ * @function
5
+ * Get all controls related to the form for manipulating with the
6
+ * DOMNodeReference class. Rather than having to instantiate each fields that you need manually,
7
+ * you can call this method once with the form ID and gain access to all fields
8
+ * @param formId The string GUID of the form you want to bind to
9
+ * @returns An array of DOMNodeReferences
10
+ */
11
+ export default function bindForm<T extends string>(formId: string): Promise<DOMNodeReferenceArray & Record<T, DOMNodeReference>>;
package/dist/bundle.js CHANGED
@@ -102,36 +102,110 @@ var API = {
102
102
  };
103
103
  var API_default = API;
104
104
 
105
+ // src/DOMNodeReferenceArray.ts
106
+ var DOMNodeReferenceArray = class extends Array {
107
+ /**
108
+ * Hides all the containers of the DOMNodeReference instances in the array.
109
+ */
110
+ hideAll() {
111
+ this.forEach((instance) => instance.hide());
112
+ return this;
113
+ }
114
+ /**
115
+ * Shows all the containers of the DOMNodeReference instances in the array.
116
+ */
117
+ showAll() {
118
+ this.forEach((instance) => instance.show());
119
+ return this;
120
+ }
121
+ };
122
+ function enhanceArray(array) {
123
+ const enhancedArray = new DOMNodeReferenceArray(...array);
124
+ return new Proxy(enhancedArray, {
125
+ get(target, prop, receiver) {
126
+ if (prop in target) {
127
+ return Reflect.get(target, prop, receiver);
128
+ }
129
+ if (typeof prop === "string") {
130
+ return target.find((instance) => instance.element.id === prop);
131
+ }
132
+ return void 0;
133
+ }
134
+ });
135
+ }
136
+
105
137
  // src/waitFor.ts
106
- function waitFor(target, root = document) {
138
+ function waitFor(target, root = document, multiple = false, debounceTime) {
107
139
  return new Promise((resolve, reject) => {
108
- const observer = new MutationObserver(() => {
109
- const observedElement = root.querySelector(target);
110
- if (observedElement) {
140
+ if (multiple) {
141
+ let timeout;
142
+ const observedElements = [];
143
+ const observedSet = /* @__PURE__ */ new Set();
144
+ if (debounceTime < 1) {
145
+ return resolve(
146
+ Array.from(root.querySelectorAll(target))
147
+ );
148
+ }
149
+ const observer = new MutationObserver(() => {
150
+ const found = Array.from(root.querySelectorAll(target));
151
+ found.forEach((element) => {
152
+ if (!observedSet.has(element)) {
153
+ observedSet.add(element);
154
+ observedElements.push(element);
155
+ }
156
+ });
111
157
  clearTimeout(timeout);
158
+ timeout = setTimeout(() => {
159
+ if (observedElements.length > 0) {
160
+ observer.disconnect();
161
+ resolve(observedElements);
162
+ } else {
163
+ reject(
164
+ new Error(
165
+ `No elements found with target: "${target}" within ${debounceTime / 1e3} seconds. If the element you are expecting has not loaded yet, consider raising your timeout.`
166
+ )
167
+ );
168
+ }
169
+ }, debounceTime);
170
+ });
171
+ observer.observe(root, {
172
+ childList: true,
173
+ subtree: true,
174
+ attributes: false
175
+ });
176
+ } else {
177
+ const observer = new MutationObserver(() => {
178
+ const observedElement = root.querySelector(target);
179
+ if (observedElement) {
180
+ clearTimeout(timeout);
181
+ observer.disconnect();
182
+ resolve(observedElement);
183
+ }
184
+ });
185
+ const timeout = setTimeout(() => {
112
186
  observer.disconnect();
113
- resolve(observedElement);
187
+ reject(
188
+ new Error(
189
+ `Element not found by target: "${target}" within ${debounceTime / 1e3} second. If the element you are expecting has not loaded yet, consider raising your timeout.`
190
+ )
191
+ );
192
+ }, debounceTime);
193
+ if (target instanceof HTMLElement) {
194
+ clearTimeout(timeout);
195
+ return resolve(target);
114
196
  }
115
- });
116
- const timeout = setTimeout(() => {
117
- observer.disconnect();
118
- reject(new Error(`Element not found: ${target} within 5 seconds`));
119
- }, 5e3);
120
- if (target instanceof HTMLElement) {
121
- clearTimeout(timeout);
122
- return resolve(target);
123
- }
124
- const element = root.querySelector(target);
125
- if (element) {
126
- clearTimeout(timeout);
127
- return resolve(element);
197
+ const element = root.querySelector(target);
198
+ if (element) {
199
+ clearTimeout(timeout);
200
+ return resolve(element);
201
+ }
202
+ observer.observe(root, {
203
+ subtree: true,
204
+ attributes: true,
205
+ childList: true
206
+ // Detects added/removed child elements
207
+ });
128
208
  }
129
- observer.observe(document.body, {
130
- subtree: true,
131
- attributes: true,
132
- childList: true
133
- // Detects added/removed child elements
134
- });
135
209
  });
136
210
  }
137
211
 
@@ -237,13 +311,18 @@ var _updateRadioGroup = Symbol("_URG");
237
311
  var _attachVisibilityController = Symbol("_AVC");
238
312
  var _attachRadioButtons = Symbol("_ARB");
239
313
  var _bindMethods = Symbol("_B");
314
+ var _debounceTime = Symbol("DT");
315
+ var _observers = Symbol("O");
316
+ var _boundEventListeners = Symbol("BEV");
240
317
  var DOMNodeReference = class _DOMNodeReference {
241
318
  // properties initialized in the constructor
242
319
  target;
320
+ root;
321
+ [_debounceTime];
243
322
  isLoaded;
244
323
  defaultDisplay;
245
- observers = [];
246
- boundEventListeners = [];
324
+ [_observers] = [];
325
+ [_boundEventListeners] = [];
247
326
  /**
248
327
  * The value of the element that this node represents
249
328
  * stays in syncs with the live DOM elements?.,m via event handler
@@ -252,11 +331,15 @@ var DOMNodeReference = class _DOMNodeReference {
252
331
  /**
253
332
  * Creates an instance of DOMNodeReference.
254
333
  * @param target - The CSS selector to find the desired DOM element.
334
+ * @param root - Optionally specify the element within to search for the element targeted by 'target'
335
+ * Defaults to 'document.body'
255
336
  */
256
337
  /******/
257
338
  /******/
258
- constructor(target) {
339
+ constructor(target, root = document.body, debounceTime) {
259
340
  this.target = target;
341
+ this.root = root;
342
+ this[_debounceTime] = debounceTime;
260
343
  this.isLoaded = false;
261
344
  this.defaultDisplay = "";
262
345
  this.value = null;
@@ -264,8 +347,11 @@ var DOMNodeReference = class _DOMNodeReference {
264
347
  }
265
348
  async [_init]() {
266
349
  try {
267
- const element = await waitFor(this.target);
268
- this.element = element;
350
+ if (this.target instanceof HTMLElement) {
351
+ this.element = this.target;
352
+ } else {
353
+ this.element = await waitFor(this.target, this.root, false, this[_debounceTime]);
354
+ }
269
355
  if (!this.element) {
270
356
  throw new DOMNodeNotFoundError(this);
271
357
  }
@@ -302,30 +388,28 @@ var DOMNodeReference = class _DOMNodeReference {
302
388
  async [_valueSync]() {
303
389
  try {
304
390
  this.updateValue();
305
- if (!(this.element instanceof HTMLElement)) {
306
- throw new Error("Element is not a valid HTML element");
307
- }
308
391
  const eventMapping = {
309
392
  checkbox: "click",
310
393
  radio: "click",
311
394
  select: "change",
312
- "select-multiple": "change"
395
+ "select-multiple": "change",
396
+ textarea: "keyup"
313
397
  // Add other input types as needed
314
398
  };
315
399
  let eventType;
316
400
  if (this.element instanceof HTMLSelectElement) {
317
401
  eventType = "change";
318
402
  } else if (this.element instanceof HTMLInputElement) {
319
- eventType = eventMapping[this.element.type] || "input";
403
+ eventType = eventMapping[this.element.type] ?? "input";
404
+ } else if (this.element instanceof HTMLTextAreaElement) {
405
+ eventType = eventMapping[this.element.type] ?? "input";
320
406
  } else {
321
407
  eventType = "input";
322
408
  }
323
409
  this.element.addEventListener(eventType, this.updateValue);
324
- const _element = this.element;
325
- const _updateValue = this.updateValue;
326
- this.boundEventListeners.push({
327
- element: _element,
328
- handler: _updateValue,
410
+ this[_boundEventListeners].push({
411
+ element: this.element,
412
+ handler: this.updateValue,
329
413
  event: eventType
330
414
  });
331
415
  if (this.element instanceof HTMLInputElement && this.element.dataset.type === "date") {
@@ -343,10 +427,10 @@ var DOMNodeReference = class _DOMNodeReference {
343
427
  if (!parentElement) {
344
428
  throw new Error("Date input must have a parent element");
345
429
  }
346
- const dateNode = await waitFor("[data-date-format]", parentElement);
430
+ const dateNode = await waitFor("[data-date-format]", parentElement, false, 1500);
347
431
  dateNode.addEventListener("select", this.updateValue);
348
432
  const _handler = this.updateValue;
349
- this.boundEventListeners.push({
433
+ this[_boundEventListeners].push({
350
434
  element: dateNode,
351
435
  handler: _handler,
352
436
  event: "select"
@@ -427,7 +511,8 @@ var DOMNodeReference = class _DOMNodeReference {
427
511
  this.noRadio = await createDOMNodeReference(`#${this.element.id}_0`);
428
512
  }
429
513
  [_bindMethods]() {
430
- for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {
514
+ const prototype = Object.getPrototypeOf(this);
515
+ for (const key of Object.getOwnPropertyNames(prototype)) {
431
516
  const value = this[key];
432
517
  if (key !== "constructor" && typeof value === "function") {
433
518
  this[key] = value.bind(this);
@@ -435,10 +520,10 @@ var DOMNodeReference = class _DOMNodeReference {
435
520
  }
436
521
  }
437
522
  [_destroy]() {
438
- this.boundEventListeners?.forEach((binding) => {
523
+ this[_boundEventListeners]?.forEach((binding) => {
439
524
  binding.element?.removeEventListener(binding.event, binding.handler);
440
525
  });
441
- this.observers?.forEach((observer) => {
526
+ this[_observers]?.forEach((observer) => {
442
527
  observer.disconnect();
443
528
  });
444
529
  this.yesRadio?.[_destroy]();
@@ -477,7 +562,7 @@ var DOMNodeReference = class _DOMNodeReference {
477
562
  this.element.addEventListener(eventType, eventHandler.bind(this));
478
563
  const _element = this.element;
479
564
  const _handler = eventHandler;
480
- this.boundEventListeners.push({
565
+ this[_boundEventListeners].push({
481
566
  element: _element,
482
567
  handler: _handler,
483
568
  event: eventType
@@ -525,7 +610,7 @@ var DOMNodeReference = class _DOMNodeReference {
525
610
  if (value instanceof Function) {
526
611
  value = value();
527
612
  }
528
- if (this.element.classList.contains("boolean-radio")) {
613
+ if (this.element.classList.contains("boolean-radio") && this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
529
614
  this.yesRadio.element.checked = value;
530
615
  this.noRadio.element.checked = !value;
531
616
  } else {
@@ -596,7 +681,7 @@ var DOMNodeReference = class _DOMNodeReference {
596
681
  );
597
682
  if (childInputs.length > 0) {
598
683
  const promises = childInputs.map(async (input) => {
599
- const inputRef = await createDOMNodeReference(input, false);
684
+ const inputRef = await createDOMNodeReference(input, { multiple: false });
600
685
  return inputRef.clearValue();
601
686
  });
602
687
  await Promise.all(promises);
@@ -716,7 +801,7 @@ var DOMNodeReference = class _DOMNodeReference {
716
801
  }
717
802
  /**
718
803
  *
719
- * @param {Partial<CSSStyleDeclaration} options and object containing the styles you want to set : {key: value} e.g.: {'display': 'block'}
804
+ * @param options and object containing the styles you want to set : {key: value} e.g.: {'display': 'block'}
720
805
  * @returns - Instance of this [provides option to method chain]
721
806
  */
722
807
  setStyle(options) {
@@ -725,7 +810,8 @@ var DOMNodeReference = class _DOMNodeReference {
725
810
  `powerpagestoolkit: 'DOMNodeReference.setStyle' required options to be in the form of an object. Argument passed was of type: ${typeof options}`
726
811
  );
727
812
  }
728
- for (const key in options) {
813
+ for (const _key in options) {
814
+ const key = _key;
729
815
  this.element.style[key] = options[key];
730
816
  }
731
817
  return this;
@@ -745,18 +831,106 @@ var DOMNodeReference = class _DOMNodeReference {
745
831
  }
746
832
  return this;
747
833
  }
834
+ /**
835
+ * Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
836
+ *
837
+ * @param rule The business rule containing conditions for various actions.
838
+ * @returns Instance of this for method chaining.
839
+ */
840
+ applyBusinessRule(rule) {
841
+ try {
842
+ if (rule.setVisibility) {
843
+ const [condition, dependencies = [], clearValuesOnHide = true] = rule.setVisibility;
844
+ const initialState = condition();
845
+ this.toggleVisibility(initialState);
846
+ if (dependencies.length) {
847
+ this._configDependencyTracking(
848
+ () => this.toggleVisibility(condition()),
849
+ dependencies,
850
+ {
851
+ clearValuesOnHide,
852
+ observeVisibility: true,
853
+ trackInputEvents: false,
854
+ trackRadioButtons: false
855
+ }
856
+ );
857
+ }
858
+ }
859
+ if (rule.setRequired) {
860
+ const [isRequired, isValid, fieldDisplayName, dependencies] = rule.setRequired;
861
+ if (!fieldDisplayName.trim()) {
862
+ throw new ValidationConfigError(
863
+ this,
864
+ "Field display name is required"
865
+ );
866
+ }
867
+ if (typeof Page_Validators === "undefined") {
868
+ throw new ValidationConfigError(this, "Page_Validators not found");
869
+ }
870
+ const validatorId = `${this.element.id}Validator`;
871
+ const newValidator = document.createElement("span");
872
+ newValidator.style.display = "none";
873
+ newValidator.id = validatorId;
874
+ Object.assign(newValidator, {
875
+ controltovalidate: this.element.id,
876
+ errormessage: `<a href='#${this.element.id}_label'>${fieldDisplayName} is a required field</a>`,
877
+ evaluationfunction: () => {
878
+ const isFieldRequired = isRequired();
879
+ const isFieldVisible = window.getComputedStyle(this.visibilityController).display !== "none";
880
+ return !isFieldRequired || !isFieldVisible || isValid();
881
+ }
882
+ });
883
+ Page_Validators.push(newValidator);
884
+ this.setRequiredLevel(isRequired());
885
+ this._configDependencyTracking(
886
+ () => this.setRequiredLevel(isRequired()),
887
+ dependencies
888
+ );
889
+ }
890
+ if (rule.setValue) {
891
+ const [condition, value] = rule.setValue;
892
+ if (condition()) {
893
+ this.setValue(value);
894
+ }
895
+ }
896
+ if (rule.setDisabled) {
897
+ const [condition, dependencies = []] = rule.setDisabled;
898
+ condition() ? this.disable() : this.enable();
899
+ const initialState = condition();
900
+ if (dependencies.length) {
901
+ this._configDependencyTracking(
902
+ () => {
903
+ condition() ? this.enable() : this.disable();
904
+ },
905
+ dependencies,
906
+ {
907
+ observeVisibility: true,
908
+ trackInputEvents: true,
909
+ trackRadioButtons: true
910
+ }
911
+ );
912
+ }
913
+ }
914
+ return this;
915
+ } catch (error) {
916
+ throw new ValidationConfigError(
917
+ this,
918
+ `Failed to apply business rule: ${error}`
919
+ );
920
+ }
921
+ }
748
922
  /**
749
923
  * Configures conditional rendering for the target element based on a condition
750
924
  * and the visibility of one or more trigger elements.
751
- *
752
- * @param condition - A function that returns a boolean to determine
925
+ * @deprecated Use the new 'applyBusinessRule Method
926
+ * @param condition A function that returns a boolean to determine
753
927
  * the visibility of the target element. If `condition()` returns true, the element is shown;
754
928
  * otherwise, it is hidden.
755
929
  * @param dependencies - An array of `DOMNodeReference` instances. Event listeners are
756
930
  * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
757
931
  * the target node.
758
- * @throws - When there's an error in setting up conditional rendering
759
- * @returns - Instance of this [provides option to method chain]
932
+ * @throws When there's an error in setting up conditional rendering
933
+ * @returns Instance of this [provides option to method chain]
760
934
  */
761
935
  configureConditionalRendering(condition, dependencies, clearValuesOnHide = true) {
762
936
  try {
@@ -790,13 +964,13 @@ var DOMNodeReference = class _DOMNodeReference {
790
964
  }
791
965
  /**
792
966
  * Sets up validation and requirement rules for the field with enhanced error handling and dynamic updates.
793
- *
794
- * @param isRequired - Function determining if field is required
795
- * @param isValid - Function validating field input
796
- * @param fieldDisplayName - Display name for error messages
797
- * @param dependencies - Fields that trigger requirement/validation updates
798
- * @returns - Instance of this
799
- * @throws - If validation setup fails
967
+ * @deprecated Use the new 'applyBusinessRule Method
968
+ * @param isRequired Function determining if field is required
969
+ * @param isValid Function validating field input
970
+ * @param fieldDisplayName Display name for error messages
971
+ * @param dependencies Fields that trigger requirement/validation updates
972
+ * @returns Instance of this
973
+ * @throws If validation setup fails
800
974
  */
801
975
  configureValidationAndRequirements(isRequired, isValid, fieldDisplayName, dependencies) {
802
976
  if (!fieldDisplayName?.trim()) {
@@ -845,9 +1019,9 @@ var DOMNodeReference = class _DOMNodeReference {
845
1019
  /**
846
1020
  * Sets up tracking for dependencies using both event listeners and mutation observers.
847
1021
  * @private
848
- * @param handler - The function to execute when dependencies change
849
- * @param dependencies - Array of dependent DOM nodes to track
850
- * @param options - Additional configuration options
1022
+ * @param handler The function to execute when dependencies change
1023
+ * @param dependencies Array of dependent DOM nodes to track
1024
+ * @param options Additional configuration options
851
1025
  */
852
1026
  _configDependencyTracking(handler, dependencies, options = {
853
1027
  clearValuesOnHide: false,
@@ -880,14 +1054,14 @@ var DOMNodeReference = class _DOMNodeReference {
880
1054
  }
881
1055
  };
882
1056
  dep.on("change", handleChange);
883
- this.boundEventListeners.push({
1057
+ this[_boundEventListeners].push({
884
1058
  element: dep.element,
885
1059
  event: "change",
886
1060
  handler: handleChange
887
1061
  });
888
1062
  if (trackInputEvents) {
889
1063
  dep.on("input", handleChange);
890
- this.boundEventListeners.push({
1064
+ this[_boundEventListeners].push({
891
1065
  element: dep.element,
892
1066
  event: "input",
893
1067
  handler: handleChange
@@ -907,12 +1081,12 @@ var DOMNodeReference = class _DOMNodeReference {
907
1081
  attributeFilter: ["style"],
908
1082
  subtree: false
909
1083
  });
910
- this.observers.push(observer);
1084
+ this[_observers].push(observer);
911
1085
  }
912
1086
  if (trackRadioButtons && dep.yesRadio && dep.noRadio) {
913
1087
  [dep.yesRadio, dep.noRadio].forEach((radio) => {
914
1088
  radio.on("change", handleChange);
915
- this.boundEventListeners.push({
1089
+ this[_boundEventListeners].push({
916
1090
  element: radio.element,
917
1091
  event: "change",
918
1092
  handler: handleChange
@@ -924,9 +1098,9 @@ var DOMNodeReference = class _DOMNodeReference {
924
1098
  /**
925
1099
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
926
1100
  *
927
- * @param isRequired - Determines whether the field should be marked as required.
1101
+ * @param isRequired Determines whether the field should be marked as required.
928
1102
  * If true, the "required-field" class is added to the label; if false, it is removed.
929
- * @returns - Instance of this [provides option to method chain]
1103
+ * @returns Instance of this [provides option to method chain]
930
1104
  */
931
1105
  setRequiredLevel(isRequired) {
932
1106
  if (isRequired instanceof Function) {
@@ -941,7 +1115,7 @@ var DOMNodeReference = class _DOMNodeReference {
941
1115
  * Executes a callback function once the element is fully loaded.
942
1116
  * If the element is already loaded, the callback is called immediately.
943
1117
  * Otherwise, a MutationObserver is used to detect when the element is added to the DOM.
944
- * @param callback - A callback function to execute once the element is loaded.
1118
+ * @param callback A callback function to execute once the element is loaded.
945
1119
  * Receives instance of 'this' as an argument
946
1120
  */
947
1121
  onceLoaded(callback) {
@@ -964,13 +1138,24 @@ var DOMNodeReference = class _DOMNodeReference {
964
1138
  subtree: true,
965
1139
  childList: true
966
1140
  });
967
- this.observers.push(observer);
1141
+ this[_observers].push(observer);
968
1142
  }
969
1143
  };
970
1144
 
971
1145
  // src/createDOMNodeReferences.ts
972
- async function createDOMNodeReference(target, multiple = false) {
1146
+ async function createDOMNodeReference(target, options = {
1147
+ multiple: false,
1148
+ root: document.body,
1149
+ timeout: 0
1150
+ }) {
973
1151
  try {
1152
+ if (typeof options !== "object") {
1153
+ throw new Error(
1154
+ `'options' must be of type 'object'. Received type: '${typeof options}'`
1155
+ );
1156
+ }
1157
+ validateOptions(options);
1158
+ const { multiple = false, root = document.body, timeout = 0 } = options;
974
1159
  const isMultiple = typeof multiple === "function" ? multiple() : multiple;
975
1160
  if (isMultiple) {
976
1161
  if (typeof target !== "string") {
@@ -978,25 +1163,50 @@ async function createDOMNodeReference(target, multiple = false) {
978
1163
  `'target' must be of type 'string' if 'multiple' is set to 'true'. Received type: '${typeof target}'`
979
1164
  );
980
1165
  }
981
- const elements = Array.from(
982
- document.querySelectorAll(target)
983
- );
1166
+ const elements = await waitFor(target, root, true, timeout);
984
1167
  const initializedElements = await Promise.all(
985
1168
  elements.map(async (element) => {
986
- const instance2 = new DOMNodeReference(element);
1169
+ const instance2 = new DOMNodeReference(element, root, timeout);
987
1170
  await instance2[_init]();
988
1171
  return new Proxy(instance2, createProxyHandler());
989
1172
  })
990
1173
  );
991
1174
  return enhanceArray(initializedElements);
992
1175
  }
993
- const instance = new DOMNodeReference(target);
1176
+ const instance = new DOMNodeReference(target, root, timeout);
994
1177
  await instance[_init]();
995
1178
  return new Proxy(instance, createProxyHandler());
996
1179
  } catch (e) {
997
1180
  throw new Error(e);
998
1181
  }
999
1182
  }
1183
+ function validateOptions(options) {
1184
+ const { multiple = false, root = document.body, timeout = 0 } = options;
1185
+ if (typeof multiple !== "boolean" && typeof multiple !== "function") {
1186
+ throw new Error(
1187
+ `'multiple' must be of type 'boolean' or 'function'. Received type: '${typeof multiple}'`
1188
+ );
1189
+ }
1190
+ if (typeof multiple === "function") {
1191
+ const value = multiple();
1192
+ if (typeof value !== "boolean") {
1193
+ throw new Error(
1194
+ `'multiple' function must return a boolean. Received type: '${typeof value}'`
1195
+ );
1196
+ }
1197
+ }
1198
+ if (!(root instanceof HTMLElement)) {
1199
+ throw new Error(
1200
+ `'root' must be of type 'HTMLElement'. Received type: '${typeof root}'`
1201
+ );
1202
+ }
1203
+ if (typeof timeout !== "number") {
1204
+ throw new Error(
1205
+ `'timeout' must be of type 'number'. Received type: '${typeof timeout}'`
1206
+ );
1207
+ }
1208
+ return;
1209
+ }
1000
1210
  function createProxyHandler() {
1001
1211
  return {
1002
1212
  get: (target, prop) => {
@@ -1012,26 +1222,52 @@ function createProxyHandler() {
1012
1222
  }
1013
1223
  };
1014
1224
  }
1015
- function enhanceArray(array) {
1016
- Object.defineProperties(array, {
1017
- hideAll: {
1018
- value: function() {
1019
- this.forEach((instance) => instance.hide());
1020
- return this;
1021
- }
1022
- },
1023
- showAll: {
1024
- value: function() {
1025
- this.forEach((instance) => instance.show());
1026
- return this;
1225
+
1226
+ // src/bindForm.ts
1227
+ async function bindForm(formId) {
1228
+ try {
1229
+ const form = await API_default.getRecord("systemforms", formId);
1230
+ const { formxml } = form;
1231
+ const parser = new DOMParser();
1232
+ const xmlDoc = parser.parseFromString(formxml, "application/xml");
1233
+ const controls = xmlDoc.getElementsByTagName("control");
1234
+ const dataFields = [];
1235
+ for (let i = 0; i < controls.length; i++) {
1236
+ const datafieldname = controls[i].getAttribute("datafieldname");
1237
+ if (datafieldname) {
1238
+ const refPromise = createDOMNodeReference(`#${datafieldname}`).catch((error) => {
1239
+ console.warn(
1240
+ `Failed to create a reference to the form field: ${datafieldname}`,
1241
+ error
1242
+ );
1243
+ return null;
1244
+ });
1245
+ dataFields.push(refPromise);
1027
1246
  }
1028
1247
  }
1029
- });
1030
- return array;
1248
+ const resolvedRefs = await Promise.all(dataFields);
1249
+ return enhanceArray(
1250
+ resolvedRefs.filter((ref) => ref !== null)
1251
+ );
1252
+ } catch (error) {
1253
+ if (error instanceof Error) {
1254
+ console.error(error.message);
1255
+ throw error;
1256
+ } else {
1257
+ console.error(error);
1258
+ throw new Error(String(error));
1259
+ }
1260
+ }
1031
1261
  }
1262
+
1263
+ // src/index.ts
1264
+ var toolkit = { API: API_default, createRef: createDOMNodeReference, waitFor, bindForm };
1265
+ var src_default = toolkit;
1032
1266
  export {
1033
1267
  API_default as API,
1268
+ bindForm,
1034
1269
  createDOMNodeReference as createRef,
1270
+ src_default as default,
1035
1271
  waitFor
1036
1272
  };
1037
1273
 
@@ -1,9 +1,36 @@
1
+ import { DOMNodeReferenceArray } from "./DOMNodeReferenceArray.js";
1
2
  import DOMNodeReference from "./DOMNodeReference.js";
2
- /**
3
- * Creates and initializes a DOMNodeReference instance.
4
- * @async
5
- * @param target - The CSS selector for the desired DOM element, or, optionally, the element itself for which to create a DOMNodeReference.
6
- * @param multiple Should this call return an array of instantiated references, or just a single? Defaults to false, returning a single instance
7
- * @returns A promise that resolves to a Proxy of the initialized DOMNodeReference instance.
8
- */
9
- export default function createDOMNodeReference(target: HTMLElement | string, multiple?: (() => boolean) | boolean): Promise<DOMNodeReference | DOMNodeReference[]>;
3
+ interface CreationOptions {
4
+ /**
5
+ * Should this call return an array of instantiated references, or just a single?
6
+ * Defaults to false, returning a single instance.
7
+ */
8
+ multiple?: (() => boolean) | boolean;
9
+ /**
10
+ * Optionally specify the element within which to search for the element targeted by 'target'.
11
+ * Defaults to 'document.body'.
12
+ */
13
+ root?: HTMLElement;
14
+ /**
15
+ * Optionally specify the amount of time that should be waited to find the targeted element before throwing an error.
16
+ * Useful for async DOM loading. Relies on MutationObserver.
17
+ * WARNING: Implementing multiple references with timeout can result in infinite loading.
18
+ */
19
+ timeout?: number;
20
+ }
21
+ export default function createDOMNodeReference(target: Element, options?: CreationOptions): Promise<DOMNodeReference>;
22
+ export default function createDOMNodeReference(target: string, options?: Omit<CreationOptions, "multiple"> & {
23
+ /**
24
+ * Should this call return an array of instantiated references, or just a single instance?
25
+ * Defaults to false, returning a single instance.
26
+ */
27
+ multiple?: false;
28
+ }): Promise<DOMNodeReference>;
29
+ export default function createDOMNodeReference(target: string, options?: Omit<CreationOptions, "multiple"> & {
30
+ /**
31
+ * Should this call return an array of instantiated references, or just a single instance?
32
+ * Defaults to false, returning a single instance.
33
+ */
34
+ multiple?: true;
35
+ }): Promise<DOMNodeReferenceArray>;
36
+ export {};
package/dist/index.d.ts CHANGED
@@ -2,4 +2,17 @@ import "./style.css";
2
2
  import API from "./API.js";
3
3
  import createRef from "./createDOMNodeReferences.js";
4
4
  import waitFor from "./waitFor.js";
5
- export { API, createRef, waitFor };
5
+ import bindForm from "./bindForm.js";
6
+ export { API, createRef, waitFor, bindForm };
7
+ declare const toolkit: {
8
+ API: {
9
+ createRecord(tableSetName: string, data: object): Promise<string>;
10
+ getRecord<T>(tableSetName: string, recordID: string, selectColumns?: string): Promise<T>;
11
+ getMultiple(tableSetName: string, queryParameters?: string): Promise<Array<object>>;
12
+ updateRecord(tableSetName: string, recordId: string, data: object): Promise<any>;
13
+ };
14
+ createRef: typeof createRef;
15
+ waitFor: typeof waitFor;
16
+ bindForm: typeof bindForm;
17
+ };
18
+ export default toolkit;
package/dist/waitFor.d.ts CHANGED
@@ -1,7 +1,2 @@
1
- /**
2
- *
3
- * @param target basic querySelector syntax to select an element
4
- * @param root optional parameter to replace document as the root from which to perform the node search
5
- * @returns
6
- */
7
- export default function waitFor(target: HTMLElement | string, root?: HTMLElement | Document): Promise<HTMLElement>;
1
+ export default function waitFor(target: Element | string, root: Element | Document, multiple: false, debounceTime: number): Promise<HTMLElement>;
2
+ export default function waitFor(target: Element | string, root: Element | Document, multiple: true, debounceTime: number): Promise<HTMLElement[]>;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "powerpagestoolkit",
3
- "version": "2.6.02",
3
+ "version": "2.6.4",
4
4
  "description": "Reference, manipulate, and engage with Power Pages sites through the nodes in the DOM; use a variety of custom methods that allow customizing your power pages site quicker and easier. ",
5
- "main": "./dist/bundle.js",
5
+ "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "scripts": {
8
8
  "typecheck": "tsc",
@@ -15,11 +15,13 @@
15
15
  "devDependencies": {
16
16
  "@babel/preset-env": "^7.25.8",
17
17
  "@types/node": "^22.8.6",
18
+ "@typescript-eslint/parser": "^8.20.0",
18
19
  "css-loader": "^7.1.2",
19
20
  "esbuild": "^0.24.0",
20
21
  "esbuild-css-modules-plugin": "^3.1.2",
21
22
  "eslint": "^8.57.1",
22
23
  "eslint-plugin-import": "^2.31.0",
24
+ "globals": "^15.14.0",
23
25
  "rimraf": "^6.0.1",
24
26
  "ts-loader": "^9.5.1",
25
27
  "typescript": "^5.6.3",