A TypeScript/JavaScript utility package for seamless DOM manipulation and DataVerse API interactions in PowerPages applications. This toolkit provides robust DOM element management and standardized DataVerse CRUD operations with full TypeScript support.
- Powerful DOM element manipulation and reference management
- Type-safe DataVerse API operations
- Automatic value synchronization for form elements
- Advanced conditional rendering and validation
- Radio button and checkbox handling
- Event management with proper TypeScript typing
- Mutation observer integration for dynamic content
- Tooltip and label management utilities
npm install powerpagestoolkit
A powerful class for managing DOM elements with automatic value synchronization and event handling.
DOMNodeReferences are instantiated with the help of the following factory function: createRef
createRef(
target: HTMLElement | string,
options: {
multiple: (() => boolean) | boolean = false,
root: HTMLElement,
timeout: number
}
): Promise<DOMNodeReference | DOMNodeReference[]>;
createRef takes two main arguments:
Property | Type | Details |
---|---|---|
target |
|
Use standard querySelector syntax to target an element, or elements in the DOM, or pass in an instance of the element itself to create a reference.
|
options |
|
Provides advanced configurations for niche scenarios, such as async DOM element loading, returning arrays of elements, or specifying the parent to search within for the target node. |
Import the utility function for creating DOMNodeReference(s)
import { createRef } from "powerpagestoolkit";
Instantiate one, or multiple instances of a DOMNodeReference, and optionally configure advanced options
// Create a single reference (i.e. 'querySelector')
const node = await createRef("#myElement");
// Create multiple references (i.e. 'querySelectorAll')
const nodes = await createRef(".my-class", { multiple: true });
/******************/
// ADVANCED OPTIONS
// in the event that you need to be more granular with how you are targeting
// and retrieving elements, there are additional options
// If the node you are targeting is not available at the initial execution
// of the script, set a timeout for 2 seconds
const node2 = await createRef("#target", { timeout: 2000 });
// need to target a node within a specific node? use that node as the root
const otherElement = document.getElementById("id");
const node3 = await createRef("#target", { root: otherElement });
// implement all options:
const nodes2 = await createRef("#target", {
multiple: true,
timeout: 4000,
root: otherElement,
});
Property | Type | Description |
---|---|---|
element | HTMLElement | The referenced DOM element |
value | any | Current synchronized value of the element |
isLoaded | boolean | Element load status |
target | HTMLElement | string | Original target selector or element |
yesRadio | DOMNodeReference | null | Reference to 'yes' radio (for boolean fields) |
noRadio | DOMNodeReference | null | Reference to 'no' radio (for boolean fields) |
checked | boolean | Checkbox/radio checked state |
// Add event listener with proper 'this' context
// uses standard eventListener API, and so supports all DOM events
node.on("change", function (e) {
console.log("Current value:", this.value);
});
node.on("click", function (e) {
console.log(this, " has been clicked");
});
...
This utility provides a flexible way to dynamically control field visibility, requirement status, values, and enabled states based on dependencies within PowerPages forms.
Method Signature:
applyBusinessRule(
rule: IBusinessRule,
dependencies: DOMNodeReference[]
): DOMNodeReference; /* Instance of this is returned for optional
method chaining */
BusinessRule Definition
interface IBusinessRule {
setVisibility?: [
condition: () => boolean,
clearValuesOnHide?: boolean = true
];
setRequired?: [
isRequired: () => boolean,
isValid: () => boolean
];
setValue?: [
condition: () => boolean,
value: () => any | any
];
setDisabled?: () => boolean;
}
// Show the 'taxIdField' only when
// 'businessTypeField' is set to 'Corporation' or 'LLC'
taxIdField.applyBusinessRule(
{
setVisibility: [
() =>
businessTypeField.value === "Corporation" ||
businessTypeField.value === "LLC",
],
},
[businessTypeField] // Re-evaluate when businessTypeField changes
);
// Optionally disable 'clearValuesOnHide':
taxIdField.applyBusinessRule(
{
setVisibility: [
() =>
businessTypeField.value === "Corporation" ||
businessTypeField.value === "LLC",
false, // defaults to true. False will prevent the fields from losing it's value if it is hidden
],
},
[businessTypeField] // Re-evaluate when businessTypeField changes
);
// Require 'taxIdField' when 'businessTypeField' is 'Corporation' or 'LLC'
taxIdField.applyBusinessRule(
{
setRequired: [
function () {
return (
businessTypeField.value === "Corporation" ||
businessTypeField.value === "LLC"
);
},
function () {
return this.value != null && this.value !== "";
},
],
},
[businessTypeField] // Revalidate when businessTypeField changes
);
// Set default industry value when 'businessTypeField' is 'Corporation'
industryField.applyBusinessRule(
{
setValue: [
() => businessTypeField.value === "Corporation",
"Corporate"
],
},
[businessTypeField] // Apply value when businessTypeField changes
);
// Disable 'taxIdField' when 'businessTypeField' is 'Individual'
taxIdField.applyBusinessRule(
{
setDisabled: () => businessTypeField.value === "Individual",
},
[businessTypeField] // Enable/disable when businessTypeField changes
);
Value management
// set a static value
node.setValue("new value");
// or set a value by using some sort of logic
node.setValue(() => {
if (true) {
return "value";
} else return "default";
});
// Sync with DOM
node.updateValue();
// Clear the value for both the instance and the target element
node.clearValue();
Content manipulation
node.setInnerHTML("<span>New content</span>");
node.append(childElement);
node.prepend(headerElement);
node.after(siblingElement);
node.before(labelElement);
Styling
node.setStyle({
display: "block",
color: "red",
});
Enabling/Disabling inputs
node.disable();
node.enable();
// LABEL AND INFO OPERATIONS
const label = node.getLabel();
// appends a tooltip to the label associated with the element targeted by 'this'
node.addLabelTooltip(
"Helper text",
/* Optionally pass in css styles to customize the tooltip icon*/
{ color: "orange", fontSize: "30px" }
);
// appends a tooltip directly to the element targeted by 'this'
node.addTooltip(
"Inline helper",
/* Optionally pass in css styles to customize the tooltip icon*/
{ color: "orange", fontSize: "30px" }
);
Example:
import { createRef } from "powerpagestoolkit";
const title = await createRef("#myTitle");
title.addTooltip("This is an Example of a tooltip!", { color: "red" });
Here's an improved markdown documentation with more comprehensive details:
The bindForm
method simplifies form element management in DataVerse by providing a semantic and efficient way to access form controls, sections, and tabs.
- Retrieves form definition directly from DataVerse
- Automatically generates references for:
- Controls
- Sections
- Tabs
Element Type | Description | Accessibility |
---|---|---|
control |
Includes all form fields and sub-grids | Accessed via logical name |
section |
Standard PowerApps form sections | Accessed via logical name |
tab |
Form tabs corresponding to PowerApps layout | Accessed via logical name |
import { bindForm } from "powerpagestoolkit";
// Basic form binding
bindForm("form-guid").then((form) => {
// Access elements by their logical name
const nameField = form["name"];
// execute custom methods
nameField.applyBusinessRule(
{
setVisibility: [() => someNode.value === "desired value"],
},
[someNode]
);
// Or executes methods immediately upon accessing
form["phonenumber"].addTooltip("Example tooltip text");
});
/**
* Binds a form by its GUID and returns a collection of form elements
* @param formGuid Unique identifier for the form
* @returns Promise resolving to form element references
*/
function bindForm(formGuid: string): Promise<DOMNodeReferenceArray & Record<string: DOMNodeReference>>;
- Reduces code complexity
- Improves readability
- Provides type-safe access to form elements
- Supports flexible form interactions
- Use logical names consistently
- Handle async nature of form binding
- Leverage TypeScript for enhanced type checking
Ensure proper error handling for form binding:
bindForm("form-guid")
.then((form) => {
// Form processing
})
.catch((error) => {
console.error("Form binding failed", error);
});
Perform secure API calls to DataVerse from your PowerPages site. This method implements the shell deferred token to send requests with __RequestVerificationToken
await API.createRecord("accounts", {
name: "Gypsum LLC",
type: "Vendor",
})
.then((recordId) => {
console.log("Created record:", recordId);
})
.catch((error) => {
console.error("Creation failed:", error);
});
// Single record
const record = await API.getRecord(
"accounts",
"record-guid",
"select=name,accountnumber"
);
// Multiple records
const records = await API.getMultiple(
"contacts",
'$filter=firstname eq "Jane"&$select=firstname,lastname'
);
await API.updateRecord("contacts", "record-guid", {
name: "Jane Smith",
email: "[email protected]",
});
- Always await DOMNodeReference creation:
const node = await createRef("#element");
- Include all referenced nodes in dependency arrays:
node.configureConditionalRendering(
() => dependentNode.value === "test",
[dependentNode] // Required!
);
-
Use TypeScript for better type safety and IntelliSense support.
-
Use proper error handling with API operations:
/* optionally await */ API.createRecord(/*...*/)
.then((recordId) => {})
.catch((error) => {
// handle your errors appropriately
});
The package includes full TypeScript definitions and type safety. Use TypeScript for the best development experience and catch potential errors at compile time.
Contributions are welcome, feel free to create a pull request with enhancements. Please include an explanation of the changes made. All pull requests will be reviewed by the project owner.
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.
If you like this project, found it useful, or would like to help support the long-term support of this package, please feel free to contribute via GitHub Sponsors: Keaton-Brewster