Hone logo
Hone
Problems

Build a Lightweight DOM Element Wrapper

Web development often involves frequent interaction with the Document Object Model (DOM). Directly manipulating the native DOM API can sometimes be verbose and repetitive, especially when performing common tasks like selecting elements, modifying content, or handling events. This challenge asks you to create a simple wrapper around a native DOM element, providing a more concise and chainable API for common operations.

Problem Description

Your task is to implement a DomWrapper class or object that encapsulates a single native DOM element. This wrapper should expose a set of methods that simplify common DOM operations, allowing for a more fluent and readable coding style, similar to popular libraries but on a much smaller scale.

Key requirements:

  • Constructor: The DomWrapper should be initialized with either:
    • A CSS selector string (e.g., '#my-id', '.my-class', 'div'). If multiple elements match, it should wrap the first one found. If no element is found, it should still create a DomWrapper instance that gracefully handles subsequent method calls (e.g., by doing nothing or returning null/undefined where appropriate, but not throwing errors).
    • A direct reference to a native DOM element object.
  • Chainable Methods: Most methods that modify the element's state (e.g., addClass, removeClass, css, append, prepend, attr [setter]) should return the DomWrapper instance itself (this) to allow for method chaining.
  • Query/Getter Methods: Methods that retrieve information or return new wrapper instances (e.g., text, html, attr [getter], find, children, parent) should return the relevant data or a new DomWrapper instance (or an array of DomWrapper instances for find/children).
  • Core Methods to Implement:
    • text(content): Get or set the text content of the element. If content is provided, sets it; otherwise, returns the current text.
    • html(content): Get or set the inner HTML of the element. If content is provided, sets it; otherwise, returns the current HTML.
    • addClass(...classNames): Adds one or more CSS classes to the element.
    • removeClass(...classNames): Removes one or more CSS classes from the element.
    • toggleClass(className, force): Toggles a CSS class. force (optional boolean) can be used to explicitly add or remove.
    • append(content): Appends content (string HTML or another DomWrapper instance) to the element's children.
    • prepend(content): Prepends content (string HTML or another DomWrapper instance) to the element's children.
    • on(eventName, handler): Attaches an event listener to the element. The handler should ideally have this bound to the DomWrapper instance.
    • off(eventName, handler): Removes an event listener from the element.
    • attr(attributeName, value): Get or set an attribute. If value is provided, sets it; otherwise, returns the attribute's value.
    • css(property, value): Get or set a CSS property. If value is provided, sets it; otherwise, returns the computed style for property.
    • find(selector): Finds the first descendant element matching the selector and returns a new DomWrapper instance for it. If no element is found, returns a DomWrapper instance that handles this gracefully.
    • children(): Returns an array of new DomWrapper instances for all direct children of the element.
    • parent(): Returns a new DomWrapper instance for the element's direct parent.
    • remove(): Removes the element from the DOM. This method should make the current DomWrapper instance's underlying element reference null and return null or undefined.

Edge Cases:

  • Constructor with a non-existent selector: Methods called on the resulting DomWrapper instance should not throw errors but gracefully handle the absence of the underlying DOM element (e.g., text() returns empty string, addClass() does nothing, find() returns a wrapper for null).
  • attr or css for a non-existent property/attribute.
  • on/off with invalid event names or handlers.
  • Chaining methods on a DomWrapper that no longer holds a real DOM element (e.g., after remove() has been called).

Examples

Example 1: Basic Element Creation and Manipulation

Input (Conceptual HTML setup):
<div id="app">
  <p id="greeting">Hello</p>
</div>

Pseudocode:
// Imagine DomWrapper is a class you instantiate
let myWrapper = new DomWrapper('#greeting');
myWrapper.text('Welcome to Hone!');
myWrapper.addClass('highlight');

Output (Conceptual HTML result):
<div id="app">
  <p id="greeting" class="highlight">Welcome to Hone!</p>
</div>
Explanation: A DomWrapper is created for the paragraph element. Its text content is updated, and a CSS class 'highlight' is added to it.

Example 2: Chaining and Event Handling

Input (Conceptual HTML setup):
<div id="container">
  <button id="actionBtn">Click Me</button>
</div>

Pseudocode:
let btnWrapper = new DomWrapper('#actionBtn');
btnWrapper
  .attr('data-count', '0') // Set a data attribute
  .css('background-color', 'blue') // Set a CSS property
  .on('click', function(event) {
    // 'this' inside the handler refers to the DomWrapper instance if bound correctly
    let currentCount = parseInt(this.attr('data-count')); 
    this.attr('data-count', currentCount + 1);
    this.text('Clicked ' + (currentCount + 1) + ' times');
    console.log('Button was clicked!');
  });

Output (Conceptual behavior after clicking the button once):
- The button's 'data-count' attribute will be '1'.
- The button's text will change to 'Clicked 1 times'.
- A message will be logged to the console.
Explanation: The DomWrapper allows chaining `attr` and `css` methods. An event listener is attached which updates the element's attribute and text upon click, demonstrating how `this` can refer to the wrapper itself within the handler.

Example 3: Querying and Edge Cases

Input (Conceptual HTML setup):
<div id="parent">
  <span class="child">Span 1</span>
  <p class="child">Paragraph</p>
  <button id="removeMe">Remove</button>
</div>

Pseudocode:
let parentWrapper = new DomWrapper('#parent');
let firstSpan = parentWrapper.find('span'); // Finds the first span within #parent
console.log(firstSpan.text()); // Should output "Span 1"

let nonExistent = new DomWrapper('.non-existent-class');
console.log(nonExistent.text()); // Should output "" (empty string)
nonExistent.addClass('error'); // Should do nothing, no error thrown.

let allChildren = parentWrapper.children();
// allChildren will be an array of DomWrapper instances for 'span.child' and 'p.child'
allChildren[0].css('color', 'red'); // Sets color of "Span 1" to red

let removeBtn = new DomWrapper('#removeMe');
removeBtn.remove(); // Removes the button from the DOM.
console.log(removeBtn.parent()); // Should return a wrapper for null/undefined gracefully

Output (Conceptual):
- Console: "Span 1"
- Console: "" (empty string)
- "Span 1" element will have its text color changed to red.
- The button with id "removeMe" will be gone from the DOM.
- The last console log should output a wrapper instance whose underlying element is null/undefined.
Explanation: `find` retrieves a new wrapper for a descendant. The constructor gracefully handles non-existent elements, preventing errors. `children` returns multiple wrappers for direct children. The `remove` method deletes the element and invalidates the wrapper for further DOM operations.

Constraints

  • The implementation should not use any external DOM manipulation libraries (e.g., jQuery, Lodash). Rely solely on native browser DOM APIs.
  • The wrapper should primarily manage a single underlying native DOM element. Methods like find and children should return new DomWrapper instances (or arrays of them) rather than attempting to manage multiple elements within a single wrapper.
  • Assume a modern browser environment (e.g., support for document.querySelector, document.querySelectorAll, element.addEventListener, element.classList, element.style, element.remove).
  • The solution should be reasonably performant for typical web page interactions (handling hundreds of DOM manipulations, not millions).
  • Input selectors will be valid CSS selectors. Input HTML strings will be valid fragments.

Notes

  • Consider how to store the underlying native DOM element reference within your DomWrapper instance. This reference will be null or undefined if the wrapper was initialized with a non-existent selector or if the element was remove()d.
  • Pay close attention to the return values of each method to ensure chainability and correct data retrieval, especially for methods that can act as both getters and setters.
  • For methods like on and off, remember that this inside the native event handler function usually refers to the native DOM element. You might need to use bind or an arrow function to ensure this refers to your DomWrapper instance within the handler.
  • When a DomWrapper instance is created for a non-existent element, ensure that its methods fail gracefully without throwing runtime errors. This might involve checking for the existence of the underlying DOM element at the beginning of each method call.
  • Think about how to return DomWrapper instances from methods like find, children, and parent. You'll likely need to instantiate new DomWrapper objects within these methods.
Loading editor...
plaintext