powerpagestoolkit 2.7.0 → 2.7.101

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
@@ -85,10 +85,10 @@ import { createRef } from "powerpagestoolkit";
85
85
  Instantiate one, or multiple instances of a DOMNodeReference, and optionally configure advanced options
86
86
 
87
87
  ```javascript
88
- // Create a single reference
88
+ // Create a single reference (i.e. 'querySelector')
89
89
  const node = await createRef("#myElement");
90
90
 
91
- // Create multiple references
91
+ // Create multiple references (i.e. 'querySelectorAll')
92
92
  const nodes = await createRef(".my-class", { multiple: true });
93
93
 
94
94
  /******************/
@@ -142,76 +142,116 @@ node.on("click", function (e) {
142
142
  ...
143
143
  ```
144
144
 
145
- ##### Visibility Control
145
+ ##### Business Rule Application
146
146
 
147
- ```typescript
148
- // Basic visibility
149
- node.hide();
150
- node.show();
151
- ```
147
+ This utility provides a flexible way to dynamically control field visibility, requirement status, values, and enabled states based on dependencies within PowerPages forms.
152
148
 
153
- **_Advanced conditional rendering_**
149
+ _Method Signature:_
154
150
 
155
- 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
+ ```
156
158
 
157
- _Method signature:_
159
+ **BusinessRule Definition**
158
160
 
159
161
  ```typescript
160
- configureConditionalRendering(
162
+ interface IBusinessRule {
163
+ setVisibility?: [
161
164
  condition: () => boolean,
162
- dependencies?: DOMNodeReference[],
163
- clearValuesOnHide: boolean = true
164
- ): DOMNodeReference /* Instance of this returned
165
- for optional method chaining */
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
+ }
166
177
  ```
167
178
 
168
- _Example implementation:_
179
+ ##### Visibility Control
169
180
 
170
181
  ```typescript
171
- node.configureConditionalRendering(
172
- 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(
173
185
  {
174
- return otherNode.value === "some value";
186
+ setVisibility: [
187
+ () =>
188
+ businessTypeField.value === "Corporation" ||
189
+ businessTypeField.value === "LLC",
190
+ ],
175
191
  },
176
- [otherNode] /* Dependency array | if the values or visibility of these
177
- change, the function is re-evaluated */,
192
+ [businessTypeField] // Re-evaluate when businessTypeField changes
193
+ );
178
194
 
179
- true /* should the values in the targeted elements (this.element)
180
- 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
181
206
  );
182
207
  ```
183
208
 
184
209
  ##### Validation and Requirements
185
210
 
186
- This utility enhances PowerPages forms by adding dynamic field validation and conditional requirements based on other field values.
187
-
188
- _Method signature:_
189
-
190
211
  ```typescript
191
- configureValidationAndRequirements(
192
- isRequired: () => boolean,
193
- isValid: () => boolean,
194
- fieldDisplayName: string,
195
- dependencies: DOMNodeReference[]
196
- ): DOMNodeReference; /* instance of this is returned for optional
197
- 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
+ );
198
229
  ```
199
230
 
200
- _Example implementation:_
231
+ ##### Setting Field Values Conditionally
201
232
 
202
233
  ```typescript
203
- node.configureValidationAndRequirements(
204
- // Make field required only when "Yes" is checked
205
- () => dependentNode.yesRadio?.checked ?? false,
206
-
207
- // Basic validation: ensure field isn't empty
208
- function () {
209
- return this.value != null && this.value !== "";
234
+ // Set default industry value when 'businessTypeField' is 'Corporation'
235
+ industryField.applyBusinessRule(
236
+ {
237
+ setValue: [
238
+ () => businessTypeField.value === "Corporation",
239
+ "Corporate"
240
+ ],
210
241
  },
242
+ [businessTypeField] // Apply value when businessTypeField changes
243
+ );
244
+ ```
211
245
 
212
- "Contact Phone", // Shows in error message: "Contact Phone is required"
246
+ ##### Enabling and Disabling Fields
213
247
 
214
- [dependentNode] // Revalidate when dependentNode changes
248
+ ```typescript
249
+ // Disable 'taxIdField' when 'businessTypeField' is 'Individual'
250
+ taxIdField.applyBusinessRule(
251
+ {
252
+ setDisabled: () => businessTypeField.value === "Individual",
253
+ },
254
+ [businessTypeField] // Enable/disable when businessTypeField changes
215
255
  );
216
256
  ```
217
257
 
@@ -261,7 +301,6 @@ _Enabling/Disabling inputs_
261
301
  ```typescript
262
302
  node.disable();
263
303
  node.enable();
264
-
265
304
  ```
266
305
 
267
306
  ##### Label and Tooltip Management
@@ -295,6 +334,89 @@ title.addTooltip("This is an Example of a tooltip!", { color: "red" });
295
334
 
296
335
  ![Example](./assets//infoIconExample.gif)
297
336
 
337
+ Here's an improved markdown documentation with more comprehensive details:
338
+
339
+ ### BindForm Method
340
+
341
+ The `bindForm` method simplifies form element management in DataVerse by providing a semantic and efficient way to access form controls, sections, and tabs.
342
+
343
+ ##### Key Features
344
+
345
+ - Retrieves form definition directly from DataVerse
346
+ - Automatically generates references for:
347
+ - Controls
348
+ - Sections
349
+ - Tabs
350
+
351
+ ##### Element Types
352
+
353
+ | Element Type | Description | Accessibility |
354
+ | ------------ | ------------------------------------------- | ------------------------- |
355
+ | `control` | Includes all form fields and sub-grids | Accessed via logical name |
356
+ | `section` | Standard PowerApps form sections | Accessed via logical name |
357
+ | `tab` | Form tabs corresponding to PowerApps layout | Accessed via logical name |
358
+
359
+ ##### Usage Example
360
+
361
+ ```javascript
362
+ import { bindForm } from "powerpagestoolkit";
363
+
364
+ // Basic form binding
365
+ bindForm("form-guid").then((form) => {
366
+ // Access elements by their logical name
367
+ const nameField = form["name"];
368
+
369
+ // execute custom methods
370
+ nameField.applyBusinessRule(
371
+ {
372
+ setVisibility: [() => someNode.value === "desired value"],
373
+ },
374
+ [someNode]
375
+ );
376
+
377
+ // Or executes methods immediately upon accessing
378
+ form["phonenumber"].addTooltip("Example tooltip text");
379
+ });
380
+ ```
381
+
382
+ ##### Method Signature
383
+
384
+ ```typescript
385
+ /**
386
+ * Binds a form by its GUID and returns a collection of form elements
387
+ * @param formGuid Unique identifier for the form
388
+ * @returns Promise resolving to form element references
389
+ */
390
+ function bindForm(formGuid: string): Promise<DOMNodeReferenceArray & Record<string: DOMNodeReference>>;
391
+ ```
392
+
393
+ ##### Benefits
394
+
395
+ - Reduces code complexity
396
+ - Improves readability
397
+ - Provides type-safe access to form elements
398
+ - Supports flexible form interactions
399
+
400
+ ##### Best Practices
401
+
402
+ - Use logical names consistently
403
+ - Handle async nature of form binding
404
+ - Leverage TypeScript for enhanced type checking
405
+
406
+ ##### Error Handling
407
+
408
+ Ensure proper error handling for form binding:
409
+
410
+ ```javascript
411
+ bindForm("form-guid")
412
+ .then((form) => {
413
+ // Form processing
414
+ })
415
+ .catch((error) => {
416
+ console.error("Form binding failed", error);
417
+ });
418
+ ```
419
+
298
420
  ### DataVerse API
299
421
 
300
422
  Perform secure API calls to DataVerse from your PowerPages site. This method implements the shell deferred token to send requests with `__RequestVerificationToken`
package/dist/bundle.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/safeAjax.ts
1
+ // src/utils/safeAjax.ts
2
2
  function safeAjax(ajaxOptions) {
3
3
  const deferredAjax = $.Deferred();
4
4
  shell.getTokenDeferred().done(function(token) {
@@ -20,7 +20,7 @@ function safeAjax(ajaxOptions) {
20
20
  return deferredAjax.promise();
21
21
  }
22
22
 
23
- // src/API.ts
23
+ // src/core/API.ts
24
24
  var API = {
25
25
  /**
26
26
  * @param tableSetName The dataverse set name for the table that you are updating a record in
@@ -102,48 +102,14 @@ 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
-
139
- // src/waitFor.ts
140
- function waitFor(target, root = document, multiple = false, debounceTime) {
105
+ // src/utils/waitFor.ts
106
+ function waitFor(target, root = document, multiple = false, debounceTime2) {
141
107
  return new Promise((resolve, reject) => {
142
108
  if (multiple) {
143
109
  let timeout;
144
110
  const observedElements = [];
145
111
  const observedSet = /* @__PURE__ */ new Set();
146
- if (debounceTime < 1) {
112
+ if (debounceTime2 < 1) {
147
113
  return resolve(
148
114
  Array.from(root.querySelectorAll(target))
149
115
  );
@@ -164,11 +130,11 @@ function waitFor(target, root = document, multiple = false, debounceTime) {
164
130
  } else {
165
131
  reject(
166
132
  new Error(
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.`
133
+ `No elements found with target: "${target}" within ${debounceTime2 / 1e3} seconds. If the element you are expecting has not loaded yet, consider raising your timeout.`
168
134
  )
169
135
  );
170
136
  }
171
- }, debounceTime);
137
+ }, debounceTime2);
172
138
  });
173
139
  observer.observe(root, {
174
140
  childList: true,
@@ -188,10 +154,10 @@ function waitFor(target, root = document, multiple = false, debounceTime) {
188
154
  observer.disconnect();
189
155
  reject(
190
156
  new Error(
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.`
157
+ `Element not found by target: "${target}" within ${debounceTime2 / 1e3} second. If the element you are expecting has not loaded yet, consider raising your timeout.`
192
158
  )
193
159
  );
194
- }, debounceTime);
160
+ }, debounceTime2);
195
161
  if (target instanceof HTMLElement) {
196
162
  clearTimeout(timeout);
197
163
  return resolve(target);
@@ -211,7 +177,7 @@ function waitFor(target, root = document, multiple = false, debounceTime) {
211
177
  });
212
178
  }
213
179
 
214
- // src/createInfoElement.ts
180
+ // src/utils/createInfoElement.ts
215
181
  function CreateInfoEl(titleString, iconStyle) {
216
182
  if (typeof titleString !== "string") {
217
183
  throw new Error(
@@ -275,7 +241,22 @@ function CreateInfoEl(titleString, iconStyle) {
275
241
  return span;
276
242
  }
277
243
 
278
- // src/errors.ts
244
+ // src/constants/symbols.ts
245
+ var init = Symbol("__I");
246
+ var destroy = Symbol("__D");
247
+ var valueSync = Symbol("__VS");
248
+ var dateSync = Symbol("__DS");
249
+ var getElementValue = Symbol("__GEV");
250
+ var attachVisibilityController = Symbol("__AVC");
251
+ var attachRadioButtons = Symbol("__ARB");
252
+ var bindMethods = Symbol("__B");
253
+ var debounceTime = Symbol("__DT");
254
+ var observers = Symbol("__O");
255
+ var boundEventListeners = Symbol("__BEV");
256
+ var isValidFormElement = Symbol("__VFE");
257
+ var registerEventListener = Symbol("__REV");
258
+
259
+ // src/errors/errors.ts
279
260
  var DOMNodeInitializationError = class extends Error {
280
261
  constructor(instance, error) {
281
262
  super(
@@ -303,29 +284,27 @@ var ValidationConfigError = class extends Error {
303
284
  }
304
285
  };
305
286
 
306
- // src/DOMNodeReference.ts
307
- var _init = Symbol("_I");
308
- var _destroy = Symbol("_D");
309
- var _valueSync = Symbol("_VS");
310
- var _dateSync = Symbol("_DS");
311
- var _getElementValue = Symbol("_GEV");
312
- var _updateRadioGroup = Symbol("_URG");
313
- var _attachVisibilityController = Symbol("_AVC");
314
- var _attachRadioButtons = Symbol("_ARB");
315
- var _bindMethods = Symbol("_B");
316
- var _debounceTime = Symbol("DT");
317
- var _observers = Symbol("O");
318
- var _boundEventListeners = Symbol("BEV");
287
+ // src/core/DOMNodeReference.ts
288
+ var eventMapping = {
289
+ checkbox: "click",
290
+ radio: "click",
291
+ select: "change",
292
+ "select-multiple": "change",
293
+ textarea: "keyup"
294
+ // Add other input types as needed
295
+ };
319
296
  var DOMNodeReference = class _DOMNodeReference {
320
297
  // properties initialized in the constructor
321
298
  target;
322
299
  logicalName;
323
300
  root;
324
- [_debounceTime];
301
+ [debounceTime];
325
302
  isLoaded;
326
303
  defaultDisplay;
327
- [_observers] = [];
328
- [_boundEventListeners] = [];
304
+ [observers] = [];
305
+ [boundEventListeners] = [];
306
+ isRadio = false;
307
+ radioType = null;
329
308
  /**
330
309
  * The value of the element that this node represents
331
310
  * stays in syncs with the live DOM elements?.,m via event handler
@@ -339,7 +318,7 @@ var DOMNodeReference = class _DOMNodeReference {
339
318
  */
340
319
  /******/
341
320
  /******/
342
- constructor(target, root = document.body, debounceTime) {
321
+ constructor(target, root = document.body, debounceTime2) {
343
322
  this.target = target;
344
323
  if (typeof target === "string") {
345
324
  let lName = null;
@@ -354,32 +333,39 @@ var DOMNodeReference = class _DOMNodeReference {
354
333
  this.logicalName = (lName || target).replace(/[#\[\]]/g, "");
355
334
  }
356
335
  this.root = root;
357
- this[_debounceTime] = debounceTime;
336
+ this[debounceTime] = debounceTime2;
358
337
  this.isLoaded = false;
359
338
  this.defaultDisplay = "";
360
339
  this.value = null;
361
- this[_bindMethods]();
340
+ this[bindMethods]();
362
341
  }
363
- async [_init]() {
342
+ async [init]() {
364
343
  try {
365
344
  if (this.target instanceof HTMLElement) {
366
345
  this.element = this.target;
367
346
  } else {
368
- this.element = await waitFor(this.target, this.root, false, this[_debounceTime]);
347
+ this.element = await waitFor(
348
+ this.target,
349
+ this.root,
350
+ false,
351
+ this[debounceTime]
352
+ );
369
353
  }
370
354
  if (!this.element) {
371
355
  throw new DOMNodeNotFoundError(this);
372
356
  }
373
- if (this.element.classList.contains("boolean-radio")) {
374
- await this[_attachRadioButtons]();
357
+ if (this.element.id && this.element.querySelectorAll(
358
+ `#${this.element.id} > input[type="radio"]`
359
+ ).length > 0) {
360
+ await this[attachRadioButtons]();
375
361
  }
376
- this[_valueSync]();
377
- this[_attachVisibilityController]();
362
+ this[valueSync]();
363
+ this[attachVisibilityController]();
378
364
  this.defaultDisplay = this.visibilityController.style.display;
379
365
  const observer = new MutationObserver((mutations) => {
380
366
  for (const mutation of mutations) {
381
367
  if (Array.from(mutation.removedNodes).includes(this.element)) {
382
- this[_destroy]();
368
+ this[destroy]();
383
369
  observer.disconnect();
384
370
  break;
385
371
  }
@@ -395,40 +381,27 @@ var DOMNodeReference = class _DOMNodeReference {
395
381
  throw new DOMNodeInitializationError(this, errorMessage);
396
382
  }
397
383
  }
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
- };
406
384
  /**
407
385
  * Initializes value synchronization with appropriate event listeners
408
386
  * based on element type.
409
- * @private
410
387
  */
411
- async [_valueSync]() {
388
+ async [valueSync]() {
412
389
  try {
390
+ if (!this[isValidFormElement](this.element)) return;
413
391
  this.updateValue();
414
392
  let eventType;
415
393
  if (this.element instanceof HTMLSelectElement) {
416
394
  eventType = "change";
417
395
  } else if (this.element instanceof HTMLInputElement) {
418
- eventType = this.eventMapping[this.element.type] ?? "input";
396
+ eventType = eventMapping[this.element.type] ?? "input";
419
397
  } else if (this.element instanceof HTMLTextAreaElement) {
420
- eventType = this.eventMapping[this.element.type] ?? "input";
398
+ eventType = eventMapping[this.element.type] ?? "input";
421
399
  } else {
422
400
  eventType = "input";
423
401
  }
424
- this.element.addEventListener(eventType, this.updateValue);
425
- this[_boundEventListeners].push({
426
- element: this.element,
427
- handler: this.updateValue,
428
- event: eventType
429
- });
402
+ this[registerEventListener](this.element, eventType, this.updateValue);
430
403
  if (this.element instanceof HTMLInputElement && this.element.dataset.type === "date") {
431
- await this[_dateSync](this.element);
404
+ await this[dateSync](this.element);
432
405
  }
433
406
  } catch (error) {
434
407
  throw new DOMNodeInitializationError(
@@ -437,28 +410,44 @@ var DOMNodeReference = class _DOMNodeReference {
437
410
  );
438
411
  }
439
412
  }
440
- async [_dateSync](element) {
413
+ [isValidFormElement](element) {
414
+ return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSpanElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement;
415
+ }
416
+ [registerEventListener](element, eventType, handler) {
417
+ element.addEventListener(eventType, handler);
418
+ this[boundEventListeners].push({
419
+ element,
420
+ handler,
421
+ event: eventType
422
+ });
423
+ }
424
+ async [dateSync](element) {
441
425
  const parentElement = element.parentElement;
442
426
  if (!parentElement) {
443
427
  throw new Error("Date input must have a parent element");
444
428
  }
445
- const dateNode = await waitFor("[data-date-format]", parentElement, false, 1500);
446
- dateNode.addEventListener("select", this.updateValue);
447
- const _handler = this.updateValue;
448
- this[_boundEventListeners].push({
449
- element: dateNode,
450
- handler: _handler,
451
- event: "select"
452
- });
429
+ const dateNode = await waitFor(
430
+ "[data-date-format]",
431
+ parentElement,
432
+ false,
433
+ 1500
434
+ );
435
+ this[registerEventListener](dateNode, "select", this.updateValue);
453
436
  }
454
437
  /**
455
438
  * Gets the current value of the element based on its type
456
- * @private
439
+ * @protected
457
440
  * @returns Object containing value and optional checked state
458
441
  */
459
- [_getElementValue]() {
442
+ [getElementValue]() {
460
443
  const input = this.element;
461
444
  const select = this.element;
445
+ if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
446
+ return {
447
+ value: this.yesRadio.checked,
448
+ checked: this.yesRadio.checked
449
+ };
450
+ }
462
451
  switch (input.type) {
463
452
  case "checkbox":
464
453
  case "radio":
@@ -489,19 +478,7 @@ var DOMNodeReference = class _DOMNodeReference {
489
478
  };
490
479
  }
491
480
  }
492
- /**
493
- * Updates related radio buttons if this is part of a radio group
494
- * @private
495
- */
496
- [_updateRadioGroup]() {
497
- if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
498
- this.yesRadio.updateValue();
499
- this.noRadio?.updateValue();
500
- this.checked = this.yesRadio.checked;
501
- this.value = this.yesRadio.checked;
502
- }
503
- }
504
- [_attachVisibilityController]() {
481
+ [attachVisibilityController]() {
505
482
  this.visibilityController = this.element;
506
483
  if (this.element.tagName === "TABLE") {
507
484
  const fieldset = this.element.closest("fieldset");
@@ -524,11 +501,26 @@ var DOMNodeReference = class _DOMNodeReference {
524
501
  }
525
502
  }
526
503
  }
527
- async [_attachRadioButtons]() {
528
- this.yesRadio = await createDOMNodeReference(`#${this.element.id}_1`);
529
- this.noRadio = await createDOMNodeReference(`#${this.element.id}_0`);
504
+ async [attachRadioButtons]() {
505
+ if (!this.element) {
506
+ console.error(
507
+ "'this.element' not found: cannot attach radio buttons for ",
508
+ this.target
509
+ );
510
+ return;
511
+ }
512
+ this.yesRadio = await createDOMNodeReference('input[type="radio"][value="1"]', {
513
+ root: this.element
514
+ });
515
+ this.yesRadio.isRadio = true;
516
+ this.yesRadio.radioType = "truthy";
517
+ this.noRadio = await createDOMNodeReference('input[type="radio"][value="0"]', {
518
+ root: this.element
519
+ });
520
+ this.noRadio.isRadio = true;
521
+ this.noRadio.radioType = "falsy";
530
522
  }
531
- [_bindMethods]() {
523
+ [bindMethods]() {
532
524
  const prototype = Object.getPrototypeOf(this);
533
525
  for (const key of Object.getOwnPropertyNames(prototype)) {
534
526
  const value = this[key];
@@ -537,15 +529,15 @@ var DOMNodeReference = class _DOMNodeReference {
537
529
  }
538
530
  }
539
531
  }
540
- [_destroy]() {
541
- this[_boundEventListeners]?.forEach((binding) => {
532
+ [destroy]() {
533
+ this[boundEventListeners]?.forEach((binding) => {
542
534
  binding.element?.removeEventListener(binding.event, binding.handler);
543
535
  });
544
- this[_observers]?.forEach((observer) => {
536
+ this[observers]?.forEach((observer) => {
545
537
  observer.disconnect();
546
538
  });
547
- this.yesRadio?.[_destroy]();
548
- this.noRadio?.[_destroy]();
539
+ this.yesRadio?.[destroy]();
540
+ this.noRadio?.[destroy]();
549
541
  this.yesRadio = null;
550
542
  this.noRadio = null;
551
543
  this.isLoaded = false;
@@ -555,13 +547,19 @@ var DOMNodeReference = class _DOMNodeReference {
555
547
  * Updates the value and checked state based on element type
556
548
  * @public
557
549
  */
558
- updateValue() {
559
- const elementValue = this[_getElementValue]();
550
+ updateValue(e) {
551
+ if (e) {
552
+ e.stopPropagation();
553
+ }
554
+ if (this.yesRadio && this.noRadio) {
555
+ this.yesRadio.updateValue();
556
+ this.noRadio.updateValue();
557
+ }
558
+ const elementValue = this[getElementValue]();
560
559
  this.value = elementValue.value;
561
560
  if (elementValue.checked !== void 0) {
562
561
  this.checked = elementValue.checked;
563
562
  }
564
- this[_updateRadioGroup]();
565
563
  }
566
564
  /**
567
565
  * Sets up an event listener based on the specified event type, executing the specified
@@ -577,14 +575,11 @@ var DOMNodeReference = class _DOMNodeReference {
577
575
  `Argument "eventHandler" must be a Function. Received: ${typeof eventHandler}`
578
576
  );
579
577
  }
580
- this.element.addEventListener(eventType, eventHandler.bind(this));
581
- const _element = this.element;
582
- const _handler = eventHandler;
583
- this[_boundEventListeners].push({
584
- element: _element,
585
- handler: _handler,
586
- event: eventType
587
- });
578
+ this[registerEventListener](
579
+ this.element,
580
+ eventType,
581
+ eventHandler.bind(this)
582
+ );
588
583
  return this;
589
584
  }
590
585
  /**
@@ -632,9 +627,9 @@ var DOMNodeReference = class _DOMNodeReference {
632
627
  if (this.element instanceof HTMLSelectElement) {
633
628
  eventType = "change";
634
629
  } else if (this.element instanceof HTMLInputElement) {
635
- eventType = this.eventMapping[this.element.type] ?? "input";
630
+ eventType = eventMapping[this.element.type] ?? "input";
636
631
  } else if (this.element instanceof HTMLTextAreaElement) {
637
- eventType = this.eventMapping[this.element.type] ?? "input";
632
+ eventType = eventMapping[this.element.type] ?? "input";
638
633
  } else {
639
634
  eventType = "input";
640
635
  }
@@ -711,7 +706,9 @@ var DOMNodeReference = class _DOMNodeReference {
711
706
  );
712
707
  if (childInputs.length > 0) {
713
708
  const promises = childInputs.map(async (input) => {
714
- const inputRef = await createDOMNodeReference(input, { multiple: false });
709
+ const inputRef = await createDOMNodeReference(input, {
710
+ multiple: false
711
+ });
715
712
  return inputRef.clearValue();
716
713
  });
717
714
  await Promise.all(promises);
@@ -851,7 +848,7 @@ var DOMNodeReference = class _DOMNodeReference {
851
848
  * @returns - Instance of this [provides option to method chain]
852
849
  */
853
850
  uncheckRadios() {
854
- if (this.yesRadio && this.noRadio) {
851
+ if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
855
852
  this.yesRadio.element.checked = false;
856
853
  this.noRadio.element.checked = false;
857
854
  } else {
@@ -916,7 +913,7 @@ var DOMNodeReference = class _DOMNodeReference {
916
913
  evaluationfunction: () => {
917
914
  const isFieldRequired = isRequired.bind(this)();
918
915
  const isFieldVisible = window.getComputedStyle(this.visibilityController).display !== "none";
919
- return !isFieldRequired || !isFieldVisible || isValid.bind(this)(isRequired.bind(this));
916
+ return !isFieldRequired || !isFieldVisible || isValid.bind(this)();
920
917
  }
921
918
  });
922
919
  Page_Validators.push(newValidator);
@@ -928,7 +925,8 @@ var DOMNodeReference = class _DOMNodeReference {
928
925
  );
929
926
  }
930
927
  if (rule.setValue) {
931
- const [condition, value] = rule.setValue;
928
+ let [condition, value] = rule.setValue;
929
+ if (value instanceof Function) value = value();
932
930
  if (condition.bind(this)()) {
933
931
  this.setValue.bind(this)(value);
934
932
  }
@@ -945,7 +943,7 @@ var DOMNodeReference = class _DOMNodeReference {
945
943
  }
946
944
  }
947
945
  if (rule.setDisabled) {
948
- const [condition] = rule.setDisabled;
946
+ const condition = rule.setDisabled;
949
947
  condition.bind(this)() ? this.disable() : this.enable();
950
948
  if (dependencies.length) {
951
949
  this._configDependencyTracking(
@@ -1069,7 +1067,7 @@ var DOMNodeReference = class _DOMNodeReference {
1069
1067
  }
1070
1068
  /**
1071
1069
  * Sets up tracking for dependencies using both event listeners and mutation observers.
1072
- * @private
1070
+ * @protected
1073
1071
  * @param handler The function to execute when dependencies change
1074
1072
  * @param dependencies Array of dependent DOM nodes to track
1075
1073
  * @param options Additional configuration options. clearValuesOnHide defaults to false.
@@ -1105,19 +1103,9 @@ var DOMNodeReference = class _DOMNodeReference {
1105
1103
  this.clearValue();
1106
1104
  }
1107
1105
  };
1108
- dep.on("change", handleChange);
1109
- this[_boundEventListeners].push({
1110
- element: dep.element,
1111
- event: "change",
1112
- handler: handleChange
1113
- });
1106
+ this[registerEventListener](dep.element, "change", handleChange);
1114
1107
  if (trackInputEvents) {
1115
- dep.on("input", handleChange);
1116
- this[_boundEventListeners].push({
1117
- element: dep.element,
1118
- event: "input",
1119
- handler: handleChange
1120
- });
1108
+ this[registerEventListener](dep.element, "input", handleChange);
1121
1109
  }
1122
1110
  if (observeVisibility) {
1123
1111
  const observer = new MutationObserver(() => {
@@ -1133,12 +1121,12 @@ var DOMNodeReference = class _DOMNodeReference {
1133
1121
  attributeFilter: ["style"],
1134
1122
  subtree: false
1135
1123
  });
1136
- this[_observers].push(observer);
1124
+ this[observers].push(observer);
1137
1125
  }
1138
1126
  if (trackRadioButtons && dep.yesRadio && dep.noRadio) {
1139
1127
  [dep.yesRadio, dep.noRadio].forEach((radio) => {
1140
1128
  radio.on("change", handleChange);
1141
- this[_boundEventListeners].push({
1129
+ this[boundEventListeners].push({
1142
1130
  element: radio.element,
1143
1131
  event: "change",
1144
1132
  handler: handleChange
@@ -1190,11 +1178,47 @@ var DOMNodeReference = class _DOMNodeReference {
1190
1178
  subtree: true,
1191
1179
  childList: true
1192
1180
  });
1193
- this[_observers].push(observer);
1181
+ this[observers].push(observer);
1182
+ }
1183
+ };
1184
+
1185
+ // src/core/DOMNodeReferenceArray.ts
1186
+ var DOMNodeReferenceArray = class extends Array {
1187
+ /**
1188
+ * Hides all the containers of the DOMNodeReference instances in the array.
1189
+ */
1190
+ hideAll() {
1191
+ this.forEach((instance) => instance.hide());
1192
+ return this;
1193
+ }
1194
+ /**
1195
+ * Shows all the containers of the DOMNodeReference instances in the array.
1196
+ */
1197
+ showAll() {
1198
+ this.forEach((instance) => instance.show());
1199
+ return this;
1194
1200
  }
1195
1201
  };
1196
1202
 
1197
- // src/createDOMNodeReferences.ts
1203
+ // src/utils/enhanceArray.ts
1204
+ function enhanceArray(array) {
1205
+ const enhancedArray = new DOMNodeReferenceArray(...array);
1206
+ return new Proxy(enhancedArray, {
1207
+ get(target, prop, receiver) {
1208
+ if (prop in target) {
1209
+ return Reflect.get(target, prop, receiver);
1210
+ }
1211
+ if (typeof prop === "string") {
1212
+ return target.find(
1213
+ (instance) => instance.target.toString().replace(/[#\[\]]/g, "") === prop || instance.logicalName === prop
1214
+ );
1215
+ }
1216
+ return void 0;
1217
+ }
1218
+ });
1219
+ }
1220
+
1221
+ // src/core/createDOMNodeReferences.ts
1198
1222
  async function createDOMNodeReference(target, options = {
1199
1223
  multiple: false,
1200
1224
  root: document.body,
@@ -1219,14 +1243,14 @@ async function createDOMNodeReference(target, options = {
1219
1243
  const initializedElements = await Promise.all(
1220
1244
  elements.map(async (element) => {
1221
1245
  const instance2 = new DOMNodeReference(element, root, timeout);
1222
- await instance2[_init]();
1246
+ await instance2[init]();
1223
1247
  return new Proxy(instance2, createProxyHandler());
1224
1248
  })
1225
1249
  );
1226
1250
  return enhanceArray(initializedElements);
1227
1251
  }
1228
1252
  const instance = new DOMNodeReference(target, root, timeout);
1229
- await instance[_init]();
1253
+ await instance[init]();
1230
1254
  return new Proxy(instance, createProxyHandler());
1231
1255
  } catch (e) {
1232
1256
  throw new Error(e);
@@ -1275,7 +1299,7 @@ function createProxyHandler() {
1275
1299
  };
1276
1300
  }
1277
1301
 
1278
- // src/bindForm.ts
1302
+ // src/core/bindForm.ts
1279
1303
  async function bindForm(formId) {
1280
1304
  try {
1281
1305
  const form = await API_default.getRecord("systemforms", formId);
@@ -0,0 +1,2 @@
1
+ declare const eventMapping: Record<string, keyof HTMLElementEventMap>;
2
+ export default eventMapping;
@@ -0,0 +1,13 @@
1
+ export declare const init: unique symbol;
2
+ export declare const destroy: unique symbol;
3
+ export declare const valueSync: unique symbol;
4
+ export declare const dateSync: unique symbol;
5
+ export declare const getElementValue: unique symbol;
6
+ export declare const attachVisibilityController: unique symbol;
7
+ export declare const attachRadioButtons: unique symbol;
8
+ export declare const bindMethods: unique symbol;
9
+ export declare const debounceTime: unique symbol;
10
+ export declare const observers: unique symbol;
11
+ export declare const boundEventListeners: unique symbol;
12
+ export declare const isValidFormElement: unique symbol;
13
+ export declare const registerEventListener: unique symbol;
@@ -1,54 +1,15 @@
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
- }
31
- export declare const _init: unique symbol;
32
- declare const _destroy: unique symbol;
33
- declare const _valueSync: unique symbol;
34
- declare const _dateSync: unique symbol;
35
- declare const _getElementValue: unique symbol;
36
- declare const _updateRadioGroup: unique symbol;
37
- declare const _attachVisibilityController: unique symbol;
38
- declare const _attachRadioButtons: unique symbol;
39
- declare const _bindMethods: unique symbol;
40
- declare const _debounceTime: unique symbol;
41
- declare const _observers: unique symbol;
42
- declare const _boundEventListeners: unique symbol;
1
+ import * as s from "@/constants/symbols.js";
43
2
  export default class DOMNodeReference {
44
3
  target: Element | string;
45
4
  logicalName?: string;
46
5
  root: Element;
47
- private [_debounceTime];
48
- private isLoaded;
49
- private defaultDisplay;
50
- private [_observers];
51
- private [_boundEventListeners];
6
+ protected [s.debounceTime]: number;
7
+ protected isLoaded: boolean;
8
+ protected defaultDisplay: string;
9
+ protected [s.observers]: Array<MutationObserver>;
10
+ protected [s.boundEventListeners]: Array<IBoundEventListener>;
11
+ protected isRadio: boolean;
12
+ protected radioType: RadioType | null;
52
13
  /**
53
14
  * The value of the element that this node represents
54
15
  * stays in syncs with the live DOM elements?.,m via event handler
@@ -60,7 +21,7 @@ export default class DOMNodeReference {
60
21
  * or access properties not available through this class.
61
22
  */
62
23
  element: HTMLElement;
63
- private visibilityController;
24
+ protected visibilityController: HTMLElement;
64
25
  checked: boolean;
65
26
  /**
66
27
  * Represents the 'yes' option of a boolean radio field.
@@ -81,35 +42,30 @@ export default class DOMNodeReference {
81
42
  * Defaults to 'document.body'
82
43
  */
83
44
  /******/ /******/ constructor(target: Element | string, root: Element | undefined, debounceTime: number);
84
- [_init](): Promise<void>;
85
- private eventMapping;
45
+ [s.init](): Promise<void>;
86
46
  /**
87
47
  * Initializes value synchronization with appropriate event listeners
88
48
  * based on element type.
89
- * @private
90
49
  */
91
- private [_valueSync];
92
- private [_dateSync];
50
+ protected [s.valueSync](): Promise<void>;
51
+ protected [s.isValidFormElement](element: Element): element is FormElement;
52
+ protected [s.registerEventListener](element: Element, eventType: keyof HTMLElementEventMap, handler: (e: Event) => unknown): void;
53
+ protected [s.dateSync](element: HTMLInputElement): Promise<void>;
93
54
  /**
94
55
  * Gets the current value of the element based on its type
95
- * @private
56
+ * @protected
96
57
  * @returns Object containing value and optional checked state
97
58
  */
98
- private [_getElementValue];
99
- /**
100
- * Updates related radio buttons if this is part of a radio group
101
- * @private
102
- */
103
- private [_updateRadioGroup];
104
- private [_attachVisibilityController];
105
- private [_attachRadioButtons];
106
- private [_bindMethods];
107
- private [_destroy];
59
+ protected [s.getElementValue](): ElementValue;
60
+ protected [s.attachVisibilityController](): void;
61
+ protected [s.attachRadioButtons](): Promise<void>;
62
+ protected [s.bindMethods](): void;
63
+ protected [s.destroy](): void;
108
64
  /**
109
65
  * Updates the value and checked state based on element type
110
66
  * @public
111
67
  */
112
- updateValue(): void;
68
+ updateValue(e?: Event): void;
113
69
  /**
114
70
  * Sets up an event listener based on the specified event type, executing the specified
115
71
  * event handler
@@ -263,13 +219,18 @@ export default class DOMNodeReference {
263
219
  configureValidationAndRequirements(isRequired: () => boolean, isValid: () => boolean, fieldDisplayName: string, dependencies: Array<DOMNodeReference>): DOMNodeReference;
264
220
  /**
265
221
  * Sets up tracking for dependencies using both event listeners and mutation observers.
266
- * @private
222
+ * @protected
267
223
  * @param handler The function to execute when dependencies change
268
224
  * @param dependencies Array of dependent DOM nodes to track
269
225
  * @param options Additional configuration options. clearValuesOnHide defaults to false.
270
226
  * all other options defaults to true
271
227
  */
272
- private _configDependencyTracking;
228
+ protected _configDependencyTracking(handler: () => void, dependencies: Array<DOMNodeReference>, options?: {
229
+ clearValuesOnHide?: boolean;
230
+ observeVisibility?: boolean;
231
+ trackInputEvents?: boolean;
232
+ trackRadioButtons?: boolean;
233
+ }): void;
273
234
  /**
274
235
  * Sets the required level for the field by adding or removing the "required-field" class on the label.
275
236
  *
@@ -287,4 +248,3 @@ export default class DOMNodeReference {
287
248
  */
288
249
  onceLoaded(callback: (instance: DOMNodeReference) => any): any;
289
250
  }
290
- export {};
@@ -1,5 +1,5 @@
1
1
  import DOMNodeReference from "./DOMNodeReference.js";
2
- export declare class DOMNodeReferenceArray extends Array<DOMNodeReference> {
2
+ export default class DOMNodeReferenceArray extends Array<DOMNodeReference> {
3
3
  /**
4
4
  * Hides all the containers of the DOMNodeReference instances in the array.
5
5
  */
@@ -9,4 +9,3 @@ export declare class DOMNodeReferenceArray extends Array<DOMNodeReference> {
9
9
  */
10
10
  showAll(this: DOMNodeReferenceArray): DOMNodeReferenceArray;
11
11
  }
12
- export declare function enhanceArray<T extends string>(array: DOMNodeReference[]): DOMNodeReferenceArray & Record<T, DOMNodeReference>;
@@ -1,5 +1,5 @@
1
1
  import DOMNodeReference from "./DOMNodeReference.js";
2
- import { DOMNodeReferenceArray } from "./DOMNodeReferenceArray.js";
2
+ import DOMNodeReferenceArray from "./DOMNodeReferenceArray.js";
3
3
  /**
4
4
  * @function
5
5
  * Get all controls related to the form for manipulating with the
@@ -0,0 +1,21 @@
1
+ import DOMNodeReference from "./DOMNodeReference.js";
2
+ import DOMNodeReferenceArray from "./DOMNodeReferenceArray.js";
3
+ export default function createDOMNodeReference<T extends string>(target: Element, options?: ICreationOptions): Promise<DOMNodeReference>;
4
+ export default function createDOMNodeReference(target: string, options?: Omit<ICreationOptions, "multiple"> & {
5
+ /**
6
+ * Should this call return an array of instantiated references, or just a single instance?
7
+ * Defaults to false, returning a single instance.
8
+ */
9
+ multiple?: false;
10
+ }): Promise<DOMNodeReference>;
11
+ export default function createDOMNodeReference<T extends string>(target: string, options?: Omit<ICreationOptions, "multiple"> & {
12
+ /**
13
+ * Should this call return an array of instantiated references, or just a single instance?
14
+ * Defaults to false, returning a single instance.
15
+ */
16
+ multiple?: true;
17
+ }): Promise<DOMNodeReferenceArray>;
18
+ export declare function validateOptions(options: any): void;
19
+ export declare function createProxyHandler(): {
20
+ get: (target: DOMNodeReference, prop: string | symbol) => any;
21
+ };
@@ -1,4 +1,4 @@
1
- import DOMNodeReference from "./DOMNodeReference.js";
1
+ import DOMNodeReference from "../core/DOMNodeReference.js";
2
2
  export declare class DOMNodeInitializationError extends Error {
3
3
  constructor(instance: DOMNodeReference, error: string);
4
4
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./style.css";
2
- import API from "./API.js";
3
- import createRef from "./createDOMNodeReferences.js";
4
- import waitFor from "./waitFor.js";
5
- import bindForm from "./bindForm.js";
2
+ import API from "./core/API.js";
3
+ import createRef from "./core/createDOMNodeReferences.js";
4
+ import waitFor from "./utils/waitFor.js";
5
+ import bindForm from "./core/bindForm.js";
6
6
  export { API, createRef, waitFor, bindForm };
@@ -0,0 +1,5 @@
1
+ import DOMNodeReference from "@/core/DOMNodeReference.js";
2
+ export default abstract class ReferenceManager {
3
+ static instances: Set<DOMNodeReference>;
4
+ static getInstance<T>(target: T): DOMNodeReference | undefined;
5
+ }
@@ -0,0 +1,3 @@
1
+ import DOMNodeReference from "@/core/DOMNodeReference.js";
2
+ import DOMNodeReferenceArray from "@/core/DOMNodeReferenceArray.js";
3
+ export default function enhanceArray<T extends string>(array: DOMNodeReference[]): DOMNodeReferenceArray & Record<T, DOMNodeReference>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "powerpagestoolkit",
3
- "version": "2.7.0",
3
+ "version": "2.7.101",
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
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/dist/List.d.ts DELETED
@@ -1,29 +0,0 @@
1
- /**
2
- * so far this whole thing is a moot point
3
- * Microsoft provides no way to get important specific information
4
- * about the records represented by each 'row' in a list
5
- * rendering this effort particularly useless
6
- *
7
- * Saving for in case things change in the future and this
8
- * could be re-factored/extended to provide some usable value
9
- */
10
- export declare const _init: symbol;
11
- /**
12
- * Provides information about how to target elements in
13
- * the construction of the list
14
- */
15
- interface ListOptions {
16
- containerSelector: string;
17
- rowSelector: string;
18
- cellSelector: string;
19
- }
20
- interface ListItem extends Array<Element> {
21
- }
22
- export default class List {
23
- [x: symbol]: () => Promise<List>;
24
- items: ListItem[];
25
- private options;
26
- private container;
27
- constructor(options?: Partial<ListOptions>);
28
- }
29
- export {};
@@ -1,36 +0,0 @@
1
- import { DOMNodeReferenceArray } from "./DOMNodeReferenceArray.js";
2
- import DOMNodeReference from "./DOMNodeReference.js";
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
- */
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?: 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;
28
- }): 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 {};
File without changes
File without changes
File without changes