DOM Manipulation Techniques: Exclusive Selection Patterns and Node Operations
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:
- Remove the target style from all elements (clear the group)
- Apply the style exclusively to the current element
- 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:
- Register click events on thumbnail collection using iteration
- Extract the
srcattribute from clicked thumbnail - 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 propertieselement.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 identifiernodeValue: 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 concatenationcreateElement(): 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.