powerpagestoolkit 2.6.33311 → 2.7.1

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
@@ -95,7 +95,8 @@ const nodes = await createRef(".my-class", { multiple: true });
95
95
  // ADVANCED OPTIONS
96
96
  // in the event that you need to be more granular with how you are targeting
97
97
  // and retrieving elements, there are additional options
98
- // If the node you are targeted is not available at the initial execution
98
+
99
+ // If the node you are targeting is not available at the initial execution
99
100
  // of the script, set a timeout for 2 seconds
100
101
  const node2 = await createRef("#target", { timeout: 2000 });
101
102
 
@@ -141,76 +142,113 @@ node.on("click", function (e) {
141
142
  ...
142
143
  ```
143
144
 
144
- ##### Visibility Control
145
+ ##### Business Rule Application
145
146
 
146
- ```typescript
147
- // Basic visibility
148
- node.hide();
149
- node.show();
150
- ```
147
+ This utility provides a flexible way to dynamically control field visibility, requirement status, values, and enabled states based on dependencies within PowerPages forms.
151
148
 
152
- **_Advanced conditional rendering_**
149
+ _Method Signature:_
153
150
 
154
- Out of the box, Microsoft does not provide PowerPages developers the ability to hide or show fields or form elements based on the value of another field. This method allows such configurations
151
+ ```typescript
152
+ applyBusinessRule(
153
+ rule: IBusinessRule,
154
+ dependencies: DOMNodeReference[]
155
+ ): DOMNodeReference; /* Instance of this is returned for optional
156
+ method chaining */
157
+ ```
155
158
 
156
- _Method signature:_
159
+ **BusinessRule Definition**
157
160
 
158
161
  ```typescript
159
- configureConditionalRendering(
160
- condition: () => boolean,
161
- dependencies?: DOMNodeReference[],
162
- clearValuesOnHide: boolean = true
163
- ): DOMNodeReference /* Instance of this returned
164
- for optional method chaining */
162
+ interface IBusinessRule {
163
+ setVisibility?: [
164
+ condition: () => boolean,
165
+ clearValuesOnHide?: boolean = true
166
+ ];
167
+ setRequired?: [
168
+ isRequired: () => boolean,
169
+ isValid: () => boolean
170
+ ];
171
+ setValue?: [
172
+ condition: () => boolean,
173
+ value: () => any | any
174
+ ];
175
+ setDisabled?: () => boolean;
176
+ }
165
177
  ```
166
178
 
167
- _Example implementation:_
179
+ ##### Visibility Control
168
180
 
169
181
  ```typescript
170
- node.configureConditionalRendering(
171
- function () // Function to evaluate wether this node should be visible or not
182
+ // Show the 'taxIdField' only when
183
+ // 'businessTypeField' is set to 'Corporation' or 'LLC'
184
+ taxIdField.applyBusinessRule(
172
185
  {
173
- return otherNode.value === "some value";
186
+ setVisibility: [
187
+ () =>
188
+ businessTypeField.value === "Corporation" ||
189
+ businessTypeField.value === "LLC",
190
+ ],
174
191
  },
175
- [otherNode] /* Dependency array | if the values or visibility of these
176
- change, the function is re-evaluated */,
192
+ [businessTypeField] // Re-evaluate when businessTypeField changes
193
+ );
177
194
 
178
- true /* should the values in the targeted elements (this.element)
179
- be cleared if this node is hidden? Default = true */
195
+ // Optionally disable 'clearValuesOnHide:
196
+ taxIdField.applyBusinessRule(
197
+ {
198
+ setVisibility: [
199
+ () =>
200
+ businessTypeField.value === "Corporation" ||
201
+ businessTypeField.value === "LLC",
202
+ false, // defaults to true. False will prevent the fields from losing it's value if it is hidden
203
+ ],
204
+ },
205
+ [businessTypeField] // Re-evaluate when businessTypeField changes
180
206
  );
181
207
  ```
182
208
 
183
209
  ##### Validation and Requirements
184
210
 
185
- This utility enhances PowerPages forms by adding dynamic field validation and conditional requirements based on other field values.
186
-
187
- _Method signature:_
188
-
189
211
  ```typescript
190
- configureValidationAndRequirements(
191
- isRequired: () => boolean,
192
- isValid: () => boolean,
193
- fieldDisplayName: string,
194
- dependencies: DOMNodeReference[]
195
- ): DOMNodeReference; /* instance of this is returned for optional
196
- method chaining */
212
+ // Require 'taxIdField' when 'businessTypeField' is 'Corporation' or 'LLC'
213
+ taxIdField.applyBusinessRule(
214
+ {
215
+ setRequired: [
216
+ function () {
217
+ return (
218
+ businessTypeField.value === "Corporation" ||
219
+ businessTypeField.value === "LLC"
220
+ );
221
+ },
222
+ function () {
223
+ return this.value != null && this.value !== "";
224
+ },
225
+ ],
226
+ },
227
+ [businessTypeField] // Revalidate when businessTypeField changes
228
+ );
197
229
  ```
198
230
 
199
- _Example implementation:_
231
+ ##### Setting Field Values Conditionally
200
232
 
201
233
  ```typescript
202
- node.configureValidationAndRequirements(
203
- // Make field required only when "Yes" is checked
204
- () => dependentNode.yesRadio?.checked ?? false,
205
-
206
- // Basic validation: ensure field isn't empty
207
- function () {
208
- return this.value != null && this.value !== "";
234
+ // Set default industry value when 'businessTypeField' is 'Corporation'
235
+ industryField.applyBusinessRule(
236
+ {
237
+ setValue: [() => businessTypeField.value === "Corporation", "Corporate"],
209
238
  },
239
+ [businessTypeField] // Apply value when businessTypeField changes
240
+ );
241
+ ```
210
242
 
211
- "Contact Phone", // Shows in error message: "Contact Phone is required"
243
+ ##### Enabling and Disabling Fields
212
244
 
213
- [dependentNode] // Revalidate when dependentNode changes
245
+ ```typescript
246
+ // Disable 'taxIdField' when 'businessTypeField' is 'Individual'
247
+ taxIdField.applyBusinessRule(
248
+ {
249
+ setDisabled: [() => businessTypeField.value === "Individual"],
250
+ },
251
+ [businessTypeField] // Enable/disable when businessTypeField changes
214
252
  );
215
253
  ```
216
254
 
@@ -260,7 +298,6 @@ _Enabling/Disabling inputs_
260
298
  ```typescript
261
299
  node.disable();
262
300
  node.enable();
263
-
264
301
  ```
265
302
 
266
303
  ##### Label and Tooltip Management
@@ -294,6 +331,89 @@ title.addTooltip("This is an Example of a tooltip!", { color: "red" });
294
331
 
295
332
  ![Example](./assets//infoIconExample.gif)
296
333
 
334
+ Here's an improved markdown documentation with more comprehensive details:
335
+
336
+ ### BindForm Method
337
+
338
+ The `bindForm` method simplifies form element management in DataVerse by providing a semantic and efficient way to access form controls, sections, and tabs.
339
+
340
+ ##### Key Features
341
+
342
+ - Retrieves form definition directly from DataVerse
343
+ - Automatically generates references for:
344
+ - Controls
345
+ - Sections
346
+ - Tabs
347
+
348
+ ##### Element Types
349
+
350
+ | Element Type | Description | Accessibility |
351
+ | ------------ | ------------------------------------------- | ------------------------- |
352
+ | `control` | Includes all form fields and sub-grids | Accessed via logical name |
353
+ | `section` | Standard PowerApps form sections | Accessed via logical name |
354
+ | `tab` | Form tabs corresponding to PowerApps layout | Accessed via logical name |
355
+
356
+ ##### Usage Example
357
+
358
+ ```javascript
359
+ import { bindForm } from "powerpagestoolkit";
360
+
361
+ // Basic form binding
362
+ bindForm("form-guid").then((form) => {
363
+ // Access elements by their logical name
364
+ const nameField = form["name"];
365
+
366
+ // execute custom methods
367
+ nameField.applyBusinessRule(
368
+ {
369
+ setVisibility: [() => someNode.value === "desired value"],
370
+ },
371
+ [someNode]
372
+ );
373
+
374
+ // Or executes methods immediately upon accessing
375
+ form["phonenumber"].addTooltip("Example tooltip text");
376
+ });
377
+ ```
378
+
379
+ ##### Method Signature
380
+
381
+ ```typescript
382
+ /**
383
+ * Binds a form by its GUID and returns a collection of form elements
384
+ * @param formGuid Unique identifier for the form
385
+ * @returns Promise resolving to form element references
386
+ */
387
+ function bindForm(formGuid: string): Promise<DOMNodeReferenceArray & Record<string: DOMNodeReference>>;
388
+ ```
389
+
390
+ ##### Benefits
391
+
392
+ - Reduces code complexity
393
+ - Improves readability
394
+ - Provides type-safe access to form elements
395
+ - Supports flexible form interactions
396
+
397
+ ##### Best Practices
398
+
399
+ - Use logical names consistently
400
+ - Handle async nature of form binding
401
+ - Leverage TypeScript for enhanced type checking
402
+
403
+ ##### Error Handling
404
+
405
+ Ensure proper error handling for form binding:
406
+
407
+ ```javascript
408
+ bindForm("form-guid")
409
+ .then((form) => {
410
+ // Form processing
411
+ })
412
+ .catch((error) => {
413
+ console.error("Form binding failed", error);
414
+ });
415
+ ```
416
+
297
417
  ### DataVerse API
298
418
 
299
419
  Perform secure API calls to DataVerse from your PowerPages site. This method implements the shell deferred token to send requests with `__RequestVerificationToken`
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,30 @@
1
+ interface IBusinessRule {
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
+
7
+ * @param clearValuesOnHide Should the values in the targeted field be cleared when hidden? Defaults to true
8
+ */
9
+ setVisibility?: [condition: () => boolean, clearValuesOnHide?: boolean];
10
+ /**
11
+ * @param isRequired Function determining if field is required
12
+ * @param isValid Function validating field input.
13
+ */
14
+ setRequired?: [isRequired: () => boolean, isValid: () => boolean];
15
+ /**
16
+ * @param condition A function to determine if the value provided should be applied to this field
17
+ * @param value The value to set for the HTML element.
18
+ * for parents of boolean radios, pass true or false as value, or
19
+ * an expression returning a boolean
20
+ */
21
+ setValue?: [condition: () => boolean, value: () => any | any];
22
+ /**
23
+ * @param condition A function to determine if this field
24
+ * should be enabled in a form, or disabled. True || 1 = disabled. False || 0 = enabled
25
+ */
26
+ setDisabled?: () => boolean;
27
+ }
1
28
  export declare const _init: unique symbol;
2
29
  declare const _destroy: unique symbol;
3
30
  declare const _valueSync: unique symbol;
@@ -11,8 +38,9 @@ declare const _debounceTime: unique symbol;
11
38
  declare const _observers: unique symbol;
12
39
  declare const _boundEventListeners: unique symbol;
13
40
  export default class DOMNodeReference {
14
- target: HTMLElement | string;
15
- root: HTMLElement;
41
+ target: Element | string;
42
+ logicalName?: string;
43
+ root: Element;
16
44
  private [_debounceTime];
17
45
  private isLoaded;
18
46
  private defaultDisplay;
@@ -49,8 +77,9 @@ export default class DOMNodeReference {
49
77
  * @param root - Optionally specify the element within to search for the element targeted by 'target'
50
78
  * Defaults to 'document.body'
51
79
  */
52
- /******/ /******/ constructor(target: HTMLElement | string, root: HTMLElement | undefined, debounceTime: number);
80
+ /******/ /******/ constructor(target: Element | string, root: Element | undefined, debounceTime: number);
53
81
  [_init](): Promise<void>;
82
+ private eventMapping;
54
83
  /**
55
84
  * Initializes value synchronization with appropriate event listeners
56
85
  * based on element type.
@@ -196,52 +225,61 @@ export default class DOMNodeReference {
196
225
  * @returns - Instance of this [provides option to method chain]
197
226
  */
198
227
  uncheckRadios(): DOMNodeReference;
228
+ /**
229
+ * Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
230
+ *
231
+ * @param rule The business rule containing conditions for various actions.
232
+ * @param dependencies For re-evaluation conditions when the state of the dependencies change
233
+ * @returns Instance of this for method chaining.
234
+ */
235
+ applyBusinessRule(rule: IBusinessRule, dependencies: DOMNodeReference[]): DOMNodeReference;
199
236
  /**
200
237
  * Configures conditional rendering for the target element based on a condition
201
238
  * and the visibility of one or more trigger elements.
202
- *
203
- * @param condition - A function that returns a boolean to determine
239
+ * @deprecated Use the new 'applyBusinessRule Method
240
+ * @param condition A function that returns a boolean to determine
204
241
  * the visibility of the target element. If `condition()` returns true, the element is shown;
205
242
  * otherwise, it is hidden.
206
243
  * @param dependencies - An array of `DOMNodeReference` instances. Event listeners are
207
244
  * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
208
245
  * the target node.
209
- * @throws - When there's an error in setting up conditional rendering
210
- * @returns - Instance of this [provides option to method chain]
246
+ * @throws When there's an error in setting up conditional rendering
247
+ * @returns Instance of this [provides option to method chain]
211
248
  */
212
249
  configureConditionalRendering(condition: () => boolean, dependencies?: Array<DOMNodeReference>, clearValuesOnHide?: boolean): DOMNodeReference;
213
250
  /**
214
251
  * Sets up validation and requirement rules for the field with enhanced error handling and dynamic updates.
215
- *
216
- * @param isRequired - Function determining if field is required
217
- * @param isValid - Function validating field input
218
- * @param fieldDisplayName - Display name for error messages
219
- * @param dependencies - Fields that trigger requirement/validation updates
220
- * @returns - Instance of this
221
- * @throws - If validation setup fails
252
+ * @deprecated Use the new 'applyBusinessRule Method
253
+ * @param isRequired Function determining if field is required
254
+ * @param isValid Function validating field input
255
+ * @param fieldDisplayName Display name for error messages
256
+ * @param dependencies Fields that trigger requirement/validation updates
257
+ * @returns Instance of this
258
+ * @throws If validation setup fails
222
259
  */
223
260
  configureValidationAndRequirements(isRequired: () => boolean, isValid: () => boolean, fieldDisplayName: string, dependencies: Array<DOMNodeReference>): DOMNodeReference;
224
261
  /**
225
262
  * Sets up tracking for dependencies using both event listeners and mutation observers.
226
263
  * @private
227
- * @param handler - The function to execute when dependencies change
228
- * @param dependencies - Array of dependent DOM nodes to track
229
- * @param options - Additional configuration options
264
+ * @param handler The function to execute when dependencies change
265
+ * @param dependencies Array of dependent DOM nodes to track
266
+ * @param options Additional configuration options. clearValuesOnHide defaults to false.
267
+ * all other options defaults to true
230
268
  */
231
269
  private _configDependencyTracking;
232
270
  /**
233
271
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
234
272
  *
235
- * @param isRequired - Determines whether the field should be marked as required.
273
+ * @param isRequired Determines whether the field should be marked as required.
236
274
  * If true, the "required-field" class is added to the label; if false, it is removed.
237
- * @returns - Instance of this [provides option to method chain]
275
+ * @returns Instance of this [provides option to method chain]
238
276
  */
239
277
  setRequiredLevel(isRequired: (() => boolean) | boolean): DOMNodeReference;
240
278
  /**
241
279
  * Executes a callback function once the element is fully loaded.
242
280
  * If the element is already loaded, the callback is called immediately.
243
281
  * Otherwise, a MutationObserver is used to detect when the element is added to the DOM.
244
- * @param callback - A callback function to execute once the element is loaded.
282
+ * @param callback A callback function to execute once the element is loaded.
245
283
  * Receives instance of 'this' as an argument
246
284
  */
247
285
  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,6 +102,40 @@ 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(
131
+ (instance) => instance.target.toString().replace(/[#\[\]]/g, "") === prop || instance.logicalName === prop
132
+ );
133
+ }
134
+ return void 0;
135
+ }
136
+ });
137
+ }
138
+
105
139
  // src/waitFor.ts
106
140
  function waitFor(target, root = document, multiple = false, debounceTime) {
107
141
  return new Promise((resolve, reject) => {
@@ -130,7 +164,7 @@ function waitFor(target, root = document, multiple = false, debounceTime) {
130
164
  } else {
131
165
  reject(
132
166
  new Error(
133
- `No elements found with target: "${target}" within ${debounceTime / 1e3} seconds. If the element you are expected has not loaded yet, consider raising your timeout.`
167
+ `No elements found with target: "${target}" within ${debounceTime / 1e3} seconds. If the element you are expecting has not loaded yet, consider raising your timeout.`
134
168
  )
135
169
  );
136
170
  }
@@ -154,7 +188,7 @@ function waitFor(target, root = document, multiple = false, debounceTime) {
154
188
  observer.disconnect();
155
189
  reject(
156
190
  new Error(
157
- `Element not found by target: "${target}" within ${debounceTime / 1e3} second. If the element you are expected has not loaded yet, consider raising your timeout.`
191
+ `Element not found by target: "${target}" within ${debounceTime / 1e3} second. If the element you are expecting has not loaded yet, consider raising your timeout.`
158
192
  )
159
193
  );
160
194
  }, debounceTime);
@@ -285,6 +319,7 @@ var _boundEventListeners = Symbol("BEV");
285
319
  var DOMNodeReference = class _DOMNodeReference {
286
320
  // properties initialized in the constructor
287
321
  target;
322
+ logicalName;
288
323
  root;
289
324
  [_debounceTime];
290
325
  isLoaded;
@@ -306,6 +341,18 @@ var DOMNodeReference = class _DOMNodeReference {
306
341
  /******/
307
342
  constructor(target, root = document.body, debounceTime) {
308
343
  this.target = target;
344
+ if (typeof target === "string") {
345
+ let lName = null;
346
+ const bracketMatch = target.match(/\[([^\]]+)\]/);
347
+ if (bracketMatch) {
348
+ lName = bracketMatch[1];
349
+ const quoteMatch = lName.match(/["']([^"']+)["']/);
350
+ if (quoteMatch) {
351
+ lName = quoteMatch[1];
352
+ }
353
+ }
354
+ this.logicalName = (lName || target).replace(/[#\[\]]/g, "");
355
+ }
309
356
  this.root = root;
310
357
  this[_debounceTime] = debounceTime;
311
358
  this.isLoaded = false;
@@ -348,6 +395,14 @@ var DOMNodeReference = class _DOMNodeReference {
348
395
  throw new DOMNodeInitializationError(this, errorMessage);
349
396
  }
350
397
  }
398
+ eventMapping = {
399
+ checkbox: "click",
400
+ radio: "click",
401
+ select: "change",
402
+ "select-multiple": "change",
403
+ textarea: "keyup"
404
+ // Add other input types as needed
405
+ };
351
406
  /**
352
407
  * Initializes value synchronization with appropriate event listeners
353
408
  * based on element type.
@@ -356,30 +411,20 @@ var DOMNodeReference = class _DOMNodeReference {
356
411
  async [_valueSync]() {
357
412
  try {
358
413
  this.updateValue();
359
- if (!(this.element instanceof HTMLElement)) {
360
- throw new Error("Element is not a valid HTML element");
361
- }
362
- const eventMapping = {
363
- checkbox: "click",
364
- radio: "click",
365
- select: "change",
366
- "select-multiple": "change"
367
- // Add other input types as needed
368
- };
369
414
  let eventType;
370
415
  if (this.element instanceof HTMLSelectElement) {
371
416
  eventType = "change";
372
417
  } else if (this.element instanceof HTMLInputElement) {
373
- eventType = eventMapping[this.element.type] || "input";
418
+ eventType = this.eventMapping[this.element.type] ?? "input";
419
+ } else if (this.element instanceof HTMLTextAreaElement) {
420
+ eventType = this.eventMapping[this.element.type] ?? "input";
374
421
  } else {
375
422
  eventType = "input";
376
423
  }
377
424
  this.element.addEventListener(eventType, this.updateValue);
378
- const _element = this.element;
379
- const _updateValue = this.updateValue;
380
425
  this[_boundEventListeners].push({
381
- element: _element,
382
- handler: _updateValue,
426
+ element: this.element,
427
+ handler: this.updateValue,
383
428
  event: eventType
384
429
  });
385
430
  if (this.element instanceof HTMLInputElement && this.element.dataset.type === "date") {
@@ -436,8 +481,11 @@ var DOMNodeReference = class _DOMNodeReference {
436
481
  value: input.value !== "" ? Number(input.value) : null
437
482
  };
438
483
  default:
484
+ let cleanValue = input.value;
485
+ if (this.element.classList.contains("decimal") || this.element.classList.contains("money"))
486
+ cleanValue = input.value.replace(/[$,]/g, "");
439
487
  return {
440
- value: this.element.classList.contains("decimal") || this.element.classList.contains("money") ? parseFloat(input.value) : input.value
488
+ value: this.element.classList.contains("decimal") || this.element.classList.contains("money") ? parseFloat(cleanValue) : cleanValue
441
489
  };
442
490
  }
443
491
  }
@@ -580,9 +628,21 @@ var DOMNodeReference = class _DOMNodeReference {
580
628
  if (value instanceof Function) {
581
629
  value = value();
582
630
  }
631
+ let eventType;
632
+ if (this.element instanceof HTMLSelectElement) {
633
+ eventType = "change";
634
+ } else if (this.element instanceof HTMLInputElement) {
635
+ eventType = this.eventMapping[this.element.type] ?? "input";
636
+ } else if (this.element instanceof HTMLTextAreaElement) {
637
+ eventType = this.eventMapping[this.element.type] ?? "input";
638
+ } else {
639
+ eventType = "input";
640
+ }
641
+ this.element.dispatchEvent(new Event(eventType, { bubbles: false }));
583
642
  if (this.element.classList.contains("boolean-radio") && this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
584
643
  this.yesRadio.element.checked = value;
585
644
  this.noRadio.element.checked = !value;
645
+ this.value = value;
586
646
  } else {
587
647
  this.element.value = value;
588
648
  }
@@ -801,18 +861,128 @@ var DOMNodeReference = class _DOMNodeReference {
801
861
  }
802
862
  return this;
803
863
  }
864
+ /**
865
+ * Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
866
+ *
867
+ * @param rule The business rule containing conditions for various actions.
868
+ * @param dependencies For re-evaluation conditions when the state of the dependencies change
869
+ * @returns Instance of this for method chaining.
870
+ */
871
+ applyBusinessRule(rule, dependencies) {
872
+ try {
873
+ if (rule.setVisibility) {
874
+ const [condition, clearValuesOnHide = true] = rule.setVisibility;
875
+ const initialState = condition.bind(this)();
876
+ this.toggleVisibility(initialState);
877
+ if (dependencies.length) {
878
+ this._configDependencyTracking(
879
+ () => this.toggleVisibility(condition.bind(this)()),
880
+ dependencies,
881
+ {
882
+ clearValuesOnHide,
883
+ observeVisibility: true,
884
+ trackInputEvents: false,
885
+ trackRadioButtons: false
886
+ }
887
+ );
888
+ }
889
+ }
890
+ if (rule.setRequired) {
891
+ const [isRequired, isValid] = rule.setRequired;
892
+ const fieldDisplayName = (() => {
893
+ let label = this.getLabel();
894
+ if (!label)
895
+ return new Error(
896
+ `There was an error accessing the label for this element: ${String(
897
+ this.target
898
+ )}`
899
+ );
900
+ label = label.innerHTML;
901
+ if (label.length > 50) {
902
+ label = label.substring(0, 50) + "...";
903
+ }
904
+ return label;
905
+ })();
906
+ if (typeof Page_Validators === "undefined") {
907
+ throw new ValidationConfigError(this, "Page_Validators not found");
908
+ }
909
+ const validatorId = `${this.element.id}Validator`;
910
+ const newValidator = document.createElement("span");
911
+ newValidator.style.display = "none";
912
+ newValidator.id = validatorId;
913
+ Object.assign(newValidator, {
914
+ controltovalidate: this.element.id,
915
+ errormessage: `<a href='#${this.element.id}_label'>${fieldDisplayName} is a required field</a>`,
916
+ evaluationfunction: () => {
917
+ const isFieldRequired = isRequired.bind(this)();
918
+ const isFieldVisible = window.getComputedStyle(this.visibilityController).display !== "none";
919
+ return !isFieldRequired || !isFieldVisible || isValid.bind(this)();
920
+ }
921
+ });
922
+ Page_Validators.push(newValidator);
923
+ this.setRequiredLevel(isRequired.bind(this)());
924
+ this._configDependencyTracking(
925
+ () => this.setRequiredLevel(isRequired.bind(this)()),
926
+ dependencies,
927
+ { clearValuesOnHide: false }
928
+ );
929
+ }
930
+ if (rule.setValue) {
931
+ let [condition, value] = rule.setValue;
932
+ if (value instanceof Function) value = value();
933
+ if (condition.bind(this)()) {
934
+ this.setValue.bind(this)(value);
935
+ }
936
+ if (dependencies.length) {
937
+ this._configDependencyTracking(
938
+ () => {
939
+ if (condition.bind(this)()) {
940
+ this.setValue.bind(this)(value);
941
+ }
942
+ },
943
+ dependencies,
944
+ { clearValuesOnHide: false }
945
+ );
946
+ }
947
+ }
948
+ if (rule.setDisabled) {
949
+ const condition = rule.setDisabled;
950
+ condition.bind(this)() ? this.disable() : this.enable();
951
+ if (dependencies.length) {
952
+ this._configDependencyTracking(
953
+ () => {
954
+ condition.bind(this)() ? this.enable() : this.disable();
955
+ },
956
+ dependencies,
957
+ {
958
+ clearValuesOnHide: false,
959
+ observeVisibility: true,
960
+ trackInputEvents: true,
961
+ trackRadioButtons: true
962
+ }
963
+ );
964
+ }
965
+ }
966
+ return this;
967
+ } catch (error) {
968
+ throw new ValidationConfigError(
969
+ this,
970
+ `Failed to apply business rule: ${error}`
971
+ );
972
+ }
973
+ }
804
974
  /**
805
975
  * Configures conditional rendering for the target element based on a condition
806
976
  * and the visibility of one or more trigger elements.
807
- *
808
- * @param condition - A function that returns a boolean to determine
977
+ * @deprecated Use the new 'applyBusinessRule Method
978
+ * @param condition A function that returns a boolean to determine
809
979
  * the visibility of the target element. If `condition()` returns true, the element is shown;
810
980
  * otherwise, it is hidden.
811
981
  * @param dependencies - An array of `DOMNodeReference` instances. Event listeners are
812
982
  * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
813
983
  * the target node.
814
- * @throws - When there's an error in setting up conditional rendering
815
- * @returns - Instance of this [provides option to method chain]
984
+ * @throws When there's an error in setting up conditional rendering
985
+ * @returns Instance of this [provides option to method chain]
816
986
  */
817
987
  configureConditionalRendering(condition, dependencies, clearValuesOnHide = true) {
818
988
  try {
@@ -846,13 +1016,13 @@ var DOMNodeReference = class _DOMNodeReference {
846
1016
  }
847
1017
  /**
848
1018
  * Sets up validation and requirement rules for the field with enhanced error handling and dynamic updates.
849
- *
850
- * @param isRequired - Function determining if field is required
851
- * @param isValid - Function validating field input
852
- * @param fieldDisplayName - Display name for error messages
853
- * @param dependencies - Fields that trigger requirement/validation updates
854
- * @returns - Instance of this
855
- * @throws - If validation setup fails
1019
+ * @deprecated Use the new 'applyBusinessRule Method
1020
+ * @param isRequired Function determining if field is required
1021
+ * @param isValid Function validating field input
1022
+ * @param fieldDisplayName Display name for error messages
1023
+ * @param dependencies Fields that trigger requirement/validation updates
1024
+ * @returns Instance of this
1025
+ * @throws If validation setup fails
856
1026
  */
857
1027
  configureValidationAndRequirements(isRequired, isValid, fieldDisplayName, dependencies) {
858
1028
  if (!fieldDisplayName?.trim()) {
@@ -901,9 +1071,10 @@ var DOMNodeReference = class _DOMNodeReference {
901
1071
  /**
902
1072
  * Sets up tracking for dependencies using both event listeners and mutation observers.
903
1073
  * @private
904
- * @param handler - The function to execute when dependencies change
905
- * @param dependencies - Array of dependent DOM nodes to track
906
- * @param options - Additional configuration options
1074
+ * @param handler The function to execute when dependencies change
1075
+ * @param dependencies Array of dependent DOM nodes to track
1076
+ * @param options Additional configuration options. clearValuesOnHide defaults to false.
1077
+ * all other options defaults to true
907
1078
  */
908
1079
  _configDependencyTracking(handler, dependencies, options = {
909
1080
  clearValuesOnHide: false,
@@ -980,9 +1151,9 @@ var DOMNodeReference = class _DOMNodeReference {
980
1151
  /**
981
1152
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
982
1153
  *
983
- * @param isRequired - Determines whether the field should be marked as required.
1154
+ * @param isRequired Determines whether the field should be marked as required.
984
1155
  * If true, the "required-field" class is added to the label; if false, it is removed.
985
- * @returns - Instance of this [provides option to method chain]
1156
+ * @returns Instance of this [provides option to method chain]
986
1157
  */
987
1158
  setRequiredLevel(isRequired) {
988
1159
  if (isRequired instanceof Function) {
@@ -997,7 +1168,7 @@ var DOMNodeReference = class _DOMNodeReference {
997
1168
  * Executes a callback function once the element is fully loaded.
998
1169
  * If the element is already loaded, the callback is called immediately.
999
1170
  * Otherwise, a MutationObserver is used to detect when the element is added to the DOM.
1000
- * @param callback - A callback function to execute once the element is loaded.
1171
+ * @param callback A callback function to execute once the element is loaded.
1001
1172
  * Receives instance of 'this' as an argument
1002
1173
  */
1003
1174
  onceLoaded(callback) {
@@ -1104,25 +1275,62 @@ function createProxyHandler() {
1104
1275
  }
1105
1276
  };
1106
1277
  }
1107
- function enhanceArray(array) {
1108
- Object.defineProperties(array, {
1109
- hideAll: {
1110
- value: function() {
1111
- this.forEach((instance) => instance.hide());
1112
- return this;
1113
- }
1114
- },
1115
- showAll: {
1116
- value: function() {
1117
- this.forEach((instance) => instance.show());
1118
- return this;
1119
- }
1278
+
1279
+ // src/bindForm.ts
1280
+ async function bindForm(formId) {
1281
+ try {
1282
+ const form = await API_default.getRecord("systemforms", formId);
1283
+ const { formxml } = form;
1284
+ const parser = new DOMParser();
1285
+ const xmlDoc = parser.parseFromString(formxml, "application/xml");
1286
+ const controls = processElements(xmlDoc.getElementsByTagName("control"));
1287
+ const sections = processElements(xmlDoc.getElementsByTagName("section"));
1288
+ const tabs = processElements(xmlDoc.getElementsByTagName("tab"));
1289
+ const resolvedRefs = await Promise.all([...controls, ...sections, ...tabs]);
1290
+ return enhanceArray(
1291
+ resolvedRefs.filter((ref) => ref !== null)
1292
+ );
1293
+ } catch (error) {
1294
+ if (error instanceof Error) {
1295
+ console.error(error.message);
1296
+ throw error;
1297
+ } else {
1298
+ console.error(error);
1299
+ throw new Error(String(error));
1120
1300
  }
1121
- });
1122
- return array;
1301
+ }
1302
+ }
1303
+ function processElements(element) {
1304
+ return Array.from(element).map((element2) => {
1305
+ const identifyingAttribute = getIdentifyingAttribute(element2.tagName);
1306
+ const datafieldname = element2.getAttribute(identifyingAttribute);
1307
+ if (!datafieldname) return null;
1308
+ const referenceString = createReferenceString(
1309
+ element2.tagName,
1310
+ datafieldname
1311
+ );
1312
+ if (!referenceString) return null;
1313
+ return createDOMNodeReference(referenceString).catch((error) => {
1314
+ console.warn(
1315
+ `Failed to create a reference to the form field: ${datafieldname}`,
1316
+ error
1317
+ );
1318
+ return null;
1319
+ });
1320
+ }).filter(Boolean);
1321
+ }
1322
+ function getIdentifyingAttribute(tagName) {
1323
+ return tagName === "control" ? "id" : tagName === "tab" || tagName === "section" ? "name" : "id";
1324
+ }
1325
+ function createReferenceString(tagName, datafieldname) {
1326
+ if (tagName === "control") return `#${datafieldname}`;
1327
+ if (tagName === "tab" || tagName === "section")
1328
+ return `[data-name="${datafieldname}"]`;
1329
+ return null;
1123
1330
  }
1124
1331
  export {
1125
1332
  API_default as API,
1333
+ bindForm,
1126
1334
  createDOMNodeReference as createRef,
1127
1335
  waitFor
1128
1336
  };
@@ -1,11 +1,36 @@
1
+ import { DOMNodeReferenceArray } from "./DOMNodeReferenceArray.js";
1
2
  import DOMNodeReference from "./DOMNodeReference.js";
2
- export default function createDOMNodeReference(target: HTMLElement | string, options?: {
3
- multiple?: (() => boolean) | false;
3
+ interface ICreationOptions {
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
+ */
4
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
+ */
5
19
  timeout?: number;
20
+ }
21
+ export default function createDOMNodeReference(target: Element, options?: ICreationOptions): Promise<DOMNodeReference>;
22
+ export default function createDOMNodeReference(target: string, options?: Omit<ICreationOptions, "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;
6
28
  }): Promise<DOMNodeReference>;
7
- export default function createDOMNodeReference(target: string, options?: {
8
- multiple?: (() => true) | true;
9
- root?: HTMLElement;
10
- timeout?: number;
11
- }): Promise<DOMNodeReference[]>;
29
+ export default function createDOMNodeReference(target: string, options?: Omit<ICreationOptions, "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,5 @@ 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 };
package/dist/waitFor.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export default function waitFor(target: HTMLElement | string, root: HTMLElement | Document, multiple: false, debounceTime: number): Promise<HTMLElement>;
2
- export default function waitFor(target: HTMLElement | string, root: HTMLElement | Document, multiple: true, debounceTime: number): 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.33311",
3
+ "version": "2.7.001",
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",