powerpagestoolkit 2.6.33311 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
 
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,33 @@
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. allows access to the invoked expression passed by {@link isRequired}
13
+ */
14
+ setRequired?: [
15
+ isRequired: () => boolean,
16
+ isValid: (isRequired: () => boolean) => boolean
17
+ ];
18
+ /**
19
+ * @param condition A function to determine if the value provided should be applied to this field
20
+ * @param value The value to set for the HTML element.
21
+ * for parents of boolean radios, pass true or false as value, or
22
+ * an expression returning a boolean
23
+ */
24
+ setValue?: [condition: () => boolean, value: any];
25
+ /**
26
+ * @param condition A function to determine if this field
27
+ * should be enabled in a form, or disabled. True || 1 = disabled. False || 0 = enabled
28
+ */
29
+ setDisabled?: [condition: () => boolean];
30
+ }
1
31
  export declare const _init: unique symbol;
2
32
  declare const _destroy: unique symbol;
3
33
  declare const _valueSync: unique symbol;
@@ -11,8 +41,9 @@ declare const _debounceTime: unique symbol;
11
41
  declare const _observers: unique symbol;
12
42
  declare const _boundEventListeners: unique symbol;
13
43
  export default class DOMNodeReference {
14
- target: HTMLElement | string;
15
- root: HTMLElement;
44
+ target: Element | string;
45
+ logicalName?: string;
46
+ root: Element;
16
47
  private [_debounceTime];
17
48
  private isLoaded;
18
49
  private defaultDisplay;
@@ -49,8 +80,9 @@ export default class DOMNodeReference {
49
80
  * @param root - Optionally specify the element within to search for the element targeted by 'target'
50
81
  * Defaults to 'document.body'
51
82
  */
52
- /******/ /******/ constructor(target: HTMLElement | string, root: HTMLElement | undefined, debounceTime: number);
83
+ /******/ /******/ constructor(target: Element | string, root: Element | undefined, debounceTime: number);
53
84
  [_init](): Promise<void>;
85
+ private eventMapping;
54
86
  /**
55
87
  * Initializes value synchronization with appropriate event listeners
56
88
  * based on element type.
@@ -196,52 +228,61 @@ export default class DOMNodeReference {
196
228
  * @returns - Instance of this [provides option to method chain]
197
229
  */
198
230
  uncheckRadios(): DOMNodeReference;
231
+ /**
232
+ * Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
233
+ *
234
+ * @param rule The business rule containing conditions for various actions.
235
+ * @param dependencies For re-evaluation conditions when the state of the dependencies change
236
+ * @returns Instance of this for method chaining.
237
+ */
238
+ applyBusinessRule(rule: IBusinessRule, dependencies: DOMNodeReference[]): DOMNodeReference;
199
239
  /**
200
240
  * Configures conditional rendering for the target element based on a condition
201
241
  * and the visibility of one or more trigger elements.
202
- *
203
- * @param condition - A function that returns a boolean to determine
242
+ * @deprecated Use the new 'applyBusinessRule Method
243
+ * @param condition A function that returns a boolean to determine
204
244
  * the visibility of the target element. If `condition()` returns true, the element is shown;
205
245
  * otherwise, it is hidden.
206
246
  * @param dependencies - An array of `DOMNodeReference` instances. Event listeners are
207
247
  * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
208
248
  * 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]
249
+ * @throws When there's an error in setting up conditional rendering
250
+ * @returns Instance of this [provides option to method chain]
211
251
  */
212
252
  configureConditionalRendering(condition: () => boolean, dependencies?: Array<DOMNodeReference>, clearValuesOnHide?: boolean): DOMNodeReference;
213
253
  /**
214
254
  * 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
255
+ * @deprecated Use the new 'applyBusinessRule Method
256
+ * @param isRequired Function determining if field is required
257
+ * @param isValid Function validating field input
258
+ * @param fieldDisplayName Display name for error messages
259
+ * @param dependencies Fields that trigger requirement/validation updates
260
+ * @returns Instance of this
261
+ * @throws If validation setup fails
222
262
  */
223
263
  configureValidationAndRequirements(isRequired: () => boolean, isValid: () => boolean, fieldDisplayName: string, dependencies: Array<DOMNodeReference>): DOMNodeReference;
224
264
  /**
225
265
  * Sets up tracking for dependencies using both event listeners and mutation observers.
226
266
  * @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
267
+ * @param handler The function to execute when dependencies change
268
+ * @param dependencies Array of dependent DOM nodes to track
269
+ * @param options Additional configuration options. clearValuesOnHide defaults to false.
270
+ * all other options defaults to true
230
271
  */
231
272
  private _configDependencyTracking;
232
273
  /**
233
274
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
234
275
  *
235
- * @param isRequired - Determines whether the field should be marked as required.
276
+ * @param isRequired Determines whether the field should be marked as required.
236
277
  * 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]
278
+ * @returns Instance of this [provides option to method chain]
238
279
  */
239
280
  setRequiredLevel(isRequired: (() => boolean) | boolean): DOMNodeReference;
240
281
  /**
241
282
  * Executes a callback function once the element is fully loaded.
242
283
  * If the element is already loaded, the callback is called immediately.
243
284
  * 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.
285
+ * @param callback A callback function to execute once the element is loaded.
245
286
  * Receives instance of 'this' as an argument
246
287
  */
247
288
  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,127 @@ 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)(isRequired.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
+ const [condition, value] = rule.setValue;
932
+ if (condition.bind(this)()) {
933
+ this.setValue.bind(this)(value);
934
+ }
935
+ if (dependencies.length) {
936
+ this._configDependencyTracking(
937
+ () => {
938
+ if (condition.bind(this)()) {
939
+ this.setValue.bind(this)(value);
940
+ }
941
+ },
942
+ dependencies,
943
+ { clearValuesOnHide: false }
944
+ );
945
+ }
946
+ }
947
+ if (rule.setDisabled) {
948
+ const [condition] = rule.setDisabled;
949
+ condition.bind(this)() ? this.disable() : this.enable();
950
+ if (dependencies.length) {
951
+ this._configDependencyTracking(
952
+ () => {
953
+ condition.bind(this)() ? this.enable() : this.disable();
954
+ },
955
+ dependencies,
956
+ {
957
+ clearValuesOnHide: false,
958
+ observeVisibility: true,
959
+ trackInputEvents: true,
960
+ trackRadioButtons: true
961
+ }
962
+ );
963
+ }
964
+ }
965
+ return this;
966
+ } catch (error) {
967
+ throw new ValidationConfigError(
968
+ this,
969
+ `Failed to apply business rule: ${error}`
970
+ );
971
+ }
972
+ }
804
973
  /**
805
974
  * Configures conditional rendering for the target element based on a condition
806
975
  * and the visibility of one or more trigger elements.
807
- *
808
- * @param condition - A function that returns a boolean to determine
976
+ * @deprecated Use the new 'applyBusinessRule Method
977
+ * @param condition A function that returns a boolean to determine
809
978
  * the visibility of the target element. If `condition()` returns true, the element is shown;
810
979
  * otherwise, it is hidden.
811
980
  * @param dependencies - An array of `DOMNodeReference` instances. Event listeners are
812
981
  * registered on each to toggle the visibility of the target element based on the `condition` and the visibility of
813
982
  * 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]
983
+ * @throws When there's an error in setting up conditional rendering
984
+ * @returns Instance of this [provides option to method chain]
816
985
  */
817
986
  configureConditionalRendering(condition, dependencies, clearValuesOnHide = true) {
818
987
  try {
@@ -846,13 +1015,13 @@ var DOMNodeReference = class _DOMNodeReference {
846
1015
  }
847
1016
  /**
848
1017
  * 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
1018
+ * @deprecated Use the new 'applyBusinessRule Method
1019
+ * @param isRequired Function determining if field is required
1020
+ * @param isValid Function validating field input
1021
+ * @param fieldDisplayName Display name for error messages
1022
+ * @param dependencies Fields that trigger requirement/validation updates
1023
+ * @returns Instance of this
1024
+ * @throws If validation setup fails
856
1025
  */
857
1026
  configureValidationAndRequirements(isRequired, isValid, fieldDisplayName, dependencies) {
858
1027
  if (!fieldDisplayName?.trim()) {
@@ -901,9 +1070,10 @@ var DOMNodeReference = class _DOMNodeReference {
901
1070
  /**
902
1071
  * Sets up tracking for dependencies using both event listeners and mutation observers.
903
1072
  * @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
1073
+ * @param handler The function to execute when dependencies change
1074
+ * @param dependencies Array of dependent DOM nodes to track
1075
+ * @param options Additional configuration options. clearValuesOnHide defaults to false.
1076
+ * all other options defaults to true
907
1077
  */
908
1078
  _configDependencyTracking(handler, dependencies, options = {
909
1079
  clearValuesOnHide: false,
@@ -980,9 +1150,9 @@ var DOMNodeReference = class _DOMNodeReference {
980
1150
  /**
981
1151
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
982
1152
  *
983
- * @param isRequired - Determines whether the field should be marked as required.
1153
+ * @param isRequired Determines whether the field should be marked as required.
984
1154
  * 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]
1155
+ * @returns Instance of this [provides option to method chain]
986
1156
  */
987
1157
  setRequiredLevel(isRequired) {
988
1158
  if (isRequired instanceof Function) {
@@ -997,7 +1167,7 @@ var DOMNodeReference = class _DOMNodeReference {
997
1167
  * Executes a callback function once the element is fully loaded.
998
1168
  * If the element is already loaded, the callback is called immediately.
999
1169
  * 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.
1170
+ * @param callback A callback function to execute once the element is loaded.
1001
1171
  * Receives instance of 'this' as an argument
1002
1172
  */
1003
1173
  onceLoaded(callback) {
@@ -1104,25 +1274,62 @@ function createProxyHandler() {
1104
1274
  }
1105
1275
  };
1106
1276
  }
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
- }
1277
+
1278
+ // src/bindForm.ts
1279
+ async function bindForm(formId) {
1280
+ try {
1281
+ const form = await API_default.getRecord("systemforms", formId);
1282
+ const { formxml } = form;
1283
+ const parser = new DOMParser();
1284
+ const xmlDoc = parser.parseFromString(formxml, "application/xml");
1285
+ const controls = processElements(xmlDoc.getElementsByTagName("control"));
1286
+ const sections = processElements(xmlDoc.getElementsByTagName("section"));
1287
+ const tabs = processElements(xmlDoc.getElementsByTagName("tab"));
1288
+ const resolvedRefs = await Promise.all([...controls, ...sections, ...tabs]);
1289
+ return enhanceArray(
1290
+ resolvedRefs.filter((ref) => ref !== null)
1291
+ );
1292
+ } catch (error) {
1293
+ if (error instanceof Error) {
1294
+ console.error(error.message);
1295
+ throw error;
1296
+ } else {
1297
+ console.error(error);
1298
+ throw new Error(String(error));
1120
1299
  }
1121
- });
1122
- return array;
1300
+ }
1301
+ }
1302
+ function processElements(element) {
1303
+ return Array.from(element).map((element2) => {
1304
+ const identifyingAttribute = getIdentifyingAttribute(element2.tagName);
1305
+ const datafieldname = element2.getAttribute(identifyingAttribute);
1306
+ if (!datafieldname) return null;
1307
+ const referenceString = createReferenceString(
1308
+ element2.tagName,
1309
+ datafieldname
1310
+ );
1311
+ if (!referenceString) return null;
1312
+ return createDOMNodeReference(referenceString).catch((error) => {
1313
+ console.warn(
1314
+ `Failed to create a reference to the form field: ${datafieldname}`,
1315
+ error
1316
+ );
1317
+ return null;
1318
+ });
1319
+ }).filter(Boolean);
1320
+ }
1321
+ function getIdentifyingAttribute(tagName) {
1322
+ return tagName === "control" ? "id" : tagName === "tab" || tagName === "section" ? "name" : "id";
1323
+ }
1324
+ function createReferenceString(tagName, datafieldname) {
1325
+ if (tagName === "control") return `#${datafieldname}`;
1326
+ if (tagName === "tab" || tagName === "section")
1327
+ return `[data-name="${datafieldname}"]`;
1328
+ return null;
1123
1329
  }
1124
1330
  export {
1125
1331
  API_default as API,
1332
+ bindForm,
1126
1333
  createDOMNodeReference as createRef,
1127
1334
  waitFor
1128
1335
  };
@@ -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.0",
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",