javascript

exercises

exercises.js
/**
 * ============================================================
 * 10.1 DOM BASICS - EXERCISES
 * ============================================================
 *
 * These exercises are designed for browser environment.
 * Create an HTML file with the provided structure and include this script.
 *
 * Sample HTML structure for testing:
 * <!DOCTYPE html>
 * <html>
 * <head><title>DOM Exercises</title></head>
 * <body>
 *   <div id="container">
 *     <header id="header" class="main-header">
 *       <h1>Welcome</h1>
 *       <nav id="nav" class="navigation">
 *         <ul id="nav-list">
 *           <li class="nav-item active"><a href="#home">Home</a></li>
 *           <li class="nav-item"><a href="#about">About</a></li>
 *           <li class="nav-item"><a href="#contact">Contact</a></li>
 *         </ul>
 *       </nav>
 *     </header>
 *     <main id="main">
 *       <article class="post" data-id="1" data-category="tech">
 *         <h2>First Post</h2>
 *         <p class="content">Hello World</p>
 *       </article>
 *       <article class="post" data-id="2" data-category="news">
 *         <h2>Second Post</h2>
 *         <p class="content">Another post</p>
 *       </article>
 *     </main>
 *     <footer id="footer">
 *       <p>&copy; 2024</p>
 *     </footer>
 *   </div>
 *   <script src="exercises.js"></script>
 * </body>
 * </html>
 */

/**
 * EXERCISE 1: Select by ID
 *
 * Create a function 'getHeader' that:
 * - Returns the element with id "header"
 */

// TODO: Implement getHeader
function getHeader() {
  // Your code here
}

// Test (in browser):
// console.log('Exercise 1:', getHeader());

/*
 * SOLUTION 1:
 *
 * function getHeader() {
 *     return document.getElementById('header');
 * }
 */

/**
 * EXERCISE 2: Select by Class
 *
 * Create a function 'getNavItems' that:
 * - Returns an array of all elements with class "nav-item"
 */

// TODO: Implement getNavItems
function getNavItems() {
  // Your code here
}

// Test:
// console.log('Exercise 2:', getNavItems());

/*
 * SOLUTION 2:
 *
 * function getNavItems() {
 *     return Array.from(document.querySelectorAll('.nav-item'));
 * }
 */

/**
 * EXERCISE 3: Select with Complex Selector
 *
 * Create a function 'getPostTitles' that:
 * - Returns an array of all h2 elements inside .post articles
 * - Returns the text content of each title
 */

// TODO: Implement getPostTitles
function getPostTitles() {
  // Your code here
}

// Test:
// console.log('Exercise 3:', getPostTitles());  // ["First Post", "Second Post"]

/*
 * SOLUTION 3:
 *
 * function getPostTitles() {
 *     const titles = document.querySelectorAll('.post h2');
 *     return Array.from(titles).map(h2 => h2.textContent);
 * }
 */

/**
 * EXERCISE 4: Scoped Query
 *
 * Create a function 'getLinksInNav' that:
 * - Takes a nav element as parameter
 * - Returns all anchor tags within that nav
 */

// TODO: Implement getLinksInNav
function getLinksInNav(nav) {
  // Your code here
}

// Test:
// const nav = document.getElementById('nav');
// console.log('Exercise 4:', getLinksInNav(nav));

/*
 * SOLUTION 4:
 *
 * function getLinksInNav(nav) {
 *     return Array.from(nav.querySelectorAll('a'));
 * }
 */

/**
 * EXERCISE 5: Parent Traversal
 *
 * Create a function 'getPostContainer' that:
 * - Takes any element inside a .post
 * - Returns the closest .post ancestor
 */

// TODO: Implement getPostContainer
function getPostContainer(element) {
  // Your code here
}

// Test:
// const paragraph = document.querySelector('.content');
// console.log('Exercise 5:', getPostContainer(paragraph));

/*
 * SOLUTION 5:
 *
 * function getPostContainer(element) {
 *     return element.closest('.post');
 * }
 */

/**
 * EXERCISE 6: Child Navigation
 *
 * Create a function 'getFirstAndLastNavItem' that:
 * - Returns object { first, last } with first and last nav items
 */

// TODO: Implement getFirstAndLastNavItem
function getFirstAndLastNavItem() {
  // Your code here
}

// Test:
// console.log('Exercise 6:', getFirstAndLastNavItem());

/*
 * SOLUTION 6:
 *
 * function getFirstAndLastNavItem() {
 *     const navList = document.getElementById('nav-list');
 *     return {
 *         first: navList.firstElementChild,
 *         last: navList.lastElementChild
 *     };
 * }
 */

/**
 * EXERCISE 7: Sibling Navigation
 *
 * Create a function 'getSiblings' that:
 * - Takes an element
 * - Returns an array of all its sibling elements (not including itself)
 */

// TODO: Implement getSiblings
function getSiblings(element) {
  // Your code here
}

// Test:
// const middle = document.querySelectorAll('.nav-item')[1];
// console.log('Exercise 7:', getSiblings(middle));

/*
 * SOLUTION 7:
 *
 * function getSiblings(element) {
 *     return Array.from(element.parentElement.children)
 *         .filter(child => child !== element);
 * }
 */

/**
 * EXERCISE 8: Get Text Content
 *
 * Create a function 'getAllText' that:
 * - Takes an element
 * - Returns all text content (recursively) as a string
 */

// TODO: Implement getAllText
function getAllText(element) {
  // Your code here
}

// Test:
// console.log('Exercise 8:', getAllText(document.getElementById('header')));

/*
 * SOLUTION 8:
 *
 * function getAllText(element) {
 *     return element.textContent.trim();
 * }
 */

/**
 * EXERCISE 9: Class Manipulation
 *
 * Create a function 'toggleActiveClass' that:
 * - Takes an element
 * - Toggles the 'active' class on it
 * - Returns true if class was added, false if removed
 */

// TODO: Implement toggleActiveClass
function toggleActiveClass(element) {
  // Your code here
}

// Test:
// const item = document.querySelector('.nav-item');
// console.log('Exercise 9:', toggleActiveClass(item));

/*
 * SOLUTION 9:
 *
 * function toggleActiveClass(element) {
 *     const wasActive = element.classList.contains('active');
 *     element.classList.toggle('active');
 *     return !wasActive;
 * }
 */

/**
 * EXERCISE 10: Multiple Class Operations
 *
 * Create a function 'setClasses' that:
 * - Takes an element and object { add: [...], remove: [...] }
 * - Adds and removes the specified classes
 */

// TODO: Implement setClasses
function setClasses(element, { add = [], remove = [] }) {
  // Your code here
}

// Test:
// const post = document.querySelector('.post');
// setClasses(post, { add: ['featured', 'highlighted'], remove: ['draft'] });
// console.log('Exercise 10:', post.classList);

/*
 * SOLUTION 10:
 *
 * function setClasses(element, { add = [], remove = [] }) {
 *     if (add.length) element.classList.add(...add);
 *     if (remove.length) element.classList.remove(...remove);
 * }
 */

/**
 * EXERCISE 11: Get Data Attributes
 *
 * Create a function 'getPostData' that:
 * - Takes a post element
 * - Returns object with all data attributes { id, category, ... }
 */

// TODO: Implement getPostData
function getPostData(post) {
  // Your code here
}

// Test:
// const post = document.querySelector('.post');
// console.log('Exercise 11:', getPostData(post));  // { id: "1", category: "tech" }

/*
 * SOLUTION 11:
 *
 * function getPostData(post) {
 *     return { ...post.dataset };
 * }
 */

/**
 * EXERCISE 12: Find by Attribute
 *
 * Create a function 'findPostsByCategory' that:
 * - Takes a category string
 * - Returns all posts with that data-category value
 */

// TODO: Implement findPostsByCategory
function findPostsByCategory(category) {
  // Your code here
}

// Test:
// console.log('Exercise 12:', findPostsByCategory('tech'));

/*
 * SOLUTION 12:
 *
 * function findPostsByCategory(category) {
 *     return Array.from(
 *         document.querySelectorAll(`[data-category="${category}"]`)
 *     );
 * }
 */

/**
 * EXERCISE 13: Check Element State
 *
 * Create a function 'isActiveNavItem' that:
 * - Takes a nav item element
 * - Returns true if it has the 'active' class
 */

// TODO: Implement isActiveNavItem
function isActiveNavItem(item) {
  // Your code here
}

// Test:
// const items = document.querySelectorAll('.nav-item');
// console.log('Exercise 13:', Array.from(items).map(isActiveNavItem));

/*
 * SOLUTION 13:
 *
 * function isActiveNavItem(item) {
 *     return item.classList.contains('active');
 * }
 */

/**
 * EXERCISE 14: Count Elements
 *
 * Create a function 'countElements' that:
 * - Takes a selector
 * - Returns the count of matching elements
 */

// TODO: Implement countElements
function countElements(selector) {
  // Your code here
}

// Test:
// console.log('Exercise 14:', countElements('.nav-item'));  // 3
// console.log('Exercise 14:', countElements('.post'));      // 2

/*
 * SOLUTION 14:
 *
 * function countElements(selector) {
 *     return document.querySelectorAll(selector).length;
 * }
 */

/**
 * EXERCISE 15: DOM Tree Walker
 *
 * Create a function 'getElementPath' that:
 * - Takes an element
 * - Returns the path from document to that element
 * - Path format: "html > body > div#container > main#main > article"
 */

// TODO: Implement getElementPath
function getElementPath(element) {
  // Your code here
}

// Test:
// const post = document.querySelector('.post');
// console.log('Exercise 15:', getElementPath(post));

/*
 * SOLUTION 15:
 *
 * function getElementPath(element) {
 *     const path = [];
 *     let current = element;
 *
 *     while (current && current !== document) {
 *         let selector = current.tagName.toLowerCase();
 *         if (current.id) {
 *             selector += `#${current.id}`;
 *         }
 *         path.unshift(selector);
 *         current = current.parentElement;
 *     }
 *
 *     return path.join(' > ');
 * }
 */

// ============================================================
// ADVANCED EXERCISES
// ============================================================

/**
 * EXERCISE 16: Query Builder
 *
 * Create a class 'DOMQuery' that:
 * - Constructor takes a selector or element
 * - Has method 'find(selector)' returning new DOMQuery scoped to children
 * - Has method 'first()' returning first element
 * - Has method 'last()' returning last element
 * - Has method 'each(callback)' to iterate elements
 */

// TODO: Implement DOMQuery
class DOMQuery {
  // Your code here
}

// Test:
// const q = new DOMQuery('#nav-list');
// q.find('.nav-item').each((item, i) => console.log(i, item.textContent));

/*
 * SOLUTION 16:
 *
 * class DOMQuery {
 *     constructor(selectorOrElements) {
 *         if (typeof selectorOrElements === 'string') {
 *             this.elements = Array.from(document.querySelectorAll(selectorOrElements));
 *         } else if (selectorOrElements instanceof Element) {
 *             this.elements = [selectorOrElements];
 *         } else if (Array.isArray(selectorOrElements)) {
 *             this.elements = selectorOrElements;
 *         } else {
 *             this.elements = [];
 *         }
 *     }
 *
 *     find(selector) {
 *         const found = this.elements.flatMap(el =>
 *             Array.from(el.querySelectorAll(selector))
 *         );
 *         return new DOMQuery(found);
 *     }
 *
 *     first() {
 *         return this.elements[0] || null;
 *     }
 *
 *     last() {
 *         return this.elements[this.elements.length - 1] || null;
 *     }
 *
 *     each(callback) {
 *         this.elements.forEach(callback);
 *         return this;
 *     }
 * }
 */

/**
 * EXERCISE 17: Element Matcher
 *
 * Create a function 'matchElements' that:
 * - Takes an array of elements and a filter object
 * - Filter can have: { hasClass, hasAttribute, matchesSelector }
 * - Returns elements matching all conditions
 */

// TODO: Implement matchElements
function matchElements(elements, filter) {
  // Your code here
}

// Test:
// const items = Array.from(document.querySelectorAll('.nav-item'));
// console.log('Exercise 17:', matchElements(items, {
//     hasClass: 'active',
//     matchesSelector: 'li'
// }));

/*
 * SOLUTION 17:
 *
 * function matchElements(elements, filter) {
 *     return elements.filter(el => {
 *         if (filter.hasClass && !el.classList.contains(filter.hasClass)) {
 *             return false;
 *         }
 *         if (filter.hasAttribute && !el.hasAttribute(filter.hasAttribute)) {
 *             return false;
 *         }
 *         if (filter.matchesSelector && !el.matches(filter.matchesSelector)) {
 *             return false;
 *         }
 *         return true;
 *     });
 * }
 */

/**
 * EXERCISE 18: DOM Diff
 *
 * Create a function 'compareElements' that:
 * - Takes two elements
 * - Returns object describing differences in tagName, classes, attributes
 */

// TODO: Implement compareElements
function compareElements(el1, el2) {
  // Your code here
}

// Test:
// const posts = document.querySelectorAll('.post');
// console.log('Exercise 18:', compareElements(posts[0], posts[1]));

/*
 * SOLUTION 18:
 *
 * function compareElements(el1, el2) {
 *     const diff = {
 *         sameTag: el1.tagName === el2.tagName,
 *         classes: {
 *             onlyInFirst: [...el1.classList].filter(c => !el2.classList.contains(c)),
 *             onlyInSecond: [...el2.classList].filter(c => !el1.classList.contains(c)),
 *             shared: [...el1.classList].filter(c => el2.classList.contains(c))
 *         },
 *         attributes: {
 *             different: []
 *         }
 *     };
 *
 *     // Compare data attributes
 *     const keys1 = Object.keys(el1.dataset);
 *     const keys2 = Object.keys(el2.dataset);
 *     const allKeys = new Set([...keys1, ...keys2]);
 *
 *     allKeys.forEach(key => {
 *         if (el1.dataset[key] !== el2.dataset[key]) {
 *             diff.attributes.different.push({
 *                 key: `data-${key}`,
 *                 first: el1.dataset[key],
 *                 second: el2.dataset[key]
 *             });
 *         }
 *     });
 *
 *     return diff;
 * }
 */

/**
 * EXERCISE 19: Live Collection Handler
 *
 * Create a function 'observeCollection' that:
 * - Takes a live HTMLCollection and a callback
 * - Calls callback when collection length changes
 * - Uses polling to check for changes
 * - Returns a stop function
 */

// TODO: Implement observeCollection
function observeCollection(collection, callback, interval = 100) {
  // Your code here
}

// Test:
// const buttons = document.getElementsByClassName('btn');
// const stop = observeCollection(buttons, (oldLen, newLen) => {
//     console.log(`Collection changed: ${oldLen} -> ${newLen}`);
// });
// // Later: stop();

/*
 * SOLUTION 19:
 *
 * function observeCollection(collection, callback, interval = 100) {
 *     let lastLength = collection.length;
 *
 *     const intervalId = setInterval(() => {
 *         const currentLength = collection.length;
 *         if (currentLength !== lastLength) {
 *             callback(lastLength, currentLength);
 *             lastLength = currentLength;
 *         }
 *     }, interval);
 *
 *     return function stop() {
 *         clearInterval(intervalId);
 *     };
 * }
 */

/**
 * EXERCISE 20: Selector Performance Tester
 *
 * Create a function 'benchmarkSelectors' that:
 * - Takes an array of selectors
 * - Measures time to execute each 1000 times
 * - Returns results sorted by speed
 */

// TODO: Implement benchmarkSelectors
function benchmarkSelectors(selectors) {
  // Your code here
}

// Test:
// console.log('Exercise 20:', benchmarkSelectors([
//     '#header',
//     '.nav-item',
//     'nav ul li',
//     '[data-category]'
// ]));

/*
 * SOLUTION 20:
 *
 * function benchmarkSelectors(selectors) {
 *     const results = selectors.map(selector => {
 *         const start = performance.now();
 *         for (let i = 0; i < 1000; i++) {
 *             document.querySelectorAll(selector);
 *         }
 *         const end = performance.now();
 *         return {
 *             selector,
 *             time: (end - start).toFixed(3) + 'ms',
 *             count: document.querySelectorAll(selector).length
 *         };
 *     });
 *
 *     return results.sort((a, b) =>
 *         parseFloat(a.time) - parseFloat(b.time)
 *     );
 * }
 */

// ============================================================
// RUN TESTS
// ============================================================

console.log('DOM Basics Exercises loaded.');
console.log(
  'Create the HTML structure and uncomment tests to verify solutions.'
);
console.log('Run in browser environment for full functionality.');
Exercises - JavaScript Tutorial | DeepML