javascript

examples

examples.js
/**
 * ========================================
 * 11.4 URL and History APIs - Examples
 * ========================================
 *
 * Working with URLs and browser history.
 * These examples work in browser environments.
 */

/**
 * EXAMPLE 1: URL Parsing
 *
 * Parse and extract parts of a URL.
 */

function parseURL(urlString) {
  const url = new URL(urlString);

  return {
    href: url.href, // Full URL
    protocol: url.protocol, // https:
    username: url.username, // User (if any)
    password: url.password, // Pass (if any)
    host: url.host, // hostname:port
    hostname: url.hostname, // Just hostname
    port: url.port, // Port number
    pathname: url.pathname, // /path/to/page
    search: url.search, // ?query=value
    hash: url.hash, // #section
    origin: url.origin, // https://hostname:port
  };
}

// Example usage
const parsed = parseURL(
  'https://user:pass@api.example.com:8080/v1/users?page=1&limit=10#results'
);
console.log('Parsed URL:', parsed);

/**
 * EXAMPLE 2: Building URLs
 *
 * Construct URLs safely.
 */

function buildURL(base, path, params = {}) {
  const url = new URL(path, base);

  Object.entries(params).forEach(([key, value]) => {
    if (value === undefined || value === null) return;

    if (Array.isArray(value)) {
      // Handle array values
      value.forEach((v) => url.searchParams.append(key, v));
    } else {
      url.searchParams.set(key, value);
    }
  });

  return url.toString();
}

// Examples
console.log(
  buildURL('https://api.example.com', '/search', {
    q: 'hello world',
    page: 1,
  })
);
// https://api.example.com/search?q=hello+world&page=1

console.log(
  buildURL('https://api.example.com', '/filter', {
    tags: ['js', 'web', 'api'],
  })
);
// https://api.example.com/filter?tags=js&tags=web&tags=api

/**
 * EXAMPLE 3: URLSearchParams Manipulation
 *
 * Work with query parameters.
 */

class QueryParams {
  constructor(search = window.location?.search || '') {
    this.params = new URLSearchParams(search);
  }

  // Get single value
  get(key) {
    return this.params.get(key);
  }

  // Get all values for key
  getAll(key) {
    return this.params.getAll(key);
  }

  // Set value (replaces existing)
  set(key, value) {
    this.params.set(key, value);
    return this;
  }

  // Append value (allows duplicates)
  append(key, value) {
    this.params.append(key, value);
    return this;
  }

  // Remove all with key
  delete(key) {
    this.params.delete(key);
    return this;
  }

  // Check if exists
  has(key) {
    return this.params.has(key);
  }

  // Convert to object
  toObject() {
    const obj = {};
    for (const [key, value] of this.params.entries()) {
      if (obj[key]) {
        // Handle multiple values
        obj[key] = Array.isArray(obj[key])
          ? [...obj[key], value]
          : [obj[key], value];
      } else {
        obj[key] = value;
      }
    }
    return obj;
  }

  // Convert to string
  toString() {
    const str = this.params.toString();
    return str ? `?${str}` : '';
  }
}

// Usage
const qp = new QueryParams('?page=1&sort=date&tag=js&tag=web');
console.log('Get page:', qp.get('page')); // '1'
console.log('Get all tags:', qp.getAll('tag')); // ['js', 'web']
console.log('As object:', qp.toObject());

/**
 * EXAMPLE 4: URL Validation
 *
 * Validate and normalize URLs.
 */

function isValidURL(string) {
  try {
    new URL(string);
    return true;
  } catch {
    return false;
  }
}

function isValidHTTPURL(string) {
  try {
    const url = new URL(string);
    return url.protocol === 'http:' || url.protocol === 'https:';
  } catch {
    return false;
  }
}

function isSameOrigin(url1, url2) {
  try {
    const a = new URL(url1);
    const b = new URL(url2);
    return a.origin === b.origin;
  } catch {
    return false;
  }
}

// Validation examples
console.log('Valid URL:', isValidURL('https://example.com')); // true
console.log('Valid URL:', isValidURL('not a url')); // false
console.log('HTTP URL:', isValidHTTPURL('ftp://example.com')); // false
console.log(
  'Same origin:',
  isSameOrigin('https://example.com/page1', 'https://example.com/page2')
); // true

/**
 * EXAMPLE 5: URL Encoding/Decoding
 *
 * Safely encode and decode URL components.
 */

const URLUtils = {
  // Encode a query parameter value
  encodeParam(value) {
    return encodeURIComponent(value);
  },

  // Decode a query parameter value
  decodeParam(value) {
    try {
      return decodeURIComponent(value);
    } catch {
      return value;
    }
  },

  // Build query string from object
  buildQuery(params) {
    return Object.entries(params)
      .filter(([, value]) => value !== undefined && value !== null)
      .map(([key, value]) => {
        if (Array.isArray(value)) {
          return value
            .map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`)
            .join('&');
        }
        return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
      })
      .join('&');
  },

  // Parse query string to object
  parseQuery(queryString) {
    const params = new URLSearchParams(queryString);
    const result = {};

    for (const [key, value] of params.entries()) {
      const decoded = this.decodeParam(value);
      if (result[key]) {
        result[key] = Array.isArray(result[key])
          ? [...result[key], decoded]
          : [result[key], decoded];
      } else {
        result[key] = decoded;
      }
    }

    return result;
  },
};

// Usage
console.log('Encoded:', URLUtils.encodeParam('hello world & more'));
console.log(
  'Query:',
  URLUtils.buildQuery({
    name: 'John Doe',
    tags: ['a', 'b'],
  })
);

/**
 * EXAMPLE 6: History State Management
 *
 * Manage browser history with state.
 */

class HistoryManager {
  constructor() {
    this.setupListeners();
  }

  setupListeners() {
    window.addEventListener('popstate', (event) => {
      this.onNavigate(event.state, location.href);
    });
  }

  // Navigate to new page
  push(url, state = {}) {
    const fullState = {
      ...state,
      timestamp: Date.now(),
    };
    history.pushState(fullState, '', url);
    this.onNavigate(fullState, url);
  }

  // Replace current page
  replace(url, state = {}) {
    const fullState = {
      ...state,
      timestamp: Date.now(),
    };
    history.replaceState(fullState, '', url);
  }

  // Go back
  back() {
    history.back();
  }

  // Go forward
  forward() {
    history.forward();
  }

  // Go to specific point
  go(delta) {
    history.go(delta);
  }

  // Get current state
  getState() {
    return history.state;
  }

  // Override in subclass or set handler
  onNavigate(state, url) {
    console.log('Navigated to:', url, 'State:', state);
  }
}

/**
 * EXAMPLE 7: Simple SPA Router
 *
 * Basic client-side routing.
 */

class SimpleRouter {
  constructor(routes = {}) {
    this.routes = routes;
    this.currentRoute = null;

    // Listen for browser navigation
    window.addEventListener('popstate', () => {
      this.handleRoute(location.pathname);
    });

    // Handle initial route
    this.handleRoute(location.pathname);
  }

  // Add a route
  addRoute(path, handler) {
    this.routes[path] = handler;
  }

  // Navigate programmatically
  navigate(path, state = {}) {
    if (path === location.pathname) return;

    history.pushState(state, '', path);
    this.handleRoute(path);
  }

  // Handle route matching
  handleRoute(pathname) {
    // Exact match
    if (this.routes[pathname]) {
      this.currentRoute = pathname;
      this.routes[pathname]();
      return;
    }

    // Pattern matching (e.g., /users/:id)
    for (const [pattern, handler] of Object.entries(this.routes)) {
      const params = this.matchPattern(pattern, pathname);
      if (params) {
        this.currentRoute = pathname;
        handler(params);
        return;
      }
    }

    // 404 handler
    if (this.routes['*']) {
      this.routes['*']();
    }
  }

  // Match URL pattern
  matchPattern(pattern, pathname) {
    const patternParts = pattern.split('/');
    const pathParts = pathname.split('/');

    if (patternParts.length !== pathParts.length) {
      return null;
    }

    const params = {};

    for (let i = 0; i < patternParts.length; i++) {
      const patternPart = patternParts[i];
      const pathPart = pathParts[i];

      if (patternPart.startsWith(':')) {
        // Dynamic segment
        params[patternPart.slice(1)] = pathPart;
      } else if (patternPart !== pathPart) {
        return null;
      }
    }

    return params;
  }
}

// Usage:
// const router = new SimpleRouter({
//     '/': () => console.log('Home'),
//     '/about': () => console.log('About'),
//     '/users/:id': (params) => console.log('User:', params.id),
//     '*': () => console.log('404 Not Found')
// });
// router.navigate('/users/123');

/**
 * EXAMPLE 8: Hash-Based Router
 *
 * Routing using URL hash (for static hosting).
 */

class HashRouter {
  constructor(routes = {}) {
    this.routes = routes;

    window.addEventListener('hashchange', () => {
      this.handleRoute();
    });

    // Handle initial hash
    this.handleRoute();
  }

  // Get current hash path
  getHashPath() {
    return location.hash.slice(1) || '/';
  }

  // Navigate
  navigate(path) {
    location.hash = path;
  }

  // Handle route
  handleRoute() {
    const path = this.getHashPath();

    if (this.routes[path]) {
      this.routes[path]();
    } else if (this.routes['*']) {
      this.routes['*']();
    }
  }
}

// Usage:
// const hashRouter = new HashRouter({
//     '/': () => console.log('Home'),
//     '/about': () => console.log('About'),
//     '*': () => console.log('404')
// });
// hashRouter.navigate('/about');  // URL becomes #/about

/**
 * EXAMPLE 9: Query Parameter Sync
 *
 * Keep application state in sync with URL query parameters.
 */

class QueryStateSync {
  constructor(defaultState = {}) {
    this.defaultState = defaultState;
    this.listeners = [];
  }

  // Get current state from URL
  getState() {
    const params = new URLSearchParams(location.search);
    const state = { ...this.defaultState };

    for (const [key, defaultValue] of Object.entries(this.defaultState)) {
      const value = params.get(key);
      if (value !== null) {
        // Type coercion based on default value
        if (typeof defaultValue === 'number') {
          state[key] = Number(value);
        } else if (typeof defaultValue === 'boolean') {
          state[key] = value === 'true';
        } else {
          state[key] = value;
        }
      }
    }

    return state;
  }

  // Update URL with new state
  setState(newState, replace = false) {
    const params = new URLSearchParams();
    const fullState = { ...this.getState(), ...newState };

    for (const [key, value] of Object.entries(fullState)) {
      // Only include non-default values
      if (value !== this.defaultState[key]) {
        params.set(key, String(value));
      }
    }

    const search = params.toString();
    const url = search ? `${location.pathname}?${search}` : location.pathname;

    if (replace) {
      history.replaceState(null, '', url);
    } else {
      history.pushState(null, '', url);
    }

    this.notifyListeners(fullState);
  }

  // Subscribe to state changes
  subscribe(listener) {
    this.listeners.push(listener);

    // Handle popstate
    const handler = () => listener(this.getState());
    window.addEventListener('popstate', handler);

    // Return unsubscribe function
    return () => {
      this.listeners = this.listeners.filter((l) => l !== listener);
      window.removeEventListener('popstate', handler);
    };
  }

  notifyListeners(state) {
    this.listeners.forEach((listener) => listener(state));
  }
}

// Usage:
// const queryState = new QueryStateSync({
//     page: 1,
//     sort: 'date',
//     ascending: true
// });
//
// queryState.subscribe(state => console.log('State changed:', state));
// queryState.setState({ page: 2 });

/**
 * EXAMPLE 10: Scroll Position Management
 *
 * Remember and restore scroll positions during navigation.
 */

class ScrollManager {
  constructor() {
    this.positions = new Map();
    this.setupListeners();
  }

  setupListeners() {
    // Save position before navigation
    window.addEventListener('beforeunload', () => {
      this.savePosition();
    });

    // Restore position on popstate
    window.addEventListener('popstate', () => {
      setTimeout(() => this.restorePosition(), 0);
    });
  }

  // Generate key for current page
  getKey() {
    return location.pathname + location.search;
  }

  // Save current scroll position
  savePosition() {
    const key = this.getKey();
    this.positions.set(key, {
      x: window.scrollX,
      y: window.scrollY,
    });
  }

  // Restore scroll position
  restorePosition() {
    const key = this.getKey();
    const position = this.positions.get(key);

    if (position) {
      window.scrollTo(position.x, position.y);
    } else {
      window.scrollTo(0, 0);
    }
  }

  // Navigate with scroll management
  navigate(url) {
    this.savePosition();
    history.pushState(null, '', url);
    window.scrollTo(0, 0);
  }
}

/**
 * EXAMPLE 11: URL Path Utilities
 *
 * Utility functions for path manipulation.
 */

const PathUtils = {
  // Join path segments
  join(...segments) {
    return segments
      .map((seg, i) => {
        if (i === 0) return seg.replace(/\/+$/, '');
        if (i === segments.length - 1) return seg.replace(/^\/+/, '');
        return seg.replace(/^\/+|\/+$/g, '');
      })
      .filter(Boolean)
      .join('/');
  },

  // Get parent path
  dirname(path) {
    const parts = path.split('/').filter(Boolean);
    parts.pop();
    return '/' + parts.join('/');
  },

  // Get last segment
  basename(path) {
    const parts = path.split('/').filter(Boolean);
    return parts[parts.length - 1] || '';
  },

  // Get file extension
  extname(path) {
    const base = this.basename(path);
    const dot = base.lastIndexOf('.');
    return dot > 0 ? base.slice(dot) : '';
  },

  // Normalize path (resolve .. and .)
  normalize(path) {
    const parts = path.split('/').filter(Boolean);
    const result = [];

    for (const part of parts) {
      if (part === '..') {
        result.pop();
      } else if (part !== '.') {
        result.push(part);
      }
    }

    return '/' + result.join('/');
  },

  // Check if path is absolute
  isAbsolute(path) {
    return path.startsWith('/');
  },
};

console.log('Join:', PathUtils.join('/api', 'v1/', '/users')); // /api/v1/users
console.log('Dirname:', PathUtils.dirname('/api/v1/users')); // /api/v1
console.log('Basename:', PathUtils.basename('/api/v1/users')); // users

/**
 * EXAMPLE 12: Deep Linking
 *
 * Handle deep links with state restoration.
 */

class DeepLinkManager {
  constructor() {
    this.handlers = new Map();
  }

  // Register a deep link handler
  register(pattern, handler) {
    this.handlers.set(pattern, handler);
  }

  // Process current URL
  process() {
    const url = new URL(location.href);
    const path = url.pathname;
    const params = Object.fromEntries(url.searchParams);

    for (const [pattern, handler] of this.handlers) {
      const match = this.matchPattern(pattern, path);
      if (match) {
        handler({ ...match, query: params, hash: url.hash.slice(1) });
        return true;
      }
    }
    return false;
  }

  // Match URL pattern
  matchPattern(pattern, path) {
    const regex = new RegExp('^' + pattern.replace(/:[^/]+/g, '([^/]+)') + '$');
    const match = path.match(regex);

    if (!match) return null;

    // Extract named parameters
    const paramNames = pattern.match(/:[^/]+/g) || [];
    const params = {};
    paramNames.forEach((name, index) => {
      params[name.slice(1)] = match[index + 1];
    });

    return params;
  }
}

// Usage:
// const deepLinks = new DeepLinkManager();
// deepLinks.register('/product/:id', ({ id, query }) => {
//     console.log('Open product:', id, 'Tab:', query.tab);
// });
// deepLinks.register('/search', ({ query }) => {
//     console.log('Search for:', query.q);
// });
// deepLinks.process();

console.log('URL and History API examples loaded!');
console.log('These examples are designed for browser environments.');
Examples - JavaScript Tutorial | DeepML