UNPKG

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
# 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" }); ``` ![Example](./assets//infoIconExample.gif) ### 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)