powerpagestoolkit
Version:
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.
488 lines (381 loc) • 13.6 kB
Markdown
# 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
```bash
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`
```typescript
get(
target: HTMLElement | string,
options: {
multiple: (() => boolean) | boolean = false,
root: HTMLElement,
timeoutMs:number
}
): Promise<PowerPagesElement | PowerPagesElement[]>;
```
get takes two main arguments:
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Property</th>
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Type</th>
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Details</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 8px;">target</td>
<td style="border: 1px solid #ddd; padding: 8px;">
<pre><code class="language-javascript">string | HTMLElement</code></pre>
</td>
<td style="border: 1px solid #ddd; padding: 8px;">
Use standard <code>querySelector</code> syntax to target an element, or elements in the DOM, or pass in an instance of the element itself to create a reference.
</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 8px;">options</td>
<td style="border: 1px solid #ddd; padding: 8px;">
<pre><code class="language-javascript">{
multiple: () => boolean | boolean,
root: HTMLElement,
timeoutMs:number
}</code></pre>
</td>
<td style="border: 1px solid #ddd; padding: 8px;">
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.
</td>
</tr>
</tbody>
</table>
Import the utility function for creating PowerPagesElement(s)
```typescript
import { get } from "powerpagestoolkit";
```
Instantiate one, or multiple instances of a PowerPagesElement, and optionally configure advanced options
```javascript
// 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
```typescript
// 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:_
```typescript
applyBusinessRule(
rule: BusinessRule,
dependencies: PowerPagesElement[]
): PowerPagesElement; /* Instance of this is returned for optional
method chaining */
```
**BusinessRule Definition**
```typescript
interface BusinessRule {
setVisibility?: () => boolean;
setRequirements?: () => {
isRequired: () => boolean;
isValid: () => boolean;
};
setValue?: () => {
condition: () => boolean;
value: () => any | any;
};
setDisabled?: () => boolean;
}
```
##### Visibility Control
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// Disable 'taxIdField' when 'businessTypeField' is 'Individual'
taxIdField.applyBusinessRule(
{
setDisabled: () => businessTypeField.value === "Individual",
},
[businessTypeField] // Enable/disable when businessTypeField changes
);
```
##### Element Manipulation
_Value management_
```typescript
// 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_
```typescript
node.setInnerHTML("<span>New content</span>");
node.append(childElement);
node.prepend(headerElement);
node.after(siblingElement);
node.before(labelElement);
```
_Styling_
```typescript
node.setStyle({
display: "block",
color: "red",
});
```
_Enabling/Disabling inputs_
```typescript
node.disable();
node.enable();
```
##### Label and Tooltip Management
```typescript
// 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:_
```typescript
import { get } from "powerpagestoolkit";
const title = await get("#myTitle");
title.addTooltip("This is an Example of a tooltip!", { color: "red" });
```

### 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
```javascript
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
```typescript
/**
* 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:
```javascript
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
```typescript
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
```typescript
// 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
```typescript
await API.updateRecord("contacts", "record-guid", {
name: "Jane Smith",
email: "jane@example.com",
});
```
## Best Practices
1. Always await PowerPagesElement creation:
```typescript
const node = await get("#element");
```
2. Include all referenced nodes in dependency arrays:
```typescript
node.configureConditionalRendering(
() => dependentNode.value === "test",
[dependentNode] // Required!
);
```
3. Use TypeScript for better type safety and IntelliSense support.
4. Use proper error handling with API operations:
```typescript
/* 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](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](https://github.com/sponsors/Keaton-Brewster)