javascript
exercises
exercises.js⚡javascript
/**
* ============================================================
* 10.2 Modifying the DOM - Exercises
* ============================================================
*
* Practice modifying DOM elements including content,
* attributes, classes, and styles.
*
* Instructions:
* 1. Read each exercise description carefully
* 2. Write your solution in the provided function
* 3. Test with the example HTML provided
* 4. Check the hints and solutions if needed
*/
// =============================================================
// EXERCISE 1: Update Text Content
// =============================================================
/**
* Update the text content of an element safely (no HTML parsing)
*
* @param {string} elementId - The ID of the element to update
* @param {string} newText - The new text content
* @returns {boolean} - True if successful, false if element not found
*
* Example HTML: <p id="greeting">Hello</p>
* updateText("greeting", "Welcome!") → element shows "Welcome!"
*/
function updateText(elementId, newText) {
// Your code here
}
// Test
// console.log(updateText("greeting", "Welcome, User!"));
/*
* HINT: Use textContent to safely set text without HTML parsing
*
* SOLUTION:
* function updateText(elementId, newText) {
* const element = document.getElementById(elementId);
* if (!element) return false;
* element.textContent = newText;
* return true;
* }
*/
// =============================================================
// EXERCISE 2: Safe HTML Insertion
// =============================================================
/**
* Insert HTML content with basic sanitization
* Remove any <script> tags from the content before inserting
*
* @param {string} elementId - The ID of the target element
* @param {string} htmlContent - The HTML content to insert
* @returns {boolean} - True if successful
*
* Example:
* safeInsertHTML("container", "<p>Hello</p><script>evil()</script>")
* → Only inserts "<p>Hello</p>"
*/
function safeInsertHTML(elementId, htmlContent) {
// Your code here
}
// Test
// safeInsertHTML("container", "<p>Safe content</p><script>alert('xss')</script>");
/*
* HINT: Use regex to remove script tags: /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
*
* SOLUTION:
* function safeInsertHTML(elementId, htmlContent) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* // Remove script tags
* const sanitized = htmlContent.replace(
* /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
* ""
* );
*
* element.innerHTML = sanitized;
* return true;
* }
*/
// =============================================================
// EXERCISE 3: Toggle Attribute
// =============================================================
/**
* Toggle an attribute on an element (add if missing, remove if present)
*
* @param {string} elementId - The ID of the element
* @param {string} attributeName - The name of the attribute to toggle
* @param {string} attributeValue - The value to set when adding (default: "")
* @returns {boolean} - True if attribute is now present, false if removed
*
* Example HTML: <button id="btn">Click</button>
* toggleAttribute("btn", "disabled") → adds disabled, returns true
* toggleAttribute("btn", "disabled") → removes disabled, returns false
*/
function toggleAttribute(elementId, attributeName, attributeValue = '') {
// Your code here
}
// Test
// console.log(toggleAttribute("submitBtn", "disabled"));
/*
* HINT: Use hasAttribute to check, then setAttribute or removeAttribute
*
* SOLUTION:
* function toggleAttribute(elementId, attributeName, attributeValue = "") {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* if (element.hasAttribute(attributeName)) {
* element.removeAttribute(attributeName);
* return false;
* } else {
* element.setAttribute(attributeName, attributeValue);
* return true;
* }
* }
*/
// =============================================================
// EXERCISE 4: Set Multiple Attributes
// =============================================================
/**
* Set multiple attributes on an element at once
*
* @param {string} elementId - The ID of the element
* @param {Object} attributes - Object with attribute name-value pairs
* @returns {boolean} - True if successful
*
* Example:
* setAttributes("myImg", {
* src: "photo.jpg",
* alt: "A photo",
* width: "300",
* height: "200"
* });
*/
function setAttributes(elementId, attributes) {
// Your code here
}
// Test
// setAttributes("testImg", { src: "test.jpg", alt: "Test", loading: "lazy" });
/*
* HINT: Use Object.entries() to iterate over the attributes object
*
* SOLUTION:
* function setAttributes(elementId, attributes) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* Object.entries(attributes).forEach(([name, value]) => {
* element.setAttribute(name, value);
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 5: Class Toggle with State
// =============================================================
/**
* Toggle a class and return the new state
* Also update a data attribute to track the state
*
* @param {string} elementId - The ID of the element
* @param {string} className - The class to toggle
* @returns {Object} - { hasClass: boolean, element: Element }
*
* Example:
* toggleClassWithState("menu", "open")
* → { hasClass: true, element: <element> } if class was added
*/
function toggleClassWithState(elementId, className) {
// Your code here
}
// Test
// console.log(toggleClassWithState("sidebar", "collapsed"));
/*
* HINT: Use classList.toggle() which returns the new state
*
* SOLUTION:
* function toggleClassWithState(elementId, className) {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* const hasClass = element.classList.toggle(className);
* element.dataset[className] = hasClass;
*
* return { hasClass, element };
* }
*/
// =============================================================
// EXERCISE 6: Add/Remove Multiple Classes
// =============================================================
/**
* Manage multiple classes on an element
*
* @param {string} elementId - The ID of the element
* @param {Object} classChanges - { add: [...classes], remove: [...classes] }
* @returns {string[]} - Array of current classes after changes
*
* Example:
* manageClasses("box", {
* add: ["active", "highlighted"],
* remove: ["hidden", "disabled"]
* });
*/
function manageClasses(elementId, classChanges) {
// Your code here
}
// Test
// console.log(manageClasses("card", { add: ["active", "animated"], remove: ["hidden"] }));
/*
* HINT: Use classList.add() and classList.remove() with spread operator
*
* SOLUTION:
* function manageClasses(elementId, classChanges) {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* if (classChanges.add && classChanges.add.length) {
* element.classList.add(...classChanges.add);
* }
*
* if (classChanges.remove && classChanges.remove.length) {
* element.classList.remove(...classChanges.remove);
* }
*
* return Array.from(element.classList);
* }
*/
// =============================================================
// EXERCISE 7: Switch Active Item
// =============================================================
/**
* Switch the "active" class to a specific item among siblings
* Remove "active" from all items, add to the specified one
*
* @param {string} containerId - The ID of the container
* @param {string} activeItemSelector - Selector for the item to make active
* @returns {Element|null} - The newly active element
*
* Example HTML:
* <ul id="menu">
* <li class="active">Home</li>
* <li>About</li>
* <li>Contact</li>
* </ul>
*
* switchActive("menu", "li:nth-child(2)") → About becomes active
*/
function switchActive(containerId, activeItemSelector) {
// Your code here
}
// Test
// switchActive("tabList", "[data-tab='settings']");
/*
* HINT: Get all children, remove active from all, add to selected
*
* SOLUTION:
* function switchActive(containerId, activeItemSelector) {
* const container = document.getElementById(containerId);
* if (!container) return null;
*
* // Remove active from all children
* container.querySelectorAll(".active").forEach(item => {
* item.classList.remove("active");
* });
*
* // Add active to selected item
* const activeItem = container.querySelector(activeItemSelector);
* if (activeItem) {
* activeItem.classList.add("active");
* }
*
* return activeItem;
* }
*/
// =============================================================
// EXERCISE 8: Apply Style Object
// =============================================================
/**
* Apply a style object to an element
* Support both camelCase and kebab-case property names
*
* @param {string} elementId - The ID of the element
* @param {Object} styles - Object with CSS property-value pairs
* @returns {boolean} - True if successful
*
* Example:
* applyStyles("box", {
* "background-color": "blue",
* fontSize: "16px",
* "border-radius": "8px"
* });
*/
function applyStyles(elementId, styles) {
// Your code here
}
// Test
// applyStyles("testBox", { backgroundColor: "red", padding: "20px" });
/*
* HINT: Convert kebab-case to camelCase, then set on style object
*
* SOLUTION:
* function applyStyles(elementId, styles) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* Object.entries(styles).forEach(([prop, value]) => {
* // Convert kebab-case to camelCase
* const camelProp = prop.replace(/-([a-z])/g, (_, char) =>
* char.toUpperCase()
* );
* element.style[camelProp] = value;
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 9: Reset Styles
// =============================================================
/**
* Remove specific inline styles from an element
*
* @param {string} elementId - The ID of the element
* @param {string[]} styleProperties - Array of CSS properties to remove
* @returns {boolean} - True if successful
*
* Example:
* resetStyles("box", ["backgroundColor", "padding", "margin"]);
*/
function resetStyles(elementId, styleProperties) {
// Your code here
}
// Test
// resetStyles("styled", ["color", "fontSize"]);
/*
* HINT: Use style.removeProperty() with kebab-case names
*
* SOLUTION:
* function resetStyles(elementId, styleProperties) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* styleProperties.forEach(prop => {
* // Convert camelCase to kebab-case
* const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
* element.style.removeProperty(kebabProp);
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 10: Read Data Attributes
// =============================================================
/**
* Read all data attributes from an element and return as object
* Also convert numeric strings to numbers
*
* @param {string} elementId - The ID of the element
* @returns {Object|null} - Object with all data attribute values
*
* Example HTML: <div id="config" data-limit="10" data-enabled="true" data-name="test">
* getDataAttributes("config") → { limit: 10, enabled: "true", name: "test" }
*/
function getDataAttributes(elementId) {
// Your code here
}
// Test
// console.log(getDataAttributes("productCard"));
/*
* HINT: Use element.dataset and convert numeric strings
*
* SOLUTION:
* function getDataAttributes(elementId) {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* const result = {};
*
* Object.entries(element.dataset).forEach(([key, value]) => {
* // Try to convert to number if it looks like one
* const num = Number(value);
* result[key] = !isNaN(num) && value !== "" ? num : value;
* });
*
* return result;
* }
*/
// =============================================================
// EXERCISE 11: Set Data Attributes
// =============================================================
/**
* Set multiple data attributes on an element
* Handle objects by JSON stringifying them
*
* @param {string} elementId - The ID of the element
* @param {Object} dataObj - Object with data attribute name-value pairs
* @returns {boolean} - True if successful
*
* Example:
* setDataAttributes("item", {
* id: 123,
* config: { theme: "dark", size: "large" }
* });
* → data-id="123", data-config='{"theme":"dark","size":"large"}'
*/
function setDataAttributes(elementId, dataObj) {
// Your code here
}
// Test
// setDataAttributes("widget", { id: 1, settings: { enabled: true } });
/*
* HINT: Use typeof to check for objects and JSON.stringify for them
*
* SOLUTION:
* function setDataAttributes(elementId, dataObj) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* Object.entries(dataObj).forEach(([key, value]) => {
* if (typeof value === "object") {
* element.dataset[key] = JSON.stringify(value);
* } else {
* element.dataset[key] = value;
* }
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 12: Create Styled Element
// =============================================================
/**
* Create a styled element with specified tag, content, classes, and styles
*
* @param {Object} config - Configuration object
* @param {string} config.tag - HTML tag name
* @param {string} config.content - Text content
* @param {string[]} config.classes - Array of class names
* @param {Object} config.styles - Style properties
* @param {Object} config.attributes - Other attributes
* @returns {Element} - The created element
*
* Example:
* createStyledElement({
* tag: "button",
* content: "Click Me",
* classes: ["btn", "btn-primary"],
* styles: { padding: "10px 20px" },
* attributes: { type: "button", disabled: "" }
* });
*/
function createStyledElement(config) {
// Your code here
}
// Test
// const btn = createStyledElement({ tag: "span", content: "Badge", classes: ["badge"] });
/*
* HINT: Use document.createElement, then apply all configurations
*
* SOLUTION:
* function createStyledElement(config) {
* const { tag, content, classes = [], styles = {}, attributes = {} } = config;
*
* const element = document.createElement(tag);
*
* if (content) {
* element.textContent = content;
* }
*
* if (classes.length) {
* element.classList.add(...classes);
* }
*
* Object.assign(element.style, styles);
*
* Object.entries(attributes).forEach(([name, value]) => {
* element.setAttribute(name, value);
* });
*
* return element;
* }
*/
// =============================================================
// EXERCISE 13: Build Card Component
// =============================================================
/**
* Build a card component with header, body, and footer
*
* @param {Object} cardData - Card data
* @param {string} cardData.title - Card title
* @param {string} cardData.content - Card body content
* @param {string} cardData.footer - Card footer text
* @param {string} cardData.theme - Theme class (optional)
* @returns {Element} - The card element
*
* Expected structure:
* <div class="card [theme]">
* <div class="card-header">[title]</div>
* <div class="card-body">[content]</div>
* <div class="card-footer">[footer]</div>
* </div>
*/
function buildCard(cardData) {
// Your code here
}
// Test
// const card = buildCard({ title: "Hello", content: "World", footer: "2024" });
/*
* HINT: Create container and children, use innerHTML or create elements
*
* SOLUTION:
* function buildCard(cardData) {
* const { title, content, footer, theme } = cardData;
*
* const card = document.createElement("div");
* card.className = theme ? `card ${theme}` : "card";
*
* if (title) {
* const header = document.createElement("div");
* header.className = "card-header";
* header.textContent = title;
* card.appendChild(header);
* }
*
* if (content) {
* const body = document.createElement("div");
* body.className = "card-body";
* body.textContent = content;
* card.appendChild(body);
* }
*
* if (footer) {
* const foot = document.createElement("div");
* foot.className = "card-footer";
* foot.textContent = footer;
* card.appendChild(foot);
* }
*
* return card;
* }
*/
// =============================================================
// EXERCISE 14: Style Highlighter
// =============================================================
/**
* Highlight an element temporarily by adding styles/classes
* Then remove the highlight after a delay
*
* @param {string} elementId - The ID of the element
* @param {Object} options - Highlight options
* @param {string} options.color - Background color for highlight
* @param {number} options.duration - Duration in ms (default: 2000)
* @returns {Promise<void>} - Resolves when highlight is removed
*
* Example:
* await highlightElement("result", { color: "yellow", duration: 1500 });
*/
function highlightElement(elementId, options = {}) {
// Your code here
}
// Test
// highlightElement("message", { color: "#ffeb3b", duration: 2000 });
/*
* HINT: Store original style, apply highlight, use setTimeout to restore
*
* SOLUTION:
* function highlightElement(elementId, options = {}) {
* return new Promise((resolve) => {
* const element = document.getElementById(elementId);
* if (!element) {
* resolve();
* return;
* }
*
* const { color = "yellow", duration = 2000 } = options;
*
* // Store original background
* const originalBg = element.style.backgroundColor;
* const originalTransition = element.style.transition;
*
* // Apply highlight
* element.style.transition = "background-color 0.3s";
* element.style.backgroundColor = color;
*
* // Remove highlight after duration
* setTimeout(() => {
* element.style.backgroundColor = originalBg;
*
* // Cleanup transition after animation
* setTimeout(() => {
* element.style.transition = originalTransition;
* resolve();
* }, 300);
* }, duration);
* });
* }
*/
// =============================================================
// EXERCISE 15: Theme Switcher
// =============================================================
/**
* Create a theme switcher that:
* - Toggles between light and dark themes
* - Updates CSS variables
* - Saves preference to localStorage
* - Applies to document root
*
* @param {Object} themes - Theme definitions
* @param {Object} themes.light - Light theme CSS variables
* @param {Object} themes.dark - Dark theme CSS variables
* @returns {Object} - Switcher API { toggle, setTheme, getTheme }
*
* Example:
* const switcher = createThemeSwitcher({
* light: { "--bg-color": "#fff", "--text-color": "#333" },
* dark: { "--bg-color": "#333", "--text-color": "#fff" }
* });
* switcher.toggle();
*/
function createThemeSwitcher(themes) {
// Your code here
}
// Test
// const switcher = createThemeSwitcher({
// light: { "--bg": "white" },
// dark: { "--bg": "black" }
// });
// switcher.toggle();
/*
* HINT: Use document.documentElement.style.setProperty for CSS variables
*
* SOLUTION:
* function createThemeSwitcher(themes) {
* const root = document.documentElement;
* let currentTheme = localStorage.getItem("theme") || "light";
*
* function applyTheme(themeName) {
* const theme = themes[themeName];
* if (!theme) return;
*
* Object.entries(theme).forEach(([prop, value]) => {
* root.style.setProperty(prop, value);
* });
*
* root.dataset.theme = themeName;
* localStorage.setItem("theme", themeName);
* currentTheme = themeName;
* }
*
* // Apply initial theme
* applyTheme(currentTheme);
*
* return {
* toggle() {
* const newTheme = currentTheme === "light" ? "dark" : "light";
* applyTheme(newTheme);
* return newTheme;
* },
*
* setTheme(themeName) {
* applyTheme(themeName);
* return themeName;
* },
*
* getTheme() {
* return currentTheme;
* }
* };
* }
*/
// =============================================================
// BONUS CHALLENGE: DOM State Manager
// =============================================================
/**
* Create a state manager that syncs JavaScript state with DOM
*
* Features:
* - Store state values
* - Automatically update DOM elements when state changes
* - Support text, attribute, class, and style bindings
*
* @param {Object} initialState - Initial state values
* @param {Object} bindings - DOM bindings { stateProp: { type, selector, ... } }
* @returns {Object} - State manager API
*
* Binding types:
* - text: { type: "text", selector: "#el" }
* - attribute: { type: "attribute", selector: "#el", attribute: "href" }
* - class: { type: "class", selector: "#el", className: "active" }
* - style: { type: "style", selector: "#el", property: "display" }
*
* Example:
* const state = createDOMStateManager(
* { count: 0, visible: true },
* {
* count: { type: "text", selector: "#counter" },
* visible: { type: "class", selector: "#panel", className: "visible" }
* }
* );
* state.set("count", 5); // Updates #counter text to "5"
*/
function createDOMStateManager(initialState, bindings) {
// Your code here
}
/*
* SOLUTION:
* function createDOMStateManager(initialState, bindings) {
* const state = { ...initialState };
*
* function updateDOM(prop, value) {
* const binding = bindings[prop];
* if (!binding) return;
*
* const element = document.querySelector(binding.selector);
* if (!element) return;
*
* switch (binding.type) {
* case "text":
* element.textContent = value;
* break;
* case "attribute":
* element.setAttribute(binding.attribute, value);
* break;
* case "class":
* element.classList.toggle(binding.className, !!value);
* break;
* case "style":
* element.style[binding.property] = value;
* break;
* }
* }
*
* // Apply initial state
* Object.keys(state).forEach(prop => {
* updateDOM(prop, state[prop]);
* });
*
* return {
* get(prop) {
* return state[prop];
* },
*
* set(prop, value) {
* state[prop] = value;
* updateDOM(prop, value);
* return value;
* },
*
* getAll() {
* return { ...state };
* },
*
* setAll(newState) {
* Object.entries(newState).forEach(([prop, value]) => {
* state[prop] = value;
* updateDOM(prop, value);
* });
* }
* };
* }
*/
// =============================================================
// Test HTML Template
// =============================================================
/*
Create an HTML file with this structure to test the exercises:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM Modification Exercises</title>
<style>
.active { background-color: #3498db; color: white; }
.highlight { border: 2px solid yellow; }
.hidden { display: none; }
.visible { display: block; }
.card { border: 1px solid #ddd; border-radius: 8px; margin: 10px; }
.card-header { background: #f5f5f5; padding: 10px; font-weight: bold; }
.card-body { padding: 15px; }
.card-footer { background: #f5f5f5; padding: 10px; font-size: 0.9em; }
</style>
</head>
<body>
<p id="greeting">Hello World</p>
<div id="container"></div>
<button id="btn">Test Button</button>
<img id="testImg" src="placeholder.jpg" alt="Placeholder">
<div id="box" class="box"></div>
<ul id="menu">
<li class="active">Home</li>
<li>About</li>
<li>Contact</li>
</ul>
<div id="testBox" style="width: 100px; height: 100px; background: gray;"></div>
<div id="productCard" data-id="123" data-price="99.99" data-category="electronics"></div>
<div id="widget"></div>
<div id="message">Test message</div>
<div id="cards"></div>
<script src="exercises.js"></script>
</body>
</html>
*/
// =============================================================
// Summary
// =============================================================
console.log('='.repeat(60));
console.log('10.2 Modifying the DOM - Exercises Loaded');
console.log('='.repeat(60));
console.log('Exercises 1-3: Content modification (text, HTML, attributes)');
console.log('Exercises 4-5: Attribute manipulation');
console.log('Exercises 6-7: Class manipulation');
console.log('Exercises 8-9: Style manipulation');
console.log('Exercises 10-11: Data attributes');
console.log('Exercises 12-13: Component building');
console.log('Exercises 14-15: Practical utilities');
console.log('Bonus: DOM State Manager');
console.log('='.repeat(60));