PowerPages Tool Kit
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.
Features
- 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
Installation
npm install powerpagestoolkit
Core Modules
PowerPagesElement
A powerful class for managing DOM elements with automatic value synchronization and event handling.
Basic Usage
PowerPagesElements are instantiated with the help of the following factory function: get
get(
target: HTMLElement | string,
options: {
multiple: (() => boolean) | boolean = false,
root: HTMLElement,
timeoutMs:number
}
): Promise<PowerPagesElement | PowerPagesElement[]>;
get 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 PowerPagesElement(s)
import { get } from "powerpagestoolkit";
Instantiate one, or multiple instances of a PowerPagesElement, and optionally configure advanced options
// Create a single reference (i.e. 'querySelector')
const node = await get("#myElement");
// Create multiple references (i.e. 'querySelectorAll')
const nodes = await get(".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 get("#target", { timeoutMs: 2000 });
// need to target a node within a specific node? use that node as the root
const otherElement = document.getElementById("id");
const node3 = await get("#target", { root: otherElement });
// implement all options:
const nodes2 = await get("#target", {
multiple: true,
timeoutMs: 4000,
root: otherElement,
});
Properties
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 | Radio | null | Reference to 'yes' radio (for yes/no fields) |
noRadio | Radio | null | Reference to 'no' radio (for yes/no fields) |
checked | boolean | Checkbox/radio checked state |
Key Methods
Event Handling
// 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");
});
...
Business Rule Application
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: BusinessRule,
dependencies: PowerPagesElement[]
): PowerPagesElement; /* Instance of this is returned for optional
method chaining */
BusinessRule Definition
interface BusinessRule {
setVisibility?: () => boolean;
setRequirements?: () => {
isRequired: () => boolean;
isValid: () => boolean;
};
setValue?: () => {
condition: () => boolean;
value: () => any | any;
};
setDisabled?: () => boolean;
}
Visibility Control
// 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
);
Validation and Requirements
// Require 'taxIdField' when 'businessTypeField' is 'Corporation' or 'LLC'
taxIdField.applyBusinessRule(
{
setRequirements: () => ({
isRequired: function () {
return (
businessTypeField.value === "Corporation" ||
businessTypeField.value === "LLC"
);
},
isValid: function () {
return this.value != null && this.value !== "";
},
}),
},
[businessTypeField] // Revalidate when businessTypeField changes
);
Setting Field Values Conditionally
// Set default industry value when 'businessTypeField' is 'Corporation'
industryField.applyBusinessRule(
{
setValue: () => ({
condition: () => businessTypeField.value === "Corporation",
value: "Corporate",
}),
},
[businessTypeField] // Apply value when businessTypeField changes
);
Enabling and Disabling Fields
// Disable 'taxIdField' when 'businessTypeField' is 'Individual'
taxIdField.applyBusinessRule(
{
setDisabled: () => businessTypeField.value === "Individual",
},
[businessTypeField] // Enable/disable when businessTypeField changes
);
Element Manipulation
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 Tooltip Management
// 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 { get } from "powerpagestoolkit";
const title = await get("#myTitle");
title.addTooltip("This is an Example of a tooltip!", { color: "red" });
Here's an improved markdown documentation with more comprehensive details:
BindForm Method
The bindForm
method simplifies form element management in DataVerse by providing a semantic and efficient way to access form controls, sections, and tabs.
Key Features
- Retrieves form definition directly from DataVerse
- Automatically generates references for:
- Controls
- Sections
- Tabs
Element Types
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 |
Usage Example
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");
});
Method Signature
/**
* 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<PowerPagesElementArray & Record<string: PowerPagesElement>>;
Benefits
- Reduces code complexity
- Improves readability
- Provides type-safe access to form elements
- Supports flexible form interactions
Best Practices
- Use logical names consistently
- Handle async nature of form binding
- Leverage TypeScript for enhanced type checking
Error Handling
Ensure proper error handling for form binding:
bindForm("form-guid")
.then((form) => {
// Form processing
})
.catch((error) => {
console.error("Form binding failed", error);
});
DataVerse API
Perform secure API calls to DataVerse from your PowerPages site. This method implements the shell deferred token to send requests with __RequestVerificationToken
Create Records
await API.createRecord("accounts", {
name: "Gypsum LLC",
type: "Vendor",
})
.then((recordId) => {
console.log("Created record:", recordId);
})
.catch((error) => {
console.error("Creation failed:", error);
});
Get Records
// 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'
);
Update Record
await API.updateRecord("contacts", "record-guid", {
name: "Jane Smith",
email: "jane@example.com",
});
Best Practices
- Always await PowerPagesElement creation:
const node = await get("#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
});
TypeScript Support
The package includes full TypeScript definitions and type safety. Use TypeScript for the best development experience and catch potential errors at compile time.
Contributing
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.
License
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.
Funding
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