Web APIs and Browser Integration

1. Fetch API and HTTP Request Handling

Fetch API Basics

Method Syntax Returns
fetch() fetch(url, options?) Promise<Response>
response.json() response.json() Promise<any>
response.text() response.text() Promise<string>
response.blob() response.blob() Promise<Blob>
response.arrayBuffer() response.arrayBuffer() Promise<ArrayBuffer>
response.formData() response.formData() Promise<FormData>

Response Properties

Property Type Description
response.ok boolean True if status 200-299
response.status number HTTP status code (200, 404, etc.)
response.statusText string Status message ("OK", "Not Found")
response.headers Headers Response headers
response.url string Final URL (after redirects)
response.redirected boolean True if response redirected
response.type string "basic", "cors", "opaque", etc.

Request Options

Option Type Description
method string "GET", "POST", "PUT", "DELETE", etc.
headers object Request headers
body string|FormData|Blob Request body (not for GET/HEAD)
mode string "cors", "no-cors", "same-origin"
credentials string "omit", "same-origin", "include"
cache string "default", "no-cache", "reload", etc.
signal AbortSignal For request cancellation

HTTP Status Code Categories

Range Category Common Codes
100-199 Informational 100 Continue, 101 Switching Protocols
200-299 Success 200 OK, 201 Created, 204 No Content
300-399 Redirection 301 Moved, 302 Found, 304 Not Modified
400-499 Client Error 400 Bad Request, 401 Unauthorized, 404 Not Found
500-599 Server Error 500 Internal Error, 503 Service Unavailable

Example: Basic fetch requests

// GET request
fetch('https://api.example.com/users')
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error('Fetch error:', error);
    });

// Async/await version
async function getUsers() {
    try {
        const response = await fetch('https://api.example.com/users');
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Error fetching users:', error);
        throw error;
    }
}

// POST request with JSON
async function createUser(userData) {
    const response = await fetch('https://api.example.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer token123'
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message);
    }
    
    return response.json();
}

// Usage
await createUser({
    name: 'John Doe',
    email: 'john@example.com'
});

// PUT request
async function updateUser(id, updates) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(updates)
    });
    
    return response.json();
}

// DELETE request
async function deleteUser(id) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'DELETE'
    });
    
    if (response.status === 204) {
        return {success: true};
    }
    
    return response.json();
}

// Upload file with FormData
async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('description', 'My file');
    
    const response = await fetch('https://api.example.com/upload', {
        method: 'POST',
        body: formData  // Don't set Content-Type header - browser sets it
    });
    
    return response.json();
}

// Request with timeout
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            ...options,
            signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        clearTimeout(timeoutId);
        
        if (error.name === 'AbortError') {
            throw new Error('Request timeout');
        }
        
        throw error;
    }
}

Example: Advanced fetch patterns

// Retry logic
async function fetchWithRetry(url, options = {}, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url, options);
            
            if (response.ok) {
                return response;
            }
            
            // Don't retry client errors (4xx)
            if (response.status >= 400 && response.status < 500) {
                throw new Error(`Client error: ${response.status}`);
            }
            
            // Retry on server errors (5xx)
            if (i === retries - 1) {
                throw new Error(`Max retries reached. Status: ${response.status}`);
            }
            
            // Wait before retry (exponential backoff)
            await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        } catch (error) {
            if (i === retries - 1) {
                throw error;
            }
            
            // Wait before retry
            await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        }
    }
}

// Parallel requests
async function fetchMultiple(urls) {
    const promises = urls.map(url => fetch(url).then(r => r.json()));
    return Promise.all(promises);
}

// Usage
const [users, posts, comments] = await fetchMultiple([
    'https://api.example.com/users',
    'https://api.example.com/posts',
    'https://api.example.com/comments'
]);

// Sequential requests (when one depends on another)
async function fetchUserAndPosts(userId) {
    const user = await fetch(`/api/users/${userId}`).then(r => r.json());
    const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
    
    return {user, posts};
}

// Progress tracking for upload
async function uploadWithProgress(file, onProgress) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable) {
                const percentComplete = (e.loaded / e.total) * 100;
                onProgress(percentComplete);
            }
        });
        
        xhr.addEventListener('load', () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error(`Upload failed: ${xhr.status}`));
            }
        });
        
        xhr.addEventListener('error', () => reject(new Error('Upload error')));
        
        const formData = new FormData();
        formData.append('file', file);
        
        xhr.open('POST', '/api/upload');
        xhr.send(formData);
    });
}

// Usage
await uploadWithProgress(file, (percent) => {
    console.log(`Upload progress: ${percent.toFixed(2)}%`);
});

// API client class
class ApiClient {
    constructor(baseURL, defaultHeaders = {}) {
        this.baseURL = baseURL;
        this.defaultHeaders = defaultHeaders;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        
        const config = {
            ...options,
            headers: {
                ...this.defaultHeaders,
                ...options.headers
            }
        };
        
        const response = await fetch(url, config);
        
        if (!response.ok) {
            const error = await response.json().catch(() => ({}));
            throw new Error(error.message || `HTTP error! status: ${response.status}`);
        }
        
        // Handle no-content responses
        if (response.status === 204) {
            return null;
        }
        
        return response.json();
    }
    
    get(endpoint, options) {
        return this.request(endpoint, {...options, method: 'GET'});
    }
    
    post(endpoint, data, options) {
        return this.request(endpoint, {
            ...options,
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
    }
    
    put(endpoint, data, options) {
        return this.request(endpoint, {
            ...options,
            method: 'PUT',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
    }
    
    delete(endpoint, options) {
        return this.request(endpoint, {...options, method: 'DELETE'});
    }
}

// Usage
const api = new ApiClient('https://api.example.com', {
    'Authorization': 'Bearer token123'
});

const users = await api.get('/users');
const newUser = await api.post('/users', {name: 'John'});
await api.delete(`/users/${newUser.id}`);
Key Points: fetch() returns Promise<Response>. Check response.ok for success (status 200-299). Use response.json() to parse JSON. AbortController for request cancellation. FormData for file uploads. Set headers for authentication and content type. Handle errors with try-catch. Always check response.ok before parsing body.

2. DOM Manipulation and Element Selection

Element Selection Methods

Method Returns Description
document.getElementById() Element | null Select by ID
document.querySelector() Element | null First match of CSS selector
document.querySelectorAll() NodeList All matches of CSS selector
document.getElementsByClassName() HTMLCollection (live) Elements by class name
document.getElementsByTagName() HTMLCollection (live) Elements by tag name
element.closest() Element | null Nearest ancestor matching selector
element.matches() boolean Check if element matches selector

Element Creation and Modification

Method Purpose Usage
document.createElement() Create element createElement('div')
document.createTextNode() Create text node createTextNode('text')
element.cloneNode() Clone element cloneNode(true) for deep clone
element.appendChild() Add child at end parent.appendChild(child)
element.insertBefore() Insert before reference parent.insertBefore(new, ref)
element.removeChild() Remove child parent.removeChild(child)
element.remove() Remove self element.remove()
element.replaceChild() Replace child parent.replaceChild(new, old)

Modern DOM Insertion Methods

Method Position Accepts
element.append() Inside, at end Nodes or strings
element.prepend() Inside, at start Nodes or strings
element.before() Before element (sibling) Nodes or strings
element.after() After element (sibling) Nodes or strings
element.replaceWith() Replace element Nodes or strings

Element Properties and Attributes

Property/Method Purpose Example
element.innerHTML Get/set HTML content div.innerHTML = '<p>text</p>'
element.textContent Get/set text (no HTML parsing) div.textContent = 'text'
element.innerText Get/set rendered text div.innerText = 'text'
element.getAttribute() Get attribute value img.getAttribute('src')
element.setAttribute() Set attribute img.setAttribute('src', 'url')
element.removeAttribute() Remove attribute div.removeAttribute('class')
element.hasAttribute() Check if attribute exists div.hasAttribute('data-id')
element.dataset Access data-* attributes div.dataset.userId

CSS Class Manipulation

Method Purpose Example
element.classList.add() Add class(es) div.classList.add('active', 'highlighted')
element.classList.remove() Remove class(es) div.classList.remove('active')
element.classList.toggle() Toggle class div.classList.toggle('hidden')
element.classList.contains() Check if has class div.classList.contains('active')
element.classList.replace() Replace class div.classList.replace('old', 'new')
element.className Get/set all classes (string) div.className = 'class1 class2'

Example: DOM selection and manipulation

// Element selection
const header = document.getElementById('header');
const firstButton = document.querySelector('.btn');
const allButtons = document.querySelectorAll('.btn');
const items = document.getElementsByClassName('item');

// Modern selection with optional chaining
const content = document.querySelector('.container')?.querySelector('.content');

// Creating elements
const div = document.createElement('div');
div.className = 'card';
div.id = 'user-card';
div.textContent = 'User Card';

// Setting attributes
div.setAttribute('data-user-id', '123');
div.setAttribute('role', 'article');

// Using dataset for data attributes
div.dataset.userId = '123';  // Creates data-user-id
div.dataset.userName = 'John';  // Creates data-user-name

console.log(div.dataset.userId);  // '123'

// Creating complex structures
const card = document.createElement('div');
card.className = 'card';

const title = document.createElement('h3');
title.textContent = 'Card Title';

const content2 = document.createElement('p');
content2.textContent = 'Card content...';

const button = document.createElement('button');
button.textContent = 'Click Me';
button.className = 'btn btn-primary';

card.append(title, content2, button);
document.body.appendChild(card);

// Inserting elements
const container = document.querySelector('.container');

// Insert at end
container.append('Some text', div);

// Insert at start
container.prepend('First content');

// Insert before/after
const reference = document.querySelector('.reference');
reference.before(document.createElement('div'));
reference.after(document.createElement('div'));

// Replace element
const oldElement = document.querySelector('.old');
const newElement = document.createElement('div');
newElement.textContent = 'New element';
oldElement.replaceWith(newElement);

// Removing elements
const element = document.querySelector('.to-remove');
element.remove();  // Modern way

// Or
element.parentNode.removeChild(element);  // Old way

// Clone elements
const original = document.querySelector('.original');
const clone = original.cloneNode(true);  // Deep clone
document.body.appendChild(clone);

// Class manipulation
const box = document.querySelector('.box');

box.classList.add('active');
box.classList.remove('inactive');
box.classList.toggle('visible');

if (box.classList.contains('active')) {
    console.log('Box is active');
}

// Toggle with force parameter
box.classList.toggle('active', true);  // Always add
box.classList.toggle('active', false);  // Always remove

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

// Working with innerHTML vs textContent
const container2 = document.querySelector('.container');

// innerHTML - parses HTML
container2.innerHTML = '<strong>Bold text</strong>';

// textContent - treats as plain text (safer, faster)
container2.textContent = '<strong>Not parsed</strong>';

// Finding elements relative to another
const parent = document.querySelector('.parent');
const child = parent.querySelector('.child');
const ancestor = child.closest('.ancestor');
const sibling = child.nextElementSibling;
const prevSibling = child.previousElementSibling;

// Check if element matches selector
if (element.matches('.active')) {
    console.log('Element is active');
}

Example: Efficient DOM manipulation

// BAD: Multiple reflows
for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    div.textContent = `Item ${i}`;
    document.body.appendChild(div);  // Causes reflow each time
}

// GOOD: Use DocumentFragment
const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    div.textContent = `Item ${i}`;
    fragment.appendChild(div);
}

document.body.appendChild(fragment);  // Single reflow

// GOOD: Build HTML string and insert once
const items = Array.from({length: 100}, (_, i) => `
    <div class="item">Item ${i}</div>
`).join('');

document.querySelector('.container').innerHTML = items;

// Helper function to create elements with properties
function createElement(tag, props = {}, children = []) {
    const element = document.createElement(tag);
    
    // Set properties
    Object.entries(props).forEach(([key, value]) => {
        if (key === 'className') {
            element.className = value;
        } else if (key === 'dataset') {
            Object.assign(element.dataset, value);
        } else if (key === 'style') {
            Object.assign(element.style, value);
        } else if (key.startsWith('on')) {
            const event = key.slice(2).toLowerCase();
            element.addEventListener(event, value);
        } else {
            element.setAttribute(key, value);
        }
    });
    
    // Add children
    children.forEach(child => {
        if (typeof child === 'string') {
            element.appendChild(document.createTextNode(child));
        } else {
            element.appendChild(child);
        }
    });
    
    return element;
}

// Usage
const button = createElement('button', {
    className: 'btn btn-primary',
    dataset: {userId: '123'},
    onclick: () => console.log('Clicked'),
    type: 'button'
}, ['Click Me']);

// Batch DOM reads and writes
// BAD: Interleaving reads and writes
elements.forEach(el => {
    const height = el.offsetHeight;  // Read
    el.style.height = height + 10 + 'px';  // Write
});

// GOOD: Batch all reads, then all writes
const heights = elements.map(el => el.offsetHeight);  // All reads
elements.forEach((el, i) => {
    el.style.height = heights[i] + 10 + 'px';  // All writes
});

// Template element for reusable DOM structures
const template = document.createElement('template');
template.innerHTML = `
    <div class="card">
        <h3 class="card-title"></h3>
        <p class="card-body"></p>
        <button class="card-btn">Action</button>
    </div>
`;

function createCard(title, body) {
    const clone = template.content.cloneNode(true);
    clone.querySelector('.card-title').textContent = title;
    clone.querySelector('.card-body').textContent = body;
    return clone;
}

document.body.appendChild(createCard('Title', 'Body text'));

// Observing DOM changes
const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
        console.log('Type:', mutation.type);
        console.log('Added:', mutation.addedNodes);
        console.log('Removed:', mutation.removedNodes);
    });
});

observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeOldValue: true
});

// Stop observing
observer.disconnect();
Key Points: Use querySelector() for flexible selection with CSS selectors. querySelectorAll() returns static NodeList. classList for class manipulation. textContent is safer than innerHTML. Use DocumentFragment for batch inserts. Modern methods: append(), prepend(), before(), after(), remove(). Access data attributes via element.dataset.

3. Event System and Event Delegation

Event Registration Methods

Method Syntax Notes
addEventListener() element.addEventListener(type, handler, options) Recommended, multiple handlers allowed
removeEventListener() element.removeEventListener(type, handler, options) Remove specific handler
element.onclick element.onclick = handler Only one handler, overwrites previous
HTML attribute <button onclick="handler()"> Not recommended

Event Listener Options

Option Type Description
capture boolean Use capture phase (default: false)
once boolean Remove after first invocation
passive boolean Won't call preventDefault() (improves scroll performance)
signal AbortSignal Remove listener when signal aborted

Common Event Types

Category Events When Fired
Mouse click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave Mouse interactions
Keyboard keydown, keyup, keypress (deprecated) Keyboard input
Form submit, change, input, focus, blur, invalid Form interactions
Focus focus, blur, focusin, focusout Element focus changes
Touch touchstart, touchmove, touchend, touchcancel Touch device interactions
Drag dragstart, drag, dragend, dragover, drop Drag and drop
Document DOMContentLoaded, load, beforeunload, unload Document lifecycle
Clipboard copy, cut, paste Clipboard operations

Event Object Properties

Property Type Description
event.type string Event type ("click", "keydown", etc.)
event.target Element Element that triggered event
event.currentTarget Element Element with listener attached
event.bubbles boolean Whether event bubbles
event.cancelable boolean Whether can be cancelled
event.defaultPrevented boolean Whether preventDefault called
event.timeStamp number Time event created (ms)
event.isTrusted boolean True if user-generated

Event Control Methods

Method Purpose Effect
event.preventDefault() Cancel default action Prevents default browser behavior
event.stopPropagation() Stop bubbling Prevents event from propagating
event.stopImmediatePropagation() Stop all propagation Stops propagation and remaining handlers

Example: Event handling basics

// Basic event listener
const button = document.querySelector('.btn');

button.addEventListener('click', (event) => {
    console.log('Button clicked!');
    console.log('Target:', event.target);
    console.log('Current target:', event.currentTarget);
});

// Event listener with options
button.addEventListener('click', handler, {
    once: true,      // Remove after first call
    capture: false,  // Bubbling phase
    passive: true    // Won't call preventDefault
});

// Remove event listener
function handleClick(event) {
    console.log('Clicked');
}

button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);

// Using AbortController to remove listeners
const controller = new AbortController();

button.addEventListener('click', () => {
    console.log('Clicked');
}, {signal: controller.signal});

// Remove listener
controller.abort();

// Multiple elements
const buttons = document.querySelectorAll('.btn');

buttons.forEach(button => {
    button.addEventListener('click', (e) => {
        console.log('Button clicked:', e.target.textContent);
    });
});

// Prevent default behavior
const link = document.querySelector('a');

link.addEventListener('click', (event) => {
    event.preventDefault();  // Don't navigate
    console.log('Link clicked but not followed');
});

// Form submission
const form = document.querySelector('form');

form.addEventListener('submit', (event) => {
    event.preventDefault();  // Don't submit to server
    
    const formData = new FormData(form);
    const data = Object.fromEntries(formData);
    
    console.log('Form data:', data);
    
    // Handle form submission with fetch
    fetch('/api/submit', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(data)
    });
});

// Keyboard events
const input = document.querySelector('input');

input.addEventListener('keydown', (event) => {
    console.log('Key:', event.key);
    console.log('Code:', event.code);
    console.log('Ctrl:', event.ctrlKey);
    console.log('Shift:', event.shiftKey);
    console.log('Alt:', event.altKey);
    
    // Check for specific key
    if (event.key === 'Enter') {
        console.log('Enter pressed');
    }
    
    // Check for key combination
    if (event.ctrlKey && event.key === 's') {
        event.preventDefault();  // Prevent browser save
        console.log('Ctrl+S pressed');
    }
});

// Input event (fires on every change)
input.addEventListener('input', (event) => {
    console.log('Current value:', event.target.value);
});

// Change event (fires when input loses focus)
input.addEventListener('change', (event) => {
    console.log('Final value:', event.target.value);
});

// Mouse events
const box = document.querySelector('.box');

box.addEventListener('mouseenter', () => {
    console.log('Mouse entered');
});

box.addEventListener('mouseleave', () => {
    console.log('Mouse left');
});

box.addEventListener('mousemove', (event) => {
    console.log('Mouse position:', event.clientX, event.clientY);
});

// Touch events
box.addEventListener('touchstart', (event) => {
    console.log('Touch started');
    console.log('Touches:', event.touches.length);
});

box.addEventListener('touchmove', (event) => {
    event.preventDefault();  // Prevent scrolling
    const touch = event.touches[0];
    console.log('Touch position:', touch.clientX, touch.clientY);
}, {passive: false});  // Must be false to call preventDefault

// Document ready
document.addEventListener('DOMContentLoaded', () => {
    console.log('DOM fully loaded');
});

// Page load (including images, styles)
window.addEventListener('load', () => {
    console.log('Page fully loaded');
});

// Before page unload
window.addEventListener('beforeunload', (event) => {
    event.preventDefault();
    event.returnValue = '';  // Show confirmation dialog
});

Example: Event delegation

// Event delegation - handle events on parent
const list = document.querySelector('.list');

// BAD: Add listener to each item (inefficient for many items)
document.querySelectorAll('.list-item').forEach(item => {
    item.addEventListener('click', handleClick);
});

// GOOD: Single listener on parent (event delegation)
list.addEventListener('click', (event) => {
    // Find closest list item
    const item = event.target.closest('.list-item');
    
    if (item) {
        console.log('Item clicked:', item.textContent);
        item.classList.toggle('selected');
    }
});

// More complex delegation
list.addEventListener('click', (event) => {
    const target = event.target;
    
    // Handle delete button
    if (target.matches('.delete-btn')) {
        const item = target.closest('.list-item');
        item.remove();
    }
    
    // Handle edit button
    if (target.matches('.edit-btn')) {
        const item = target.closest('.list-item');
        editItem(item);
    }
    
    // Handle item click (but not buttons)
    if (target.matches('.list-item') && !target.closest('button')) {
        toggleItem(target);
    }
});

// Delegation helper function
function delegate(parent, selector, eventType, handler) {
    parent.addEventListener(eventType, (event) => {
        const target = event.target.closest(selector);
        
        if (target && parent.contains(target)) {
            handler.call(target, event);
        }
    });
}

// Usage
delegate(list, '.list-item', 'click', function(event) {
    console.log('Item clicked:', this.textContent);
});

// Dynamic content with delegation
function addItem(text) {
    const item = document.createElement('div');
    item.className = 'list-item';
    item.innerHTML = `
        <span>${text}</span>
        <button class="delete-btn">Delete</button>
    `;
    list.appendChild(item);
    // No need to add new event listeners!
}

// Event bubbling example
document.querySelector('.grandparent').addEventListener('click', () => {
    console.log('Grandparent clicked');
});

document.querySelector('.parent').addEventListener('click', () => {
    console.log('Parent clicked');
});

document.querySelector('.child').addEventListener('click', (event) => {
    console.log('Child clicked');
    
    // Stop propagation to parent
    // event.stopPropagation();
});

// Clicking child logs: "Child clicked", "Parent clicked", "Grandparent clicked"

// Event capture phase
document.querySelector('.grandparent').addEventListener('click', () => {
    console.log('Grandparent (capture)');
}, true);  // Capture phase

document.querySelector('.parent').addEventListener('click', () => {
    console.log('Parent (capture)');
}, true);

document.querySelector('.child').addEventListener('click', () => {
    console.log('Child (bubble)');
});

// Order: "Grandparent (capture)", "Parent (capture)", "Child (bubble)"

// Custom event dispatcher
class EventBus {
    constructor() {
        this.events = {};
    }
    
    on(event, handler) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(handler);
    }
    
    off(event, handler) {
        if (!this.events[event]) return;
        
        this.events[event] = this.events[event].filter(h => h !== handler);
    }
    
    emit(event, data) {
        if (!this.events[event]) return;
        
        this.events[event].forEach(handler => {
            handler(data);
        });
    }
}

// Usage
const bus = new EventBus();

bus.on('user:login', (user) => {
    console.log('User logged in:', user);
});

bus.emit('user:login', {name: 'John', id: 123});

// Custom DOM events
const customEvent = new CustomEvent('myevent', {
    detail: {message: 'Hello'},
    bubbles: true,
    cancelable: true
});

element.addEventListener('myevent', (event) => {
    console.log('Custom event:', event.detail);
});

element.dispatchEvent(customEvent);
Key Points: Use addEventListener() for flexibility. Event delegation: attach listener to parent, check event.target. Use event.preventDefault() to cancel default action. event.stopPropagation() stops bubbling. once: true for one-time listeners. AbortController for cleanup. passive: true for scroll performance. Event phases: capture → target → bubble.

4. Storage APIs (localStorage, sessionStorage, IndexedDB)

Web Storage API (localStorage & sessionStorage)

Method Purpose Returns
setItem(key, value) Store key-value pair undefined
getItem(key) Retrieve value string | null
removeItem(key) Remove item undefined
clear() Remove all items undefined
key(index) Get key at index string | null
length Number of items number

localStorage vs sessionStorage

Feature localStorage sessionStorage
Persistence Until manually cleared Until tab/window closed
Scope Shared across tabs Per tab/window
Storage limit ~5-10 MB ~5-10 MB
Data type Strings only Strings only
Use case Long-term data, preferences Session data, temporary state

IndexedDB Key Concepts

Concept Description Example
Database Container for object stores indexedDB.open('myDB', 1)
Object Store Like a table in SQL db.createObjectStore('users')
Transaction Atomic operation wrapper db.transaction(['users'], 'readwrite')
Index Query by non-key property store.createIndex('email', 'email')
Key Unique identifier autoIncrement or keyPath
Cursor Iterate over records store.openCursor()

IndexedDB Transaction Modes

Mode Access When to Use
readonly Read only Queries, retrieving data
readwrite Read and write Creating, updating, deleting
versionchange Schema changes onupgradeneeded event

Example: localStorage and sessionStorage

// localStorage - persists across sessions
// Store data
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');

// Retrieve data
const username = localStorage.getItem('username');  // 'john_doe'
const theme = localStorage.getItem('theme');        // 'dark'

// Store objects (must stringify)
const user = {
    id: 123,
    name: 'John Doe',
    email: 'john@example.com'
};

localStorage.setItem('user', JSON.stringify(user));

// Retrieve and parse
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.name);  // 'John Doe'

// Remove item
localStorage.removeItem('theme');

// Clear all
localStorage.clear();

// Check if key exists
if (localStorage.getItem('username')) {
    console.log('Username exists');
}

// Iterate over all items
for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    const value = localStorage.getItem(key);
    console.log(key, value);
}

// sessionStorage - cleared when tab closes
sessionStorage.setItem('tempData', 'temporary');

// Storage helper class
class Storage {
    constructor(storage = localStorage) {
        this.storage = storage;
    }
    
    set(key, value) {
        try {
            this.storage.setItem(key, JSON.stringify(value));
            return true;
        } catch (error) {
            console.error('Storage error:', error);
            return false;
        }
    }
    
    get(key, defaultValue = null) {
        try {
            const item = this.storage.getItem(key);
            return item ? JSON.parse(item) : defaultValue;
        } catch (error) {
            console.error('Parse error:', error);
            return defaultValue;
        }
    }
    
    remove(key) {
        this.storage.removeItem(key);
    }
    
    clear() {
        this.storage.clear();
    }
    
    has(key) {
        return this.storage.getItem(key) !== null;
    }
    
    keys() {
        return Object.keys(this.storage);
    }
}

// Usage
const store = new Storage();

store.set('user', {name: 'John', age: 30});
const user2 = store.get('user');
console.log(user2.name);  // 'John'

// Listen for storage changes (in other tabs)
window.addEventListener('storage', (event) => {
    console.log('Storage changed:');
    console.log('Key:', event.key);
    console.log('Old value:', event.oldValue);
    console.log('New value:', event.newValue);
    console.log('URL:', event.url);
});

// Storage with expiration
class StorageWithExpiry {
    set(key, value, ttl) {
        const item = {
            value: value,
            expiry: Date.now() + ttl
        };
        localStorage.setItem(key, JSON.stringify(item));
    }
    
    get(key) {
        const itemStr = localStorage.getItem(key);
        
        if (!itemStr) {
            return null;
        }
        
        const item = JSON.parse(itemStr);
        
        if (Date.now() > item.expiry) {
            localStorage.removeItem(key);
            return null;
        }
        
        return item.value;
    }
}

// Usage
const storage = new StorageWithExpiry();

// Store for 1 hour
storage.set('session', {token: 'abc123'}, 60 * 60 * 1000);

// Later...
const session = storage.get('session');  // null if expired

// Handle quota exceeded errors
try {
    localStorage.setItem('large-data', 'x'.repeat(10 * 1024 * 1024));
} catch (error) {
    if (error.name === 'QuotaExceededError') {
        console.error('Storage quota exceeded');
        // Clear old data or notify user
    }
}

Example: IndexedDB basic operations

// Open database
function openDatabase() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open('myDatabase', 1);
        
        // Create/upgrade schema
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            
            // Create object store
            if (!db.objectStoreNames.contains('users')) {
                const store = db.createObjectStore('users', {
                    keyPath: 'id',
                    autoIncrement: true
                });
                
                // Create indexes
                store.createIndex('email', 'email', {unique: true});
                store.createIndex('name', 'name', {unique: false});
            }
        };
        
        request.onsuccess = (event) => {
            resolve(event.target.result);
        };
        
        request.onerror = (event) => {
            reject(event.target.error);
        };
    });
}

// Add data
async function addUser(user) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readwrite');
        const store = transaction.objectStore('users');
        const request = store.add(user);
        
        request.onsuccess = () => {
            resolve(request.result);  // Returns generated key
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Get data by key
async function getUser(id) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const request = store.get(id);
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Get data by index
async function getUserByEmail(email) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const index = store.index('email');
        const request = index.get(email);
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Get all data
async function getAllUsers() {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const request = store.getAll();
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Update data
async function updateUser(user) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readwrite');
        const store = transaction.objectStore('users');
        const request = store.put(user);  // Updates or inserts
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Delete data
async function deleteUser(id) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readwrite');
        const store = transaction.objectStore('users');
        const request = store.delete(id);
        
        request.onsuccess = () => {
            resolve();
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Usage
await addUser({name: 'John Doe', email: 'john@example.com'});
const user3 = await getUser(1);
const userByEmail = await getUserByEmail('john@example.com');
await updateUser({id: 1, name: 'John Smith', email: 'john@example.com'});
await deleteUser(1);

// Cursor iteration
async function iterateUsers() {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const request = store.openCursor();
        
        const results = [];
        
        request.onsuccess = (event) => {
            const cursor = event.target.result;
            
            if (cursor) {
                results.push(cursor.value);
                cursor.continue();  // Move to next
            } else {
                resolve(results);  // Done
            }
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// IndexedDB wrapper class
class IDBWrapper {
    constructor(dbName, version = 1) {
        this.dbName = dbName;
        this.version = version;
        this.db = null;
    }
    
    async open(upgrade) {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, this.version);
            
            request.onupgradeneeded = (event) => {
                if (upgrade) {
                    upgrade(event.target.result);
                }
            };
            
            request.onsuccess = (event) => {
                this.db = event.target.result;
                resolve(this.db);
            };
            
            request.onerror = () => reject(request.error);
        });
    }
    
    async add(storeName, data) {
        const transaction = this.db.transaction([storeName], 'readwrite');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.add(data);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    async get(storeName, key) {
        const transaction = this.db.transaction([storeName], 'readonly');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.get(key);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    async getAll(storeName) {
        const transaction = this.db.transaction([storeName], 'readonly');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.getAll();
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    async delete(storeName, key) {
        const transaction = this.db.transaction([storeName], 'readwrite');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.delete(key);
            request.onsuccess = () => resolve();
            request.onerror = () => reject(request.error);
        });
    }
}

// Usage
const db2 = new IDBWrapper('myApp', 1);

await db2.open((db) => {
    db.createObjectStore('users', {keyPath: 'id', autoIncrement: true});
});

await db2.add('users', {name: 'John'});
const users = await db2.getAll('users');
Key Points: localStorage persists, sessionStorage clears on tab close. Both store strings only - use JSON.stringify/parse for objects. IndexedDB for large/complex data (NoSQL database). Use transactions for IndexedDB operations. Create indexes for efficient queries. Handle QuotaExceededError. storage event for cross-tab communication.

5. Clipboard API and File API

Clipboard API Methods

Method Purpose Returns
navigator.clipboard.writeText() Copy text to clipboard Promise<void>
navigator.clipboard.readText() Read text from clipboard Promise<string>
navigator.clipboard.write() Copy data (images, HTML) to clipboard Promise<void>
navigator.clipboard.read() Read data from clipboard Promise<ClipboardItem[]>

File Input Properties

Property Type Description
input.files FileList Selected files
input.accept string File type filter (e.g., "image/*")
input.multiple boolean Allow multiple file selection

File Object Properties

Property Type Description
file.name string File name
file.size number Size in bytes
file.type string MIME type (e.g., "image/png")
file.lastModified number Last modified timestamp

FileReader Methods

Method Result Format Use Case
readAsText() String Text files
readAsDataURL() Data URL (base64) Display images, download links
readAsArrayBuffer() ArrayBuffer Binary data, manipulation
readAsBinaryString() Binary string (deprecated) Legacy support

Drag and Drop Events

Event Fires On Usage
dragstart Start dragging Set drag data
drag During drag Track drag position
dragenter Enter drop zone Visual feedback
dragover Over drop zone Allow drop (preventDefault)
dragleave Leave drop zone Remove feedback
drop Dropped Handle dropped data
dragend Drag finished Cleanup

Example: Clipboard API

// Copy text to clipboard
async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.log('Copied to clipboard');
    } catch (error) {
        console.error('Failed to copy:', error);
    }
}

// Usage
await copyToClipboard('Hello, World!');

// Read text from clipboard
async function readFromClipboard() {
    try {
        const text = await navigator.clipboard.readText();
        console.log('Clipboard text:', text);
        return text;
    } catch (error) {
        console.error('Failed to read:', error);
    }
}

// Copy button
document.querySelector('.copy-btn').addEventListener('click', async () => {
    const text = document.querySelector('.code').textContent;
    await copyToClipboard(text);
});

// Copy image to clipboard
async function copyImageToClipboard(blob) {
    try {
        await navigator.clipboard.write([
            new ClipboardItem({
                [blob.type]: blob
            })
        ]);
        console.log('Image copied');
    } catch (error) {
        console.error('Failed to copy image:', error);
    }
}

// Fetch and copy image
async function copyImageFromUrl(url) {
    const response = await fetch(url);
    const blob = await response.blob();
    await copyImageToClipboard(blob);
}

// Fallback for older browsers
function fallbackCopy(text) {
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    document.body.appendChild(textarea);
    textarea.select();
    
    try {
        const successful = document.execCommand('copy');
        console.log('Copy:', successful ? 'success' : 'failed');
    } catch (error) {
        console.error('Fallback copy failed:', error);
    }
    
    document.body.removeChild(textarea);
}

// Copy with fallback
async function copyText(text) {
    if (navigator.clipboard) {
        await navigator.clipboard.writeText(text);
    } else {
        fallbackCopy(text);
    }
}

// Listen for copy event
document.addEventListener('copy', (event) => {
    // Customize copied content
    const selection = document.getSelection().toString();
    const customText = `${selection}\n\nSource: ${window.location.href}`;
    
    event.clipboardData.setData('text/plain', customText);
    event.preventDefault();
});

// Listen for paste event
document.addEventListener('paste', (event) => {
    event.preventDefault();
    
    const text = event.clipboardData.getData('text/plain');
    console.log('Pasted:', text);
    
    // Insert at cursor or handle paste
    document.execCommand('insertText', false, text);
});

Example: File API - reading files

// HTML: <input type="file" id="fileInput" multiple accept="image/*">

const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', (event) => {
    const files = event.target.files;
    
    for (const file of files) {
        console.log('Name:', file.name);
        console.log('Size:', file.size, 'bytes');
        console.log('Type:', file.type);
        console.log('Last modified:', new Date(file.lastModified));
    }
});

// Read text file
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    
    if (file) {
        const reader = new FileReader();
        
        reader.onload = (e) => {
            const text = e.target.result;
            console.log('File content:', text);
        };
        
        reader.onerror = (e) => {
            console.error('Error reading file:', e);
        };
        
        reader.readAsText(file);
    }
});

// Read image and display
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    
    if (file && file.type.startsWith('image/')) {
        const reader = new FileReader();
        
        reader.onload = (e) => {
            const img = document.createElement('img');
            img.src = e.target.result;  // Data URL
            document.body.appendChild(img);
        };
        
        reader.readAsDataURL(file);
    }
});

// Read as ArrayBuffer for binary processing
fileInput.addEventListener('change', async (event) => {
    const file = event.target.files[0];
    
    if (file) {
        const arrayBuffer = await file.arrayBuffer();
        const bytes = new Uint8Array(arrayBuffer);
        
        console.log('First 10 bytes:', bytes.slice(0, 10));
    }
});

// Progress tracking
function readFileWithProgress(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        
        reader.onprogress = (event) => {
            if (event.lengthComputable) {
                const percent = (event.loaded / event.total) * 100;
                console.log(`Progress: ${percent.toFixed(2)}%`);
            }
        };
        
        reader.onload = (event) => {
            resolve(event.target.result);
        };
        
        reader.onerror = (event) => {
            reject(reader.error);
        };
        
        reader.readAsArrayBuffer(file);
    });
}

// Multiple files
fileInput.addEventListener('change', async (event) => {
    const files = Array.from(event.target.files);
    
    const promises = files.map(file => {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => resolve({file, data: e.target.result});
            reader.onerror = reject;
            reader.readAsDataURL(file);
        });
    });
    
    const results = await Promise.all(promises);
    results.forEach(({file, data}) => {
        console.log(`${file.name}: ${data.length} characters`);
    });
});

// File validation
function validateFile(file) {
    const maxSize = 5 * 1024 * 1024;  // 5 MB
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    
    if (file.size > maxSize) {
        throw new Error('File too large (max 5MB)');
    }
    
    if (!allowedTypes.includes(file.type)) {
        throw new Error('Invalid file type');
    }
    
    return true;
}

fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    
    try {
        validateFile(file);
        // Process file
    } catch (error) {
        alert(error.message);
        fileInput.value = '';  // Clear input
    }
});

Example: Drag and drop file upload

// HTML: <div id="dropZone">Drop files here</div>

const dropZone = document.getElementById('dropZone');

// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, preventDefaults, false);
    document.body.addEventListener(eventName, preventDefaults, false);
});

function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
}

// Visual feedback
['dragenter', 'dragover'].forEach(eventName => {
    dropZone.addEventListener(eventName, () => {
        dropZone.classList.add('highlight');
    }, false);
});

['dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, () => {
        dropZone.classList.remove('highlight');
    }, false);
});

// Handle drop
dropZone.addEventListener('drop', handleDrop, false);

function handleDrop(e) {
    const dt = e.dataTransfer;
    const files = dt.files;
    
    handleFiles(files);
}

function handleFiles(files) {
    [...files].forEach(uploadFile);
}

function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    
    fetch('/upload', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        console.log('Upload success:', data);
    })
    .catch(error => {
        console.error('Upload error:', error);
    });
}

// Preview dropped images
function handleDrop2(e) {
    const dt = e.dataTransfer;
    const files = dt.files;
    
    [...files].forEach(file => {
        if (file.type.startsWith('image/')) {
            const reader = new FileReader();
            
            reader.onload = (e) => {
                const img = document.createElement('img');
                img.src = e.target.result;
                img.style.maxWidth = '200px';
                dropZone.appendChild(img);
            };
            
            reader.readAsDataURL(file);
        }
    });
}

// Drag from element to another
const draggableItem = document.querySelector('.draggable');

draggableItem.addEventListener('dragstart', (e) => {
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/html', e.target.innerHTML);
    e.dataTransfer.setData('text/plain', e.target.textContent);
});

const dropTarget = document.querySelector('.drop-target');

dropTarget.addEventListener('dragover', (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
});

dropTarget.addEventListener('drop', (e) => {
    e.preventDefault();
    
    const html = e.dataTransfer.getData('text/html');
    dropTarget.innerHTML = html;
});

// File System Access API (modern browsers)
async function openFilePicker() {
    try {
        const [fileHandle] = await window.showOpenFilePicker({
            types: [{
                description: 'Text Files',
                accept: {'text/plain': ['.txt']}
            }],
            multiple: false
        });
        
        const file = await fileHandle.getFile();
        const text = await file.text();
        
        console.log('File content:', text);
    } catch (error) {
        console.error('Error:', error);
    }
}

// Save file
async function saveFile(content) {
    try {
        const fileHandle = await window.showSaveFilePicker({
            suggestedName: 'document.txt',
            types: [{
                description: 'Text Files',
                accept: {'text/plain': ['.txt']}
            }]
        });
        
        const writable = await fileHandle.createWritable();
        await writable.write(content);
        await writable.close();
        
        console.log('File saved');
    } catch (error) {
        console.error('Error:', error);
    }
}
Key Points: navigator.clipboard.writeText() for copying text (requires HTTPS). Use FileReader to read file contents (text, dataURL, arrayBuffer). File validation (size, type) before upload. Drag and drop: preventDefault on dragover and drop. FormData for file uploads. file.arrayBuffer() for modern async file reading. File System Access API for advanced file operations.

6. Geolocation and Device APIs

Geolocation API Methods

Method Purpose Callback Parameters
getCurrentPosition() Get current location once success, error, options
watchPosition() Track location changes success, error, options
clearWatch() Stop watching position watchId

Position Object Properties

Property Type Description
coords.latitude number Latitude in decimal degrees
coords.longitude number Longitude in decimal degrees
coords.accuracy number Accuracy in meters
coords.altitude number | null Altitude in meters
coords.altitudeAccuracy number | null Altitude accuracy in meters
coords.heading number | null Direction in degrees (0=North)
coords.speed number | null Speed in meters per second
timestamp number Timestamp of position

Geolocation Options

Option Type Description
enableHighAccuracy boolean Request best possible results (uses GPS)
timeout number Maximum time to wait (ms)
maximumAge number Max age of cached position (ms)

Device Orientation Properties

Property Range Description
alpha 0-360 Z-axis rotation (compass heading)
beta -180 to 180 X-axis rotation (front-to-back tilt)
gamma -90 to 90 Y-axis rotation (left-to-right tilt)
absolute boolean True if relative to Earth's coordinate frame

Other Device APIs

API Purpose Access Via
Battery Status Battery level and charging status navigator.getBattery()
Network Information Connection type and speed navigator.connection
Vibration Trigger device vibration navigator.vibrate()
Screen Orientation Lock/unlock screen orientation screen.orientation
Media Devices Access camera/microphone navigator.mediaDevices

Example: Geolocation API

// Get current position
if ('geolocation' in navigator) {
    navigator.geolocation.getCurrentPosition(
        (position) => {
            console.log('Latitude:', position.coords.latitude);
            console.log('Longitude:', position.coords.longitude);
            console.log('Accuracy:', position.coords.accuracy, 'meters');
            console.log('Timestamp:', new Date(position.timestamp));
        },
        (error) => {
            console.error('Error:', error.code, error.message);
            
            switch(error.code) {
                case error.PERMISSION_DENIED:
                    console.log('User denied geolocation');
                    break;
                case error.POSITION_UNAVAILABLE:
                    console.log('Location unavailable');
                    break;
                case error.TIMEOUT:
                    console.log('Request timeout');
                    break;
            }
        },
        {
            enableHighAccuracy: true,
            timeout: 5000,
            maximumAge: 0
        }
    );
} else {
    console.log('Geolocation not supported');
}

// Async/await wrapper
function getPosition(options) {
    return new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject, options);
    });
}

// Usage
try {
    const position = await getPosition({
        enableHighAccuracy: true,
        timeout: 10000
    });
    
    const {latitude, longitude} = position.coords;
    console.log(`Location: ${latitude}, ${longitude}`);
} catch (error) {
    console.error('Geolocation error:', error);
}

// Watch position (continuous tracking)
const watchId = navigator.geolocation.watchPosition(
    (position) => {
        const {latitude, longitude, speed, heading} = position.coords;
        
        console.log(`Lat: ${latitude}, Lng: ${longitude}`);
        
        if (speed !== null) {
            console.log(`Speed: ${speed} m/s`);
        }
        
        if (heading !== null) {
            console.log(`Heading: ${heading}°`);
        }
        
        // Update map or UI
        updateMap(latitude, longitude);
    },
    (error) => {
        console.error('Watch error:', error);
    },
    {
        enableHighAccuracy: true,
        maximumAge: 30000,
        timeout: 27000
    }
);

// Stop watching
navigator.geolocation.clearWatch(watchId);

// Calculate distance between two points (Haversine formula)
function calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 6371e3; // Earth's radius in meters
    const φ1 = lat1 * Math.PI / 180;
    const φ2 = lat2 * Math.PI / 180;
    const Δφ = (lat2 - lat1) * Math.PI / 180;
    const Δλ = (lon2 - lon1) * Math.PI / 180;
    
    const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    
    return R * c; // Distance in meters
}

// Usage
const distance = calculateDistance(51.5074, -0.1278, 48.8566, 2.3522);
console.log(`Distance: ${(distance / 1000).toFixed(2)} km`);

// Geolocation with React hook
function useGeolocation(options) {
    const [position, setPosition] = useState(null);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        const watchId = navigator.geolocation.watchPosition(
            setPosition,
            setError,
            options
        );
        
        return () => navigator.geolocation.clearWatch(watchId);
    }, []);
    
    return {position, error};
}

// Usage in component
function LocationTracker() {
    const {position, error} = useGeolocation({enableHighAccuracy: true});
    
    if (error) return <div>Error: {error.message}</div>;
    if (!position) return <div>Loading...</div>;
    
    return (
        <div>
            <p>Lat: {position.coords.latitude}</p>
            <p>Lng: {position.coords.longitude}</p>
        </div>
    );
}

Example: Device orientation and other APIs

// Device orientation (gyroscope)
window.addEventListener('deviceorientation', (event) => {
    const alpha = event.alpha;  // Z-axis (0-360)
    const beta = event.beta;    // X-axis (-180 to 180)
    const gamma = event.gamma;  // Y-axis (-90 to 90)
    
    console.log(`α: ${alpha}°, β: ${beta}°, γ: ${gamma}°`);
    
    // Use for compass, level, or 3D effects
    updateCompass(alpha);
});

// Device motion (accelerometer)
window.addEventListener('devicemotion', (event) => {
    const acceleration = event.acceleration;
    const rotationRate = event.rotationRate;
    
    console.log('Acceleration:');
    console.log('  x:', acceleration.x);
    console.log('  y:', acceleration.y);
    console.log('  z:', acceleration.z);
    
    console.log('Rotation rate:');
    console.log('  α:', rotationRate.alpha);
    console.log('  β:', rotationRate.beta);
    console.log('  γ:', rotationRate.gamma);
    
    // Detect shake
    const totalAcceleration = Math.abs(acceleration.x) +
                             Math.abs(acceleration.y) +
                             Math.abs(acceleration.z);
    
    if (totalAcceleration > 30) {
        console.log('Device shaken!');
    }
});

// Request permission for iOS 13+
async function requestMotionPermission() {
    if (typeof DeviceMotionEvent.requestPermission === 'function') {
        const permission = await DeviceMotionEvent.requestPermission();
        
        if (permission === 'granted') {
            window.addEventListener('devicemotion', handleMotion);
        }
    }
}

// Battery API
navigator.getBattery().then((battery) => {
    console.log('Battery level:', battery.level * 100, '%');
    console.log('Charging:', battery.charging);
    console.log('Charging time:', battery.chargingTime, 'seconds');
    console.log('Discharging time:', battery.dischargingTime, 'seconds');
    
    // Listen for changes
    battery.addEventListener('levelchange', () => {
        console.log('Battery level:', battery.level * 100, '%');
    });
    
    battery.addEventListener('chargingchange', () => {
        console.log('Charging:', battery.charging);
    });
});

// Network Information API
if ('connection' in navigator) {
    const connection = navigator.connection;
    
    console.log('Connection type:', connection.effectiveType);
    console.log('Downlink speed:', connection.downlink, 'Mb/s');
    console.log('RTT:', connection.rtt, 'ms');
    console.log('Save data:', connection.saveData);
    
    // Adjust quality based on connection
    if (connection.effectiveType === '4g') {
        loadHighQualityImages();
    } else {
        loadLowQualityImages();
    }
    
    // Listen for connection changes
    connection.addEventListener('change', () => {
        console.log('Connection changed to:', connection.effectiveType);
    });
}

// Vibration API
if ('vibrate' in navigator) {
    // Vibrate for 200ms
    navigator.vibrate(200);
    
    // Pattern: vibrate, pause, vibrate
    navigator.vibrate([200, 100, 200]);
    
    // Stop vibration
    navigator.vibrate(0);
    
    // Feedback on button click
    button.addEventListener('click', () => {
        navigator.vibrate(50);
    });
}

// Screen Orientation API
if ('orientation' in screen) {
    console.log('Current orientation:', screen.orientation.type);
    console.log('Angle:', screen.orientation.angle);
    
    // Lock orientation
    screen.orientation.lock('landscape').then(() => {
        console.log('Orientation locked');
    }).catch((error) => {
        console.error('Lock failed:', error);
    });
    
    // Unlock orientation
    screen.orientation.unlock();
    
    // Listen for orientation changes
    screen.orientation.addEventListener('change', () => {
        console.log('New orientation:', screen.orientation.type);
    });
}

// Media Devices (Camera/Microphone)
async function getUserMedia() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({
            video: {
                width: {ideal: 1920},
                height: {ideal: 1080}
            },
            audio: true
        });
        
        // Display video
        const video = document.querySelector('video');
        video.srcObject = stream;
        
        return stream;
    } catch (error) {
        console.error('Media access denied:', error);
    }
}

// List available devices
async function listDevices() {
    const devices = await navigator.mediaDevices.enumerateDevices();
    
    devices.forEach(device => {
        console.log(device.kind, ':', device.label);
    });
}

// Online/offline detection
window.addEventListener('online', () => {
    console.log('Back online');
    syncOfflineData();
});

window.addEventListener('offline', () => {
    console.log('Gone offline');
    showOfflineMessage();
});

// Check current status
if (navigator.onLine) {
    console.log('Currently online');
} else {
    console.log('Currently offline');
}

// Page Visibility API
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        console.log('Page hidden');
        pauseVideo();
    } else {
        console.log('Page visible');
        resumeVideo();
    }
});
Key Points: Geolocation API requires user permission. Use enableHighAccuracy for GPS. watchPosition() for continuous tracking. Device orientation for gyroscope data. Battery API for power management. Network Information to adapt to connection quality. Vibration API for haptic feedback. Always check for API support before use.

7. Web Workers and Service Workers

Web Worker Types

Type Scope Use Case
Dedicated Worker Single script Heavy computation, background tasks
Shared Worker Multiple scripts/tabs Cross-tab communication
Service Worker Network proxy Offline support, caching, push notifications

Worker Methods and Events

Method/Event Purpose Context
postMessage() Send message Main thread ↔ Worker
onmessage Receive message Main thread ↔ Worker
terminate() Stop worker Main thread
close() Stop self Worker
onerror Handle errors Main thread
importScripts() Load external scripts Worker

Service Worker Lifecycle

State Description Event
Installing First time registration install
Installed (Waiting) Installed but not active -
Activating Taking control activate
Activated Controlling pages -
Redundant Replaced by newer version -

Service Worker Events

Event When Fired Use Case
install First installation Cache resources
activate Becomes active Clean old caches
fetch Network request Cache strategy, offline fallback
message Receive message from page Communication
push Push notification received Show notification
sync Background sync triggered Sync data when online

Cache Strategies

Strategy Description Best For
Cache First Cache, fallback to network Static assets (CSS, JS, images)
Network First Network, fallback to cache API calls, dynamic content
Cache Only Always from cache Offline-first apps
Network Only Always from network Real-time data
Stale While Revalidate Cache first, update in background Balance freshness and speed

Example: Web Workers

// Main thread (main.js)
const worker = new Worker('worker.js');

// Send message to worker
worker.postMessage({
    type: 'calculate',
    data: [1, 2, 3, 4, 5]
});

// Receive message from worker
worker.onmessage = (event) => {
    console.log('Result from worker:', event.data);
};

// Handle errors
worker.onerror = (error) => {
    console.error('Worker error:', error.message);
};

// Terminate worker
worker.terminate();

// Worker file (worker.js)
self.onmessage = (event) => {
    const {type, data} = event.data;
    
    if (type === 'calculate') {
        // Heavy computation
        const result = data.reduce((sum, num) => sum + num, 0);
        
        // Send result back
        self.postMessage({
            type: 'result',
            result: result
        });
    }
};

// Import external scripts in worker
importScripts('math-utils.js', 'helpers.js');

// Example: Image processing in worker
// Main thread
const imageWorker = new Worker('image-processor.js');

canvas.toBlob((blob) => {
    imageWorker.postMessage({
        type: 'process',
        image: blob
    });
});

imageWorker.onmessage = (event) => {
    const processedImage = event.data;
    displayImage(processedImage);
};

// Worker (image-processor.js)
self.onmessage = async (event) => {
    const {type, image} = event.data;
    
    if (type === 'process') {
        // Process image
        const bitmap = await createImageBitmap(image);
        
        // Apply filters, transformations
        const processed = applyFilters(bitmap);
        
        self.postMessage(processed);
    }
};

// Transferable objects (zero-copy transfer)
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);  // Transfer ownership
// buffer is now unusable in main thread

// Worker pool for parallel processing
class WorkerPool {
    constructor(workerScript, poolSize = 4) {
        this.workers = [];
        this.queue = [];
        
        for (let i = 0; i < poolSize; i++) {
            const worker = new Worker(workerScript);
            worker.busy = false;
            
            worker.onmessage = (event) => {
                worker.busy = false;
                worker.currentTask.resolve(event.data);
                this.processQueue();
            };
            
            worker.onerror = (error) => {
                worker.busy = false;
                worker.currentTask.reject(error);
                this.processQueue();
            };
            
            this.workers.push(worker);
        }
    }
    
    run(data) {
        return new Promise((resolve, reject) => {
            this.queue.push({data, resolve, reject});
            this.processQueue();
        });
    }
    
    processQueue() {
        if (this.queue.length === 0) return;
        
        const availableWorker = this.workers.find(w => !w.busy);
        
        if (availableWorker) {
            const task = this.queue.shift();
            availableWorker.busy = true;
            availableWorker.currentTask = task;
            availableWorker.postMessage(task.data);
        }
    }
    
    terminate() {
        this.workers.forEach(w => w.terminate());
    }
}

// Usage
const pool = new WorkerPool('worker.js', 4);

const results = await Promise.all([
    pool.run({task: 'process', data: data1}),
    pool.run({task: 'process', data: data2}),
    pool.run({task: 'process', data: data3})
]);

pool.terminate();

Example: Service Workers

// Register service worker (main.js)
if ('serviceWorker' in navigator) {
    window.addEventListener('load', async () => {
        try {
            const registration = await navigator.serviceWorker.register('/sw.js');
            
            console.log('Service Worker registered:', registration.scope);
            
            // Check for updates
            registration.addEventListener('updatefound', () => {
                const newWorker = registration.installing;
                
                newWorker.addEventListener('statechange', () => {
                    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
                        console.log('New version available!');
                        // Prompt user to refresh
                    }
                });
            });
        } catch (error) {
            console.error('Service Worker registration failed:', error);
        }
    });
}

// Service Worker file (sw.js)
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
    '/',
    '/styles.css',
    '/script.js',
    '/images/logo.png'
];

// Install event - cache resources
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then((cache) => {
                console.log('Caching files');
                return cache.addAll(urlsToCache);
            })
    );
    
    // Skip waiting to activate immediately
    self.skipWaiting();
});

// Activate event - clean old caches
self.addEventListener('activate', (event) => {
    event.waitUntil(
        caches.keys().then((cacheNames) => {
            return Promise.all(
                cacheNames.map((cacheName) => {
                    if (cacheName !== CACHE_NAME) {
                        console.log('Deleting old cache:', cacheName);
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
    
    // Take control of all pages immediately
    self.clients.claim();
});

// Fetch event - serve from cache or network
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((response) => {
                // Cache hit - return response
                if (response) {
                    return response;
                }
                
                // Clone request
                const fetchRequest = event.request.clone();
                
                return fetch(fetchRequest).then((response) => {
                    // Check valid response
                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }
                    
                    // Clone response
                    const responseToCache = response.clone();
                    
                    // Cache new response
                    caches.open(CACHE_NAME)
                        .then((cache) => {
                            cache.put(event.request, responseToCache);
                        });
                    
                    return response;
                });
            })
    );
});

// Network First strategy
self.addEventListener('fetch', (event) => {
    event.respondWith(
        fetch(event.request)
            .then((response) => {
                // Cache successful response
                const responseToCache = response.clone();
                caches.open(CACHE_NAME)
                    .then((cache) => cache.put(event.request, responseToCache));
                
                return response;
            })
            .catch(() => {
                // Network failed, try cache
                return caches.match(event.request);
            })
    );
});

// Stale While Revalidate
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((cachedResponse) => {
                const fetchPromise = fetch(event.request)
                    .then((networkResponse) => {
                        // Update cache
                        caches.open(CACHE_NAME)
                            .then((cache) => cache.put(event.request, networkResponse.clone()));
                        
                        return networkResponse;
                    });
                
                // Return cached response immediately, update in background
                return cachedResponse || fetchPromise;
            })
    );
});

// Message handling
self.addEventListener('message', (event) => {
    if (event.data.action === 'skipWaiting') {
        self.skipWaiting();
    }
});

// Push notifications
self.addEventListener('push', (event) => {
    const data = event.data.json();
    
    const options = {
        body: data.body,
        icon: '/images/icon.png',
        badge: '/images/badge.png',
        data: {url: data.url}
    };
    
    event.waitUntil(
        self.registration.showNotification(data.title, options)
    );
});

// Notification click
self.addEventListener('notificationclick', (event) => {
    event.notification.close();
    
    event.waitUntil(
        clients.openWindow(event.notification.data.url)
    );
});

// Background sync
self.addEventListener('sync', (event) => {
    if (event.tag === 'sync-messages') {
        event.waitUntil(syncMessages());
    }
});

async function syncMessages() {
    // Get pending messages from IndexedDB
    const messages = await getPendingMessages();
    
    // Send to server
    for (const message of messages) {
        try {
            await fetch('/api/messages', {
                method: 'POST',
                body: JSON.stringify(message)
            });
            
            // Remove from pending
            await removePendingMessage(message.id);
        } catch (error) {
            console.error('Sync failed:', error);
        }
    }
}

// Unregister service worker
navigator.serviceWorker.getRegistrations().then((registrations) => {
    registrations.forEach((registration) => {
        registration.unregister();
    });
});
Key Points: Web Workers run on separate thread for heavy computation. Use postMessage() for communication. Service Workers act as network proxy for offline support. Cache strategies: cache-first, network-first, stale-while-revalidate. Service worker lifecycle: install → activate. Handle fetch events to intercept requests. Push notifications and background sync. Transferable objects for efficient data transfer.

Section 21 Summary: Web APIs and Browser Integration

  • Fetch API: Promise-based HTTP requests, response.json(), request options (method, headers, body)
  • Response Handling: Check response.ok, status codes, AbortController for cancellation
  • DOM Selection: querySelector/All, getElementById, closest, matches for flexible selection
  • DOM Manipulation: createElement, append/prepend, classList, innerHTML vs textContent
  • Events: addEventListener with options (once, capture, passive), event delegation pattern
  • Storage: localStorage (persistent), sessionStorage (tab-scoped), IndexedDB (NoSQL database)
  • Clipboard API: navigator.clipboard.writeText/readText, requires HTTPS and permissions
  • File API: FileReader (readAsText, readAsDataURL), drag and drop, file validation
  • Geolocation: getCurrentPosition, watchPosition, coordinates (lat/lng/accuracy)
  • Device APIs: Orientation, motion, battery, network info, vibration, media devices
  • Web Workers: Separate thread for computation, postMessage communication, worker pools
  • Service Workers: Network proxy, offline support, caching strategies, push notifications