UNPKG

dom-unify

Version:

A JavaScript library for manipulating the DOM with a chainable, intuitive API. It simplifies creating, modifying, and navigating DOM elements.

775 lines (632 loc) 20 kB
# dom-unify A chainable JavaScript library for intuitive DOM manipulation. It simplifies creating, modifying, navigating, and handling events for DOM elements, ideal for building dynamic interfaces. ## Installation Install via npm: ```bash npm install dom-unify ``` ## Usage Import the `dom` function to start manipulating the DOM: ```javascript import { dom } from 'dom-unify'; ``` ### Example 1: Creating and Copying Elements **Initial HTML**: ```html <body></body> ``` Create a container, add items, style them, and copy/paste them: ```javascript import { dom } from 'dom-unify'; dom() .add({ tag: 'div', class: 'container' }) // Create container .enter() // Enter container .add({ tag: 'div', class: 'item', text: 'Item 1' }) // Add first item .add({ tag: 'div', class: 'item', text: 'Item 2' }) // Add second item .find('.item') // Select all items .set({ style: { color: 'blue' } }) // Style items .copy() // Copy items to buffer .up('.container') // Return to container .paste(); // Paste copied items ``` **Resulting DOM**: ```html <body> <div class="container"> <div class="item" style="color: blue;">Item 1</div> <div class="item" style="color: blue;">Item 2</div> <div class="item" style="color: blue;">Item 1</div> <div class="item" style="color: blue;">Item 2</div> </div> </body> ``` This example demonstrates: - Creating a container and items from an empty `<body>`. - Using `.enter()` to add elements inside the container. - Styling with `.set`, copying with `.copy`, and pasting with `.paste`. - Navigating back to a specific parent (`.container`) with `.up`. ### Example 2: Building a Dynamic Interface **Initial HTML**: ```html <body></body> ``` Create a container with a header (containing an "Add Card" button) and a body (for cards). Each card has a title, a text input, a paragraph, and a "Delete" button. ```javascript import { dom } from 'dom-unify'; // Handlers for adding and deleting cards function addCard(event) { dom(event.target) .up('.container') // Go to container .find('.card-body') // Select card body .add({ tag: 'div', class: 'card' }) // Add card .enter() // Enter card .add({ tag: 'h3', text: 'Card' }) // Add title .add({ tag: 'input', attrs: { type: 'text', placeholder: 'Enter text' } }) // Add input .add({ tag: 'p', text: 'Additional content' }) // Add paragraph .add({ tag: 'button', class: 'delete-btn', text: 'Delete' }) // Add delete button .on('click', deleteCard); // Attach delete handler } function deleteCard(event) { dom(event.target) .up('.card') // Go to parent card .delete(); // Delete card } // Create container, header, and body dom() .add({ tag: 'div', class: 'container' }) // Add container .enter() // Enter container .add({ tag: 'div', class: 'card-header' }) // Add header .enter() // Enter header .add({ tag: 'button', class: 'add-card-btn', text: 'Add Card' }) // Add button .on('click', addCard) // Attach add handler .up('.container') // Return to container .add({ tag: 'div', class: 'card-body' }); // Add card body ``` **Resulting DOM after two clicks on `.add-card-btn`**: ```html <body> <div class="container"> <div class="card-header"> <button class="add-card-btn">Add Card</button> </div> <div class="card-body"> <div class="card"> <h3>Card</h3> <input type="text" placeholder="Enter text"> <p>Additional content</p> <button class="delete-btn">Delete</button> </div> <div class="card"> <h3>Card</h3> <input type="text" placeholder="Enter text"> <p>Additional content</p> <button class="delete-btn">Delete</button> </div> </div> </div> </body> ``` This example shows: - Building a dynamic interface from scratch. - Using `.on` for event handling to add/remove cards. - Relative navigation with `.up`, `.find`, `.enter`, and `.up('.container')`. - Creating nested structures without `children`. ### Example 3: Updating Content Dynamically **Initial HTML**: ```html <body></body> ``` Update a paragraph based on input in a text field. ```javascript import { dom } from 'dom-unify'; // Handler to update paragraph with input value function updateContent(event) { dom(event.target) .up('.card') // Go to parent card .find('p') // Find paragraph .set({ text: event.target.value || 'No input' }); // Update text } // Create a card with an input and paragraph dom() .add({ tag: 'div', class: 'container' }) // Add container .enter() // Enter container .add({ tag: 'div', class: 'card' }) // Add card .enter() // Enter card .add({ tag: 'input', attrs: { type: 'text', placeholder: 'Type here' } }) // Add input .on('input', updateContent) // Update on input .add({ tag: 'p', text: 'No input' }); // Add paragraph ``` **Resulting DOM after typing "Hello"**: ```html <body> <div class="container"> <div class="card"> <input type="text" placeholder="Type here" value="Hello"> <p>Hello</p> </div> </div> </body> ``` This example demonstrates: - Real-time content updates using `.on('input', ...)`. - Accessing input values directly with `event.target.value`. - Navigating and modifying elements with `.up` and `.set`. ### Example 4: Reusing Contexts with `.mark` **Initial HTML**: ```html <body></body> ``` Mark intermediate contexts to reuse them later, even after complex operations. ```javascript import { dom } from 'dom-unify'; // Handler to highlight marked container function highlightContainer(event) { dom() .getMark('container') // Restore marked container .set({ style: { backgroundColor: 'lightblue' } }); // Highlight } // Create structure and mark contexts dom() .add({ tag: 'div', class: 'container' }) // Add container .mark('container') // Mark container .enter() // Enter container .add({ tag: 'div', class: 'item', text: 'Item 1' }) // Add item .mark('item') // Mark item .add({ tag: 'button', class: 'highlight-btn', text: 'Highlight Container' }) // Add button .on('click', highlightContainer) // Attach handler .getMark('container') // Return to container .add({ tag: 'div', class: 'item', text: 'Item 2' }); // Add another item ``` **Resulting DOM after clicking `.highlight-btn`**: ```html <body> <div class="container" style="background-color: lightblue;"> <div class="item">Item 1</div> <button class="highlight-btn">Highlight Container</button> <div class="item">Item 2</div> </div> </body> ``` This example shows: - Using `.mark` to save the container and item contexts. - Reusing the container with `.getMark` to apply styles later. - Maintaining access to intermediate contexts during chained operations. ## API ### `dom(root)` Creates a `DomUnify` instance with an initial context. - **Arguments**: - `root` (optional): `HTMLElement`, `Document`, `NodeList`, array of elements, or omitted (defaults to `document.body`). - **Returns**: `DomUnify` instance for chaining. **Examples**: ```javascript // Default (body) dom().add({ tag: 'div', text: 'Added to body' }); // Single element dom('div').enter(0).add({ tag: 'span', text: 'Inside' }); // Document (body) dom(document).add({ tag: 'div', text: 'Added to body' }); // NodeList dom('.item').set({ text: 'Updated' }); // Array of elements dom() .find('div, span') // Find all div and span .set({ class: 'item' }); // Set class ``` ### `.add(config)` Adds new elements to the current context. - **Arguments**: - `config`: Object, array of objects, JSON string, HTML string, or object with `children`. - Object: `{ tag: 'div', class: 'name', id: 'id', text: 'text', html: 'html', attrs: {}, styles: {}, dataset: {}, events: {}, children: [] }`. - Array: Multiple elements via array of config objects. - JSON: Parsed as object or array. - HTML: Sanitized (removes `<script>` and `on*` attributes). - `children`: Array of config objects, strings, or `HTMLElement`s for nested elements. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Single object dom() .add({ tag: 'div', class: 'card', text: 'Card', styles: { color: 'blue' } }); // Array of objects dom() .add([ { tag: 'div', text: 'Item 1' }, { tag: 'div', text: 'Item 2' } ]); // JSON string dom() .add('{"tag": "div", "class": "card", "text": "JSON Card"}'); // HTML string dom() .add('<div class="card">HTML Card</div>'); // With children dom() .add({ tag: 'div', class: 'container', children: [ { tag: 'h1', text: 'Title' }, { tag: 'p', text: 'Content' }, document.createElement('span') ] }); ``` ### `.set(props)` Updates properties of all elements in the current context. - **Arguments**: - `props`: Object with properties: - `text`: Sets `textContent`. - `html`: Sets `innerHTML`. - `class`: Sets `className`. - `id`: Sets `id`. - `style`: Applies styles via `Object.assign`. - `attr`: Sets attributes via `setAttribute`. - `dataset`: Sets `dataset` properties. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Update multiple properties dom() .add({ tag: 'div', class: 'card' }) .set({ text: 'Updated', class: 'card updated', style: { background: 'red' }, attr: { 'data-id': '123' }, dataset: { type: 'card' } }); // Set single property dom() .add({ tag: 'div', class: 'card' }) .set({ text: 'New text' }); ``` ### `.find(selector)` Selects elements matching the selector within the current context. - **Arguments**: - `selector`: CSS selector or `'*'` for all children. If omitted, clears the context. - **Returns**: `DomUnify` instance with matched elements. **Examples**: ```javascript // Select by class dom() .add({ tag: 'div', class: 'container' }) .add({ tag: 'div', class: 'item' }) .find('.item') .set({ text: 'Found' }); // Select all children dom() .add({ tag: 'div', class: 'container' }) .add({ tag: 'span', text: 'Child' }) .find('*') .set({ text: 'All children' }); // Clear context (sets currentElements to empty array, so subsequent .add has no targets and does nothing) dom() .find() .add({ tag: 'div', text: 'No effect' }); ``` ### `.enter(index?)` Moves the context to child elements. - **Arguments**: - `index` (optional): Index of child element (supports negative indices). If omitted, uses last added elements or all children. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Enter all children dom() .add({ tag: 'div', class: 'box' }) .enter() .add({ tag: 'span', text: 'Inside' }); // Enter specific child dom() .add({ tag: 'div', class: 'box' }) .enter() .add({ tag: 'span', text: 'First' }) .add({ tag: 'span', text: 'Second' }) .enter(1) // Enter second span .set({ text: 'Selected' }); // Enter last added dom() .add({ tag: 'div', class: 'box' }) .add({ tag: 'span', text: 'Last' }) .enter() // Enter last added span .set({ text: 'Updated' }); // Negative index dom() .add({ tag: 'div', class: 'box' }) .enter() .add({ tag: 'span', text: 'First' }) .add({ tag: 'span', text: 'Second' }) .enter(-1) // Enter last span .set({ text: 'Last' }); ``` ### `.up(selector?)` Moves the context to parent elements or elements matching a selector. - **Arguments**: - `selector` (optional): CSS selector, number of levels to go up, or `-1` for topmost parent (up to `document.body`). If omitted, goes to direct parent. - **Returns**: `DomUnify` instance. **Example Structure**: ```html <body> <div class="container"> <div class="item"> <span class="child">Text</span> </div> </div> </body> ``` **Examples**: ```javascript // Go to direct parent dom('.child') .up() // Moves to .item .add({ tag: 'span', text: 'Sibling' }); // Go to specific parent dom('.child') .up('.container') // Moves to .container .add({ tag: 'div', text: 'New item' }); // Go up by levels dom('.child') .up(2) // Moves to body .add({ tag: 'div', text: 'Added to body' }); // Go to topmost parent dom('.child') .up(-1) // Moves to body .add({ tag: 'div', text: 'Added to body' }); ``` ### `.back(steps?)` Restores a previous context from history. - **Arguments**: - `steps` (optional): Number of steps to go back (default: 1) or negative number for history index from start. - **Returns**: `DomUnify` instance. - **Note**: If the current context is empty and `lastParents` is not, `.back()` restores `lastParents` and clears it. **Examples**: ```javascript // Go back one step dom() .add({ tag: 'div', class: 'container' }) .enter() .back() // Return to container .add({ tag: 'div', text: 'Sibling' }); // Go back multiple steps dom() .add({ tag: 'div', class: 'container' }) .enter() .add({ tag: 'div', class: 'item' }) .enter() .back(2) // Return to container .add({ tag: 'div', text: 'Sibling' }); // Negative steps dom() .add({ tag: 'div', class: 'container' }) .enter() .add({ tag: 'div', class: 'item' }) .back(-1) // Return to first context (body) .add({ tag: 'div', text: 'Added to body' }); ``` ### `.mark(name)` Saves the current context with a name for later retrieval. - **Arguments**: - `name`: String identifier for the context. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Mark and reuse container dom() .add({ tag: 'div', class: 'container' }) .mark('container') .add({ tag: 'div', class: 'item' }) .getMark('container') // Return to container .add({ tag: 'div', text: 'New item' }); // Mark multiple contexts dom() .add({ tag: 'div', class: 'container' }) .mark('container') .enter() .add({ tag: 'div', class: 'item' }) .mark('item') .getMark('container') // Return to container .set({ style: { border: '1px solid black' } }); ``` ### `.getMark(name)` Restores a previously marked context. - **Arguments**: - `name`: String identifier of the saved context. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Restore marked context dom() .add({ tag: 'div', class: 'container' }) .mark('root') .find('.item') .getMark('root') // Return to container .add({ tag: 'div', text: 'Back at root' }); // Reuse after complex navigation dom() .add({ tag: 'div', class: 'container' }) .mark('container') .enter() .add({ tag: 'div', class: 'item' }) .enter() .add({ tag: 'span', text: 'Child' }) .getMark('container') // Return to container .add({ tag: 'div', text: 'New item' }); ``` ### `.copy()` Copies all elements in the current context to a buffer. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Copy and paste dom() .find('.item') .copy() .up('.container') .paste(); // Copy multiple elements dom() .add({ tag: 'div', class: 'container' }) .enter() .add({ tag: 'div', class: 'item', text: 'Item 1' }) .add({ tag: 'div', class: 'item', text: 'Item 2' }) .find('.item') .copy() .up('.container') .paste(); ``` ### `.paste(position?)` Pastes elements from the buffer into the current context. - **Arguments**: - `position` (optional): `'append'` (default), `'prepend'`, or a number (index, supports negative). - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Default append dom() .find('.item') .copy() .up('.container') .paste(); // Prepend dom() .find('.item') .copy() .up('.container') .paste('prepend'); // Specific index dom() .find('.item') .copy() .up('.container') .paste(1); // Negative index dom() .find('.item') .copy() .up('.container') .paste(-1); ``` ### `.duplicate(position?)` Duplicates elements in the current context and inserts them next to the originals. - **Arguments**: - `position` (optional): `'append'` (default) or `'prepend'`. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Default append dom() .find('.item') .duplicate(); // Prepend dom() .find('.item') .duplicate('prepend'); ``` ### `.delete()` Removes all elements in the current context and stores their parents in `lastParents`. - **Returns**: `DomUnify` instance with empty context. **Examples**: ```javascript // Delete elements dom() .find('.item') .delete(); // Delete and restore parent dom() .find('.item') .delete() .back() // Restore parent from lastParents .add({ tag: 'div', text: 'New item' }); ``` ### `.cut()` Removes elements from the DOM and copies them to the buffer. - **Returns**: `DomUnify` instance with empty context. **Examples**: ```javascript // Cut and paste dom() .find('.item') .cut() .up('.container') .paste(); // Cut and paste elsewhere dom() .add({ tag: 'div', class: 'container1' }) .enter() .add({ tag: 'div', class: 'item', text: 'Item to cut' }) .up() .add({ tag: 'div', class: 'container2' }) .mark('container2') .find('.item') .cut() .getMark('container2') .paste(); ``` ### `.on(event, handler, ...args)` Attaches an event handler to all elements in the current context. - **Arguments**: - `event`: String event name (e.g., `'click'`, `'input'`). - `handler`: Function or string (function name in `window` scope). - `...args`: Optional arguments passed to the handler before the event object. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Basic click handler function handleClick(event) { console.log('Clicked:', event.target); } dom() .add({ tag: 'button', class: 'btn', text: 'Click Me' }) .on('click', handleClick); // Handler with arguments function logClick(message, event) { console.log(message, event.target); } dom() .add({ tag: 'button', class: 'btn', text: 'Click Me' }) .on('click', logClick, 'Button clicked'); // Handler by name window.myHandler = function(event) { console.log('Named handler:', event.target); }; dom() .add({ tag: 'button', class: 'btn', text: 'Click Me' }) .on('click', 'myHandler'); ``` ### `.off(event, handler?)` Removes event handlers from all elements in the current context. - **Arguments**: - `event`: String event name. - `handler` (optional): Function or string (function name). If omitted, removes all handlers for the event. - **Returns**: `DomUnify` instance. **Examples**: ```javascript // Remove specific handler function handleClick(event) { console.log('Clicked:', event.target); } dom() .add({ tag: 'button', class: 'btn', text: 'Click Me' }) .on('click', handleClick) .off('click', handleClick); // Remove all handlers for event dom() .add({ tag: 'button', class: 'btn', text: 'Click Me' }) .on('click', () => console.log('Click 1')) .on('click', () => console.log('Click 2')) .off('click'); // Remove all click handlers // Remove handler by name window.myHandler = function(event) { console.log('Named handler:', event.target); }; dom() .add({ tag: 'button', class: 'btn', text: 'Click Me' }) .on('click', 'myHandler') .off('click', 'myHandler'); ``` ## License MIT License Copyright (c) 2025 ermkon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.