javascript-today

Element Attributes and Properties

Understanding how to work with element attributes, classes, and styles is crucial for creating dynamic web pages. Let’s explore the powerful methods JavaScript provides.

HTML Attributes vs DOM Properties

Important distinction:

<input type="text" value="default" id="myInput">
const input = document.querySelector('#myInput');

// Attribute (in HTML): Initial value
console.log(input.getAttribute('value')); // "default"

// Property (in DOM): Current value
console.log(input.value); // "default" (initially)

// User types "hello"
console.log(input.getAttribute('value')); // Still "default"
console.log(input.value); // "hello" (current value)

Key difference:

  • Attributes: HTML source, static
  • Properties: DOM object, dynamic

Working with Attributes

getAttribute() and setAttribute()

const link = document.querySelector('a');

// Get attribute
const href = link.getAttribute('href');
const target = link.getAttribute('target');

// Set attribute
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');
link.setAttribute('title', 'Visit Example');

// Check if attribute exists
if (link.hasAttribute('download')) {
  console.log('This is a download link');
}

// Remove attribute
link.removeAttribute('target');

Common Attributes

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

// Image attributes
img.setAttribute('src', 'photo.jpg');
img.setAttribute('alt', 'Photo description');
img.setAttribute('width', '300');
img.setAttribute('loading', 'lazy');

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

// Form attributes
form.setAttribute('action', '/submit');
form.setAttribute('method', 'POST');

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

// Input attributes
input.setAttribute('type', 'email');
input.setAttribute('placeholder', 'Enter email');
input.setAttribute('required', '');
input.setAttribute('disabled', '');

Boolean Attributes

Special handling for attributes like disabled, checked, required:

const button = document.querySelector('button');
const checkbox = document.querySelector('input[type="checkbox"]');

// Enable/disable button
button.disabled = true;  // Disabled
button.disabled = false; // Enabled

// Or with attributes
button.setAttribute('disabled', ''); // Disabled
button.removeAttribute('disabled');  // Enabled

// Checkbox
checkbox.checked = true;  // Checked
checkbox.checked = false; // Unchecked

// Check if checked
if (checkbox.checked) {
  console.log('Checkbox is checked');
}

Working with Classes

classList API

Modern way to manage classes:

const element = document.querySelector('#box');

// Add class
element.classList.add('active');

// Add multiple classes
element.classList.add('highlight', 'border');

// Remove class
element.classList.remove('active');

// Toggle class
element.classList.toggle('hidden'); // Add if not present, remove if present

// Toggle with condition
element.classList.toggle('visible', isVisible); // Add if isVisible is true

// Check if has class
if (element.classList.contains('active')) {
  console.log('Element is active');
}

// Replace class
element.classList.replace('old-class', 'new-class');

// Get all classes as array
const classes = Array.from(element.classList);
console.log(classes); // ['highlight', 'border', ...]

className (Old Way)

const element = document.querySelector('#box');

// Get all classes as string
console.log(element.className); // "box active highlight"

// Set classes (replaces all)
element.className = 'new-class another-class';

// Add class (not recommended - error-prone)
element.className += ' extra-class';

Recommendation: Use classList instead of className

Practical Class Examples

// Toggle menu
const menuButton = document.querySelector('#menu-button');
const menu = document.querySelector('#menu');

menuButton.addEventListener('click', () => {
  menu.classList.toggle('open');
});

// Tab switching
const tabs = document.querySelectorAll('.tab');

tabs.forEach(tab => {
  tab.addEventListener('click', () => {
    // Remove active from all tabs
    tabs.forEach(t => t.classList.remove('active'));
    
    // Add active to clicked tab
    tab.classList.add('active');
  });
});

// Theme switcher
const themeButton = document.querySelector('#theme-toggle');

themeButton.addEventListener('click', () => {
  document.body.classList.toggle('dark-mode');
  
  // Save preference
  const isDark = document.body.classList.contains('dark-mode');
  localStorage.setItem('theme', isDark ? 'dark' : 'light');
});

Data Attributes

Custom data attributes (data-*) for storing information:

HTML

<button 
  data-user-id="123" 
  data-action="delete"
  data-confirm-message="Are you sure?">
  Delete
</button>

<div 
  data-product-id="42"
  data-product-name="Laptop"
  data-product-price="999">
  Laptop - $999
</div>

JavaScript Access

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

// Get data attributes
const userId = button.dataset.userId;        // "123"
const action = button.dataset.action;        // "delete"
const message = button.dataset.confirmMessage; // "Are you sure?"

// Note: data-user-id becomes dataset.userId (camelCase)
// data-confirm-message becomes dataset.confirmMessage

// Set data attributes
button.dataset.userId = "456";
button.dataset.newAttribute = "value";

// Remove data attribute
delete button.dataset.action;

// Check if exists
if ('userId' in button.dataset) {
  console.log('Has user ID');
}

Practical Data Attribute Examples

// Product cards
const productCards = document.querySelectorAll('.product-card');

productCards.forEach(card => {
  card.addEventListener('click', () => {
    const productId = card.dataset.productId;
    const productName = card.dataset.productName;
    const price = parseFloat(card.dataset.productPrice);
    
    addToCart({ id: productId, name: productName, price });
  });
});

// Modal dialogs
const openButtons = document.querySelectorAll('[data-modal]');

openButtons.forEach(button => {
  button.addEventListener('click', () => {
    const modalId = button.dataset.modal;
    const modal = document.querySelector(`#${modalId}`);
    modal.classList.add('open');
  });
});

// Sortable table
const headers = document.querySelectorAll('[data-sortable]');

headers.forEach(header => {
  header.addEventListener('click', () => {
    const column = header.dataset.sortColumn;
    const order = header.dataset.sortOrder === 'asc' ? 'desc' : 'asc';
    
    sortTable(column, order);
    
    header.dataset.sortOrder = order;
  });
});

Inline Styles

style Property

Manipulate inline CSS:

const box = document.querySelector('#box');

// Set individual styles
box.style.color = 'blue';
box.style.backgroundColor = 'yellow'; // Note: camelCase
box.style.fontSize = '20px';
box.style.border = '2px solid red';

// Get computed style
const color = box.style.color; // Only gets inline styles

// Remove style
box.style.color = '';
// Or
box.style.removeProperty('color');

CSS Property Names

CSS to JavaScript naming:

// CSS → JavaScript (camelCase)
// background-color → backgroundColor
// font-size → fontSize
// margin-top → marginTop
// border-radius → borderRadius

element.style.backgroundColor = 'red';
element.style.fontSize = '16px';
element.style.marginTop = '10px';

Getting Computed Styles

Get actual rendered styles (including CSS from stylesheets):

const element = document.querySelector('#box');

// Get computed styles
const styles = window.getComputedStyle(element);

console.log(styles.color);           // "rgb(0, 0, 255)"
console.log(styles.fontSize);        // "16px"
console.log(styles.backgroundColor); // "rgb(255, 255, 0)"

// Get specific property
const width = styles.getPropertyValue('width');
console.log(width); // "200px"

Setting Multiple Styles

const element = document.querySelector('#box');

// Method 1: One by one
element.style.color = 'white';
element.style.backgroundColor = 'black';
element.style.padding = '20px';

// Method 2: cssText (replaces all inline styles)
element.style.cssText = `
  color: white;
  background-color: black;
  padding: 20px;
  border-radius: 5px;
`;

// Method 3: setAttribute (replaces all inline styles)
element.setAttribute('style', 'color: white; background-color: black;');

// Method 4: Object.assign (modern)
Object.assign(element.style, {
  color: 'white',
  backgroundColor: 'black',
  padding: '20px'
});

Best Practice: Use Classes, Not Inline Styles

/* CSS file */
.highlighted {
  background-color: yellow;
  font-weight: bold;
}

.hidden {
  display: none;
}
// ✅ GOOD: Use classes
element.classList.add('highlighted');
element.classList.toggle('hidden');

// ❌ AVOID: Inline styles for layout/theme
element.style.backgroundColor = 'yellow';
element.style.fontWeight = 'bold';
element.style.display = 'none';

// ✅ OK: Inline styles for dynamic values
element.style.width = `${percentage}%`;
element.style.transform = `translateX(${position}px)`;

Complete Examples

<div class="gallery">
  <img data-full-size="images/photo1-full.jpg" src="images/photo1-thumb.jpg" alt="Photo 1">
  <img data-full-size="images/photo2-full.jpg" src="images/photo2-thumb.jpg" alt="Photo 2">
  <img data-full-size="images/photo3-full.jpg" src="images/photo3-thumb.jpg" alt="Photo 3">
</div>

<div id="lightbox" class="hidden">
  <img id="lightbox-image" src="" alt="">
</div>
const thumbnails = document.querySelectorAll('.gallery img');
const lightbox = document.querySelector('#lightbox');
const lightboxImage = document.querySelector('#lightbox-image');

thumbnails.forEach(thumb => {
  thumb.addEventListener('click', () => {
    const fullSizeUrl = thumb.dataset.fullSize;
    const alt = thumb.getAttribute('alt');
    
    lightboxImage.src = fullSizeUrl;
    lightboxImage.alt = alt;
    lightbox.classList.remove('hidden');
  });
});

lightbox.addEventListener('click', () => {
  lightbox.classList.add('hidden');
});

Progress Bar

function updateProgress(percentage) {
  const bar = document.querySelector('#progress-bar');
  const text = document.querySelector('#progress-text');
  
  // Update width
  bar.style.width = `${percentage}%`;
  
  // Update text
  text.textContent = `${percentage}%`;
  
  // Change color based on progress
  if (percentage < 30) {
    bar.className = 'progress-low';
  } else if (percentage < 70) {
    bar.className = 'progress-medium';
  } else {
    bar.className = 'progress-high';
  }
  
  // Store value
  bar.dataset.progress = percentage;
}

updateProgress(75);

Accordion Component

const accordionHeaders = document.querySelectorAll('.accordion-header');

accordionHeaders.forEach(header => {
  header.addEventListener('click', () => {
    const content = header.nextElementSibling;
    const isOpen = header.classList.contains('active');
    
    // Close all accordions
    accordionHeaders.forEach(h => {
      h.classList.remove('active');
      h.setAttribute('aria-expanded', 'false');
      h.nextElementSibling.style.maxHeight = null;
    });
    
    // Open clicked accordion (if it was closed)
    if (!isOpen) {
      header.classList.add('active');
      header.setAttribute('aria-expanded', 'true');
      content.style.maxHeight = content.scrollHeight + 'px';
    }
  });
});

Best Practices

DO:

  • Use classList for class manipulation
  • Use data attributes for custom data
  • Use CSS classes instead of inline styles when possible
  • Use getComputedStyle() to read actual styles
  • Use camelCase for style properties

DON’T:

  • Manipulate className directly (use classList)
  • Store complex data in data attributes (use JavaScript objects)
  • Set layout styles inline (use CSS classes)
  • Forget to remove event listeners on removed elements

Summary

Task Method Example
Get attribute getAttribute() el.getAttribute('href')
Set attribute setAttribute() el.setAttribute('href', 'url')
Add class classList.add() el.classList.add('active')
Remove class classList.remove() el.classList.remove('hidden')
Toggle class classList.toggle() el.classList.toggle('open')
Check class classList.contains() el.classList.contains('active')
Get data attr dataset el.dataset.userId
Set data attr dataset el.dataset.userId = '123'
Set style style el.style.color = 'red'
Get computed getComputedStyle() getComputedStyle(el).color