DOM Modification Basics
Once you've selected elements, the real magic begins. Modifying DOM elements is like being a digital sculptor - you can change text, alter styles, update attributes, and completely transform how elements look and behave. Think of it as having a remote control for every element on your webpage.
Modifying Text Content
There are several ways to change the text inside elements, each with different behaviors and use cases.
textContent vs innerHTML vs innerText
const element = document.querySelector('.content');
// textContent - Sets or gets text content (including hidden elements)
element.textContent = 'Simple text content';
console.log(element.textContent); // Gets all text, even from hidden elements
// innerHTML - Sets or gets HTML content (parses HTML)
element.innerHTML = '<strong>Bold text</strong> with <em>emphasis</em>';
// innerText - Sets or gets visible text (respects CSS)
element.innerText = 'Visible text only'; // Aware of styling
// Comparing the differences
const example = document.querySelector('.example');
example.innerHTML = '<p style="display:none">Hidden</p><p>Visible</p>';
console.log(example.textContent); // "HiddenVisible"
console.log(example.innerText); // "Visible"
console.log(example.innerHTML); // "<p style="display:none">Hidden</p><p>Visible</p>"
Security Considerations
// Dangerous - susceptible to XSS attacks
const userInput = '<img src="x" onerror="alert(\'hacked!\')">';
element.innerHTML = userInput; // This executes the script!
// Safe - treats input as plain text
element.textContent = userInput; // Displays the text literally
// Safe HTML insertion with sanitization
function safeSetHTML(element, html) {
const temp = document.createElement('div');
temp.textContent = html; // Escapes HTML
element.innerHTML = temp.innerHTML;
}
// Using template literals safely
const userName = 'John <script>alert("xss")</script>';
element.textContent = `Welcome, ${userName}!`; // Safe
// For complex HTML, use createElement methods instead
const safeDiv = document.createElement('div');
safeDiv.textContent = userName;
element.appendChild(safeDiv);
Modifying Styles
CSS styles can be modified directly through JavaScript in multiple ways.
Using the style Property
const element = document.querySelector('.box');
// Setting individual style properties
element.style.backgroundColor = 'blue';
element.style.color = 'white';
element.style.padding = '10px';
element.style.borderRadius = '5px';
// CSS property names in camelCase
element.style.fontSize = '16px';
element.style.marginTop = '20px';
element.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
// Setting multiple styles at once
Object.assign(element.style, {
backgroundColor: 'red',
color: 'white',
padding: '15px',
borderRadius: '8px',
transition: 'all 0.3s ease'
});
// Using CSS custom properties (variables)
element.style.setProperty('--primary-color', '#007bff');
element.style.backgroundColor = 'var(--primary-color)';
// Removing styles
element.style.backgroundColor = ''; // Removes inline style
element.style.removeProperty('padding'); // Alternative method
// Getting computed styles
const computedStyles = window.getComputedStyle(element);
console.log(computedStyles.backgroundColor);
console.log(computedStyles.getPropertyValue('font-size'));
Working with CSS Classes
const element = document.querySelector('.card');
// Adding classes
element.classList.add('active');
element.classList.add('highlighted', 'featured'); // Multiple classes
// Removing classes
element.classList.remove('active');
element.classList.remove('highlighted', 'featured');
// Toggle classes
element.classList.toggle('collapsed'); // Adds if missing, removes if present
// Toggle with condition
const shouldHighlight = true;
element.classList.toggle('highlighted', shouldHighlight);
// Check if class exists
if (element.classList.contains('active')) {
console.log('Element is active');
}
// Replace a class
element.classList.replace('old-class', 'new-class');
// Get all classes as array
const classes = [...element.classList];
// Working with className (older method)
element.className = 'card active'; // Replaces all classes
element.className += ' highlighted'; // Adds a class (note the space)
// Advanced class manipulation
function addTemporaryClass(element, className, duration = 1000) {
element.classList.add(className);
setTimeout(() => {
element.classList.remove(className);
}, duration);
}
Working with Attributes
HTML attributes can be read, modified, and removed using various methods.
Standard Attribute Methods
const link = document.querySelector('a');
const input = document.querySelector('input');
// Getting attributes
const href = link.getAttribute('href');
const inputType = input.getAttribute('type');
// Setting attributes
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
input.setAttribute('placeholder', 'Enter your name');
input.setAttribute('required', ''); // Boolean attributes
// Removing attributes
input.removeAttribute('required');
link.removeAttribute('target');
// Checking if attribute exists
if (input.hasAttribute('required')) {
console.log('This field is required');
}
// Working with boolean attributes
input.disabled = true; // Sets disabled attribute
input.disabled = false; // Removes disabled attribute
// Direct property access (for standard attributes)
input.value = 'New value';
input.type = 'password';
link.href = 'https://newsite.com';
// Custom attributes (data attributes)
element.setAttribute('data-user-id', '12345');
element.setAttribute('data-role', 'admin');
Data Attributes
const element = document.querySelector('.user-card');
// Setting data attributes
element.dataset.userId = '12345';
element.dataset.userName = 'John Doe';
element.dataset.lastLogin = '2024-01-15';
// Reading data attributes
console.log(element.dataset.userId); // '12345'
console.log(element.dataset.userName); // 'John Doe'
// Complex data attribute names
element.dataset.userRole = 'admin'; // data-user-role in HTML
element.dataset.isActive = 'true'; // data-is-active in HTML
// Working with data attributes in HTML
// <div data-user-id="12345" data-user-role="admin"></div>
// Iterating over all data attributes
for (let key in element.dataset) {
console.log(`${key}: ${element.dataset[key]}`);
}
// Converting dataset to object
const dataObject = { ...element.dataset };
// Using data attributes for configuration
function initializeComponent(element) {
const config = {
animationSpeed: parseInt(element.dataset.animationSpeed) || 300,
theme: element.dataset.theme || 'light',
autoplay: element.dataset.autoplay === 'true'
};
return config;
}
Working with Form Elements
Form elements have special properties and methods for handling user input.
Input Elements
const form = document.querySelector('form');
const textInput = document.querySelector('input[type="text"]');
const checkbox = document.querySelector('input[type="checkbox"]');
const select = document.querySelector('select');
// Text input manipulation
textInput.value = 'Initial value';
textInput.placeholder = 'Enter your name';
textInput.maxLength = 50;
textInput.readOnly = true;
// Checkbox manipulation
checkbox.checked = true;
checkbox.indeterminate = true; // Special visual state
// Select element manipulation
select.value = 'option2'; // Select by value
select.selectedIndex = 1; // Select by index
// Working with select options
const newOption = document.createElement('option');
newOption.value = 'new';
newOption.textContent = 'New Option';
select.appendChild(newOption);
// Radio buttons
const radioButtons = document.querySelectorAll('input[name="gender"]');
radioButtons.forEach(radio => {
if (radio.value === 'male') {
radio.checked = true;
}
});
// Form manipulation
form.reset(); // Reset all form fields
form.submit(); // Submit the form programmatically
// Accessing form elements
console.log(form.elements); // All form controls
console.log(form.elements.username); // Access by name
console.log(form.elements['email']); // Alternative syntax
Form Validation States
const input = document.querySelector('input[required]');
// Check validity
if (input.validity.valid) {
console.log('Input is valid');
} else {
console.log('Input is invalid');
}
// Detailed validity state
console.log(input.validity.valueMissing); // Required but empty
console.log(input.validity.typeMismatch); // Wrong type (e.g., email)
console.log(input.validity.patternMismatch); // Doesn't match pattern
console.log(input.validity.tooLong); // Exceeds maxLength
console.log(input.validity.tooShort); // Below minLength
console.log(input.validity.rangeUnderflow); // Below min
console.log(input.validity.rangeOverflow); // Above max
// Custom validation messages
input.setCustomValidity('Please enter a valid email address');
// Clear custom validation
input.setCustomValidity('');
// Trigger validation
input.checkValidity(); // Returns true/false
form.checkValidity(); // Check entire form
// Report validation errors
input.reportValidity(); // Shows browser validation UI
Real-World Applications
Dynamic Form Builder
class FormBuilder {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.form = document.createElement('form');
this.container.appendChild(this.form);
}
addField(config) {
const fieldWrapper = document.createElement('div');
fieldWrapper.className = 'form-field';
// Create label
const label = document.createElement('label');
label.textContent = config.label;
label.setAttribute('for', config.id);
// Create input based on type
let input;
switch (config.type) {
case 'select':
input = this.createSelect(config);
break;
case 'textarea':
input = document.createElement('textarea');
break;
default:
input = document.createElement('input');
input.type = config.type || 'text';
}
// Set common attributes
input.id = config.id;
input.name = config.name || config.id;
if (config.required) {
input.required = true;
}
if (config.placeholder) {
input.placeholder = config.placeholder;
}
// Add to DOM
fieldWrapper.appendChild(label);
fieldWrapper.appendChild(input);
this.form.appendChild(fieldWrapper);
return input;
}
createSelect(config) {
const select = document.createElement('select');
config.options.forEach(optionConfig => {
const option = document.createElement('option');
option.value = optionConfig.value;
option.textContent = optionConfig.label;
select.appendChild(option);
});
return select;
}
addSubmitButton(text = 'Submit') {
const button = document.createElement('button');
button.type = 'submit';
button.textContent = text;
button.className = 'submit-button';
this.form.appendChild(button);
}
}
// Usage
const builder = new FormBuilder('form-container');
builder.addField({
type: 'text',
id: 'username',
label: 'Username',
required: true,
placeholder: 'Enter username'
});
builder.addField({
type: 'select',
id: 'country',
label: 'Country',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' }
]
});
builder.addSubmitButton('Register');
Interactive Style Editor
class StyleEditor {
constructor(targetSelector) {
this.target = document.querySelector(targetSelector);
this.createControls();
}
createControls() {
const controls = document.createElement('div');
controls.className = 'style-controls';
// Background color control
this.addColorControl(controls, 'Background', 'backgroundColor');
// Text color control
this.addColorControl(controls, 'Text Color', 'color');
// Font size control
this.addRangeControl(controls, 'Font Size', 'fontSize', 12, 48, 'px');
// Padding control
this.addRangeControl(controls, 'Padding', 'padding', 0, 50, 'px');
// Border radius control
this.addRangeControl(controls, 'Border Radius', 'borderRadius', 0, 50, 'px');
document.body.insertBefore(controls, document.body.firstChild);
}
addColorControl(container, label, property) {
const wrapper = document.createElement('div');
wrapper.className = 'control-group';
const labelElement = document.createElement('label');
labelElement.textContent = label;
const input = document.createElement('input');
input.type = 'color';
input.value = '#000000';
input.addEventListener('input', (e) => {
this.target.style[property] = e.target.value;
});
wrapper.appendChild(labelElement);
wrapper.appendChild(input);
container.appendChild(wrapper);
}
addRangeControl(container, label, property, min, max, unit = '') {
const wrapper = document.createElement('div');
wrapper.className = 'control-group';
const labelElement = document.createElement('label');
labelElement.textContent = label;
const input = document.createElement('input');
input.type = 'range';
input.min = min;
input.max = max;
input.value = min;
const valueDisplay = document.createElement('span');
valueDisplay.textContent = `${min}${unit}`;
input.addEventListener('input', (e) => {
const value = e.target.value;
valueDisplay.textContent = `${value}${unit}`;
this.target.style[property] = `${value}${unit}`;
});
wrapper.appendChild(labelElement);
wrapper.appendChild(input);
wrapper.appendChild(valueDisplay);
container.appendChild(wrapper);
}
}
// Usage
const editor = new StyleEditor('.editable-element');
Dynamic Table Editor
class TableEditor {
constructor(tableId) {
this.table = document.getElementById(tableId);
this.makeEditable();
}
makeEditable() {
const cells = this.table.querySelectorAll('td');
cells.forEach(cell => {
cell.setAttribute('contenteditable', 'true');
cell.addEventListener('blur', () => this.saveCell(cell));
cell.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
cell.blur();
}
});
});
}
saveCell(cell) {
const row = cell.parentElement;
const rowIndex = row.rowIndex;
const cellIndex = cell.cellIndex;
const value = cell.textContent;
// Save to data attribute
cell.dataset.originalValue = value;
// Trigger custom event
const event = new CustomEvent('cellchange', {
detail: { rowIndex, cellIndex, value }
});
this.table.dispatchEvent(event);
}
addRow(data = []) {
const tbody = this.table.querySelector('tbody');
const row = tbody.insertRow();
// Get column count from header
const columnCount = this.table.querySelector('thead tr').cells.length;
for (let i = 0; i < columnCount; i++) {
const cell = row.insertCell();
cell.textContent = data[i] || '';
cell.setAttribute('contenteditable', 'true');
cell.addEventListener('blur', () => this.saveCell(cell));
cell.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
cell.blur();
}
});
}
return row;
}
deleteRow(rowIndex) {
if (rowIndex > 0) { // Don't delete header
this.table.deleteRow(rowIndex);
}
}
getData() {
const rows = this.table.querySelectorAll('tbody tr');
const data = [];
rows.forEach(row => {
const rowData = [];
row.querySelectorAll('td').forEach(cell => {
rowData.push(cell.textContent);
});
data.push(rowData);
});
return data;
}
}
// Usage
const tableEditor = new TableEditor('data-table');
// Listen for changes
document.getElementById('data-table').addEventListener('cellchange', (e) => {
console.log('Cell changed:', e.detail);
});
Performance Best Practices
- Minimize reflows: Batch DOM updates together
- Use CSS classes: Instead of multiple style properties
- Cache DOM references: Don't query the same element repeatedly
- Use DocumentFragment: For multiple element insertions
- Avoid forced synchronous layouts: Reading then writing styles
// Bad: Causes multiple reflows
const element = document.querySelector('.box');
element.style.width = '100px';
console.log(element.offsetWidth); // Forces reflow
element.style.height = '100px';
console.log(element.offsetHeight); // Forces another reflow
// Good: Batch updates
const element = document.querySelector('.box');
element.style.cssText = 'width: 100px; height: 100px;';
// Or
element.className = 'box sized'; // Using CSS class
// Bad: Multiple style updates
items.forEach(item => {
item.style.color = 'red';
item.style.backgroundColor = 'yellow';
item.style.border = '1px solid black';
});
// Good: Use CSS class
items.forEach(item => {
item.classList.add('highlighted');
});
// Good: Batch DOM reads and writes
const elements = document.querySelectorAll('.item');
const heights = [];
// Read phase
elements.forEach(el => {
heights.push(el.offsetHeight);
});
// Write phase
elements.forEach((el, index) => {
el.style.height = heights[index] * 2 + 'px';
});
Best Practices
- Prefer textContent over innerHTML: Unless you need to insert HTML
- Use data attributes: For custom data storage
- Validate user input: Always sanitize before inserting into DOM
- Use CSS classes: For styling instead of inline styles
- Check element existence: Before modifying properties
// Good practices example
function updateElement(selector, content, options = {}) {
const element = document.querySelector(selector);
if (!element) {
console.warn(`Element not found: ${selector}`);
return false;
}
// Update content safely
if (options.isHTML && options.trusted) {
element.innerHTML = content;
} else {
element.textContent = content;
}
// Apply classes if provided
if (options.classes) {
element.className = options.classes.join(' ');
}
// Apply data attributes if provided
if (options.data) {
Object.entries(options.data).forEach(([key, value]) => {
element.dataset[key] = value;
});
}
return true;
}
// Usage
updateElement('.message', 'Updated successfully!', {
classes: ['alert', 'alert-success'],
data: {
timestamp: Date.now(),
status: 'success'
}
});
Summary
Modifying DOM elements gives you complete control over your web page:
- Use textContent for safe text updates, innerHTML for HTML content
- Modify styles through style property or classList
- Work with attributes using getAttribute/setAttribute
- Use dataset for custom data attributes
- Handle form elements with their special properties
- Always consider security and performance
Next up: We'll learn how to create and remove elements dynamically!