javascript
examples
examples.js⚡javascript
/**
* ========================================
* 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.');