Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

DOM Manipulation Techniques: Exclusive Selection Patterns and Node Operations

Tech 1

Exclusive Selection Pattern

When managing groups of elements where only one item should display a specific style at a time, implement a mutual exclusion algorithm:

  1. Remove the target style from all elements (clear the group)
  2. Apply the style exclusively to the current element
  3. Maintain this execution sequence strictly
<button class="option">Option A</button>
<button class="option">Option B</button>
<button class="option">Option C</button>
<button class="option">Option D</button>

<script>
const options = document.querySelectorAll('.option');

for (let i = 0; i < options.length; i++) {
    options[i].addEventListener('click', function() {
        // Phase 1: Clear styling from all siblings
        for (let j = 0; j < options.length; j++) {
            options[j].style.backgroundColor = '#ffffff';
            options[j].style.color = '#333333';
        }
        // Phase 2: Highlight current selection
        this.style.backgroundColor = '#4a90e2';
        this.style.color = '#ffffff';
    });
}
</script>

Example: Dynamic Background Switcher

Implementation approach for image-based theme selection:

  1. Register click events on thumbnail collection using iteration
  2. Extract the src attribute from clicked thumbnail
  3. Apply extracted path to container's background style
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
    background: url('assets/bg-01.jpg') no-repeat center center fixed;
    background-size: cover;
    transition: background-image 0.3s ease;
}
.theme-gallery {
    display: flex;
    gap: 10px;
    padding: 20px;
    background: rgba(255,255,255,0.9);
    position: fixed;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.theme-thumb {
    width: 80px;
    height: 50px;
    object-fit: cover;
    cursor: pointer;
    border: 2px solid transparent;
    border-radius: 4px;
    transition: border-color 0.2s;
}
.theme-thumb:hover { border-color: #4a90e2; }
</style>

<div class="theme-gallery">
    <img src="assets/bg-01.jpg" class="theme-thumb" alt="Theme 1">
    <img src="assets/bg-02.jpg" class="theme-thumb" alt="Theme 2">
    <img src="assets/bg-03.jpg" class="theme-thumb" alt="Theme 3">
    <img src="assets/bg-04.jpg" class="theme-thumb" alt="Theme 4">
</div>

<script>
const thumbnails = document.querySelectorAll('.theme-thumb');

thumbnails.forEach(thumb => {
    thumb.addEventListener('click', function() {
        const imagePath = this.getAttribute('src');
        document.body.style.backgroundImage = `url('${imagePath}')`;
    });
});
</script>

Example: Interactive Table Highlighting

Hover effect implementation for data tables:

<style>
table { border-collapse: collapse; width: 100%; }
th, td { padding: 12px; border: 1px solid #ddd; text-align: left; }
thead { background-color: #f5f5f5; }
.row-highlight { background-color: #e3f2fd !important; }
</style>

<table id="data-table">
    <thead>
        <tr><th>Product</th><th>Category</th><th>Price</th></tr>
    </thead>
    <tbody>
        <tr><td>Laptop</td><td>Electronics</td><td>$999</td></tr>
        <tr><td>Desk Chair</td><td>Furniture</td><td>$299</td></tr>
        <tr><td>Coffee Mug</td><td>Kitchen</td><td>$15</td></tr>
    </tbody>
</table>

<script>
const tableBody = document.querySelector('#data-table tbody');
const rows = tableBody.querySelectorAll('tr');

rows.forEach(row => {
    row.addEventListener('mouseenter', function() {
        this.classList.add('row-highlight');
    });
    row.addEventListener('mouseleave', function() {
        this.classList.remove('row-highlight');
    });
});
</script>

Example: Master Checkbox Controller

Synchronized checkbox group behavior:

<table>
    <thead>
        <tr>
            <th><input type="checkbox" id="master-check"></th>
            <th>Item Name</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody id="item-list">
        <tr><td><input type="checkbox" class="item-check"></td><td>Server A</td><td>Active</td></tr>
        <tr><td><input type="checkbox" class="item-check"></td><td>Server B</td><td>Active</td></tr>
        <tr><td><input type="checkbox" class="item-check"></td><td>Server C</td><td>Maintenance</td></tr>
    </tbody>
</table>

<script>
const masterCheckbox = document.getElementById('master-check');
const itemCheckboxes = document.querySelectorAll('.item-check');

// Master controls slaves
masterCheckbox.addEventListener('change', function() {
    const isChecked = this.checked;
    itemCheckboxes.forEach(checkbox => {
        checkbox.checked = isChecked;
    });
});

// Slaves influence master
itemCheckboxes.forEach(checkbox => {
    checkbox.addEventListener('change', function() {
        const allChecked = Array.from(itemCheckboxes).every(cb => cb.checked);
        masterCheckbox.checked = allChecked;
    });
});
</script>

Attribute Management Strategies

Property vs. Attribute Access

Retrieval Methods:

  • element.propertyName — Accesses standard DOM properties (built-in)
  • element.getAttribute('attrName') — Retrieves HTML attributes including custom data attributes

Assignment Methods:

  • element.propertyName = value — Sets standard properties
  • element.setAttribute('attrName', value) — Sets any attribute (preferred for custom data)

Removal:

  • element.removeAttribute('attrName')
<div id="container" data-role="navigation" data-level="1" class="primary-nav"></div>

<script>
const container = document.querySelector('#container');

// Retrieval
console.log(container.id);              // "container" (property)
console.log(container.getAttribute('data-role')); // "navigation" (attribute)

// Modification
container.id = 'main-container';
container.setAttribute('data-level', '2');
container.setAttribute('data-expanded', 'true');

// Removal
container.removeAttribute('data-expanded');
</script>

Example: Tab Interface Component

Tab switching implementation using custom indexing:

<style>
.tab-container { width: 600px; margin: 40px auto; }
.tab-headers {
    display: flex;
    border-bottom: 2px solid #e0e0e0;
}
.tab-btn {
    padding: 12px 24px;
    cursor: pointer;
    background: #f5f5f5;
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: all 0.3s;
}
.tab-btn.active {
    background: #ffffff;
    border-bottom-color: #2196f3;
    color: #2196f3;
}
.tab-content { padding: 20px; border: 1px solid #e0e0e0; border-top: none; }
.content-panel { display: none; }
.content-panel.visible { display: block; }
</style>

<div class="tab-container">
    <div class="tab-headers">
        <button class="tab-btn active" data-index="0">Overview</button>
        <button class="tab-btn" data-index="1">Specifications</button>
        <button class="tab-btn" data-index="2">Reviews</button>
    </div>
    <div class="tab-content">
        <div class="content-panel visible" data-panel="0">Product overview content...</div>
        <div class="content-panel" data-panel="1">Technical specifications...</div>
        <div class="content-panel" data-panel="2">Customer reviews...</div>
    </div>
</div>

<script>
const tabs = document.querySelectorAll('.tab-btn');
const panels = document.querySelectorAll('.content-panel');

tabs.forEach(tab => {
    tab.addEventListener('click', function() {
        // Exclusive selection for tabs
        tabs.forEach(t => t.classList.remove('active'));
        this.classList.add('active');
        
        // Corresponding content display
        const targetIndex = this.getAttribute('data-index');
        
        panels.forEach(panel => {
            panel.classList.remove('visible');
            if (panel.getAttribute('data-panel') === targetIndex) {
                panel.classList.add('visible');
            }
        });
    });
});
</script>

HTML5 Data Attributes

Standardized approach for embedding custom data in markup using the data-* prefix.

Assignment:

<div data-product-id="SKU-12345" data-category="electronics" data-in-stock="true"></div>

Access Methods:

  • Legacy: element.getAttribute('data-product-id')
  • Modern API: element.dataset.productId (camelCase conversion)
  • Bracket notation: element.dataset['productId']
const productCard = document.querySelector('[data-product-id]');

// Reading
cardId = productCard.dataset.productId;  // "SKU-12345"
category = productCard.dataset.category; // "electronics"

// Writing
productCard.dataset.discount = "15%";
productCard.dataset.inStock = "false";

// Multi-word attributes convert to camelCase
// data-max-quantity -> dataset.maxQuantity
const maxQty = productCard.dataset.maxQuantity;

Node Hierarchy Navigation

Fundamental Concepts

All document contents are nodes with three essential properties:

  • nodeType: 1 (Element), 2 (Attribute), 3 (Text)
  • nodeName: Tag name or node identifier
  • nodeValue: Content value (text for text nodes)

Production code primarily interacts with element nodes (nodeType === 1).

Parent Node Access

const childElement = document.querySelector('.child-item');
const immediateParent = childElement.parentNode;  // Closest ancestor
// Returns null if no parent exists

Child Node Collections

Standard Method (includes text nodes):

const container = document.querySelector('ul');
const allNodes = container.childNodes;  // Includes whitespace text nodes

// Filter for elements only
const elementChildren = Array.from(allNodes).filter(
    node => node.nodeType === 1
);

Practical Method (elements only):

const listItems = container.children;  // HTMLCollection of element nodes
const firstItem = container.children[0];
const lastItem = container.children[container.children.length - 1];

Edge Case Handling:

// IE9+ compatible first/last element
const firstChild = container.firstElementChild;
const lastChild = container.lastElementChild;

// Universal compatibility
const first = container.children[0];
const last = container.children[container.children.length - 1];

Example: Dropdown Navigation Menu

<style>
.nav-menu { display: flex; list-style: none; gap: 20px; }
.nav-item { position: relative; }
.nav-link { padding: 10px 15px; text-decoration: none; color: #333; }
.dropdown {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    background: white;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    min-width: 150px;
}
.nav-item:hover > .dropdown { display: block; }
</style>

<ul class="nav-menu">
    <li class="nav-item">
        <a href="#" class="nav-link">Services</a>
        <ul class="dropdown">
            <li><a href="#">Consulting</a></li>
            <li><a href="#">Development</a></li>
            <li><a href="#">Support</a></li>
        </ul>
    </li>
    <li class="nav-item">
        <a href="#" class="nav-link">Products</a>
        <ul class="dropdown">
            <li><a href="#">Software</a></li>
            <li><a href="#">Hardware</a></li>
        </ul>
    </li>
</ul>

<script>
const topLevelItems = document.querySelectorAll('.nav-item');

topLevelItems.forEach(item => {
    item.addEventListener('mouseenter', function() {
        const submenu = this.children[1];  // Second child is dropdown
        if (submenu) submenu.style.display = 'block';
    });
    
    item.addEventListener('mouseleave', function() {
        const submenu = this.children[1];
        if (submenu) submenu.style.display = 'none';
    });
});
</script>

Sibling Navigation

const referenceNode = document.querySelector('.reference');

// Immediate siblings (includes text nodes)
const nextNode = referenceNode.nextSibling;
const prevNode = referenceNode.previousSibling;

// Element siblings only (IE9+)
const nextElement = referenceNode.nextElementSibling;
const prevElement = referenceNode.previousElementSibling;

// Polyfill for older browsers
function getNextElement(node) {
    let sibling = node.nextSibling;
    while (sibling && sibling.nodeType !== 1) {
        sibling = sibling.nextSibling;
    }
    return sibling;
}

Dynamic Element Creation

Creation and Insertion Methods

// 1. Create element
const newElement = document.createElement('article');
newElement.className = 'post-item';
newElement.textContent = 'Dynamic content';

// 2. Append to parent (end of children list)
parentContainer.appendChild(newElement);

// 3. Insert before specific reference
const referenceChild = parentContainer.children[0];
parentContainer.insertBefore(newElement, referenceChild);
// If reference is null, behaves like appendChild

Example: Comment System

<style>
.comment-section { max-width: 500px; margin: 20px auto; }
.comment-input {
    width: 100%;
    height: 80px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    margin-bottom: 10px;
}
.comment-list { list-style: none; margin-top: 20px; }
.comment-item {
    background: #f8f9fa;
    padding: 15px;
    margin-bottom: 10px;
    border-radius: 4px;
    position: relative;
}
.delete-btn {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    color: #dc3545;
    cursor: pointer;
    font-size: 12px;
}
</style>

<div class="comment-section">
    <textarea class="comment-input" placeholder="Enter your comment..."></textarea>
    <button id="submit-comment">Post Comment</button>
    <ul class="comment-list"></ul>
</div>

<script>
const postBtn = document.getElementById('submit-comment');
const textArea = document.querySelector('.comment-input');
const commentList = document.querySelector('.comment-list');

postBtn.addEventListener('click', function() {
    const content = textArea.value.trim();
    
    if (!content) {
        alert('Please enter comment text');
        return;
    }
    
    // Create structure
    const listItem = document.createElement('li');
    listItem.className = 'comment-item';
    
    const textSpan = document.createElement('span');
    textSpan.textContent = content;
    
    const removeLink = document.createElement('a');
    removeLink.href = 'javascript:void(0);';
    removeLink.className = 'delete-btn';
    removeLink.textContent = 'Delete';
    
    // Assemble
    listItem.appendChild(textSpan);
    listItem.appendChild(removeLink);
    
    // Insert at top
    commentList.insertBefore(listItem, commentList.firstChild);
    
    // Clear input
    textArea.value = '';
    
    // Bind delete functionality
    removeLink.addEventListener('click', function() {
        commentList.removeChild(listItem);
    });
});
</script>

Node Cloning

const original = document.querySelector('.template-item');

// Shallow clone: copies node only, no children
const shallowCopy = original.cloneNode();

// Deep clone: copies node and entire subtree
const deepCopy = original.cloneNode(true);

// Modify and attach
deepCopy.querySelector('.title').textContent = 'Cloned Item';
document.querySelector('.container').appendChild(deepCopy);

Creation Method Comparison

Three approaches for generating DOM content dynamically:

1. document.write()

Caution: Executes during document parse. If called after page load, replaces entire document.

// Only safe during initial load
document.write('<div>Initial content</div>');

// DANGEROUS after load - destroys existing DOM
document.write('<div>This clears the page!</div>');

2. innerHTML

Parses HTML string into DOM structure. Efficient for bulk updates using array concatenation.

const container = document.querySelector('.content');

// Inefficient: causes multiple reflows
for (let i = 0; i < 100; i++) {
    container.innerHTML += `<div>Item ${i}</div>`;  // Slow
}

// Efficient: single DOM update
const htmlFragments = [];
for (let i = 0; i < 100; i++) {
    htmlFragments.push(`<div>Item ${i}</div>`);
}
container.innerHTML = htmlFragments.join('');

3. createElement()

Programmatic node cosntruction. Clearer structure, slightly slower for large batches.

const wrapper = document.querySelector('.wrapper');

for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    div.textContent = `Node ${i}`;
    div.className = 'dynamic-item';
    wrapper.appendChild(div);
}

Performance Characteristics:

  • document.write(): Avoid for dynamic updates (page repaint risk)
  • innerHTML: Fastest for bulk HTML generation via string concatenation
  • createElement(): Best for maintaining references to created nodes and event binding during creasion

Recommendation: Use createElement when you need to attach event listeners immediately; use innerHTML with array joining for static content injection.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.