Created
November 18, 2023 06:38
-
-
Save erikyo/d2bf7d72b76a09975553a5dd0d3740df to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Browser Security Test</title> | |
<!-- Google Fonts --> | |
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"> | |
<!-- CSS Reset --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css"> | |
<!-- Milligram CSS --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css"> | |
<script defer src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<style> | |
body { | |
margin: 0; | |
padding: 0 2rem 4rem; | |
} | |
.wrapper { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
padding-top: 5vh; | |
} | |
#securityChart { | |
max-width: 500px; | |
height: 50vh; | |
} | |
.chart-wrapper { | |
display: flex; | |
justify-content: center; | |
padding: 36px; | |
} | |
.chart-wrapper td { | |
display: block; | |
width: auto; | |
max-width: 50%; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
} | |
</style> | |
</head> | |
<body> | |
<main class="wrapper"> | |
<h1>Browser Security Test Results</h1> | |
<h2 id="test-response" class='align-center'></h2> | |
<div class='chart-wrapper'> | |
<canvas id="securityChart"></canvas> | |
</div> | |
<div id="results"></div> | |
<div id="table-container"></div> | |
<script> | |
// the data table results | |
let dataTableResults = []; | |
// the chart data | |
let testResults = {}; // collection of test results | |
function checkSecurity(results) { | |
// CacheStorage | |
displayResult('CacheStorage', 'caches' in window); | |
// Cookie support | |
displayResult('Cookie (HTTP)', navigator.cookieEnabled); | |
displayResult('Cookie (JS)', 'cookieStore' in window); | |
// IndexedDB | |
displayResult('IndexedDB', 'indexedDB' in window); | |
// LocalStorage | |
displayResult('LocalStorage', 'localStorage' in window); | |
// WebSQL Database | |
displayResult('WebSQL Database', 'openDatabase' in window); | |
// XMLHttpRequest | |
displayResult('XMLHttpRequest', 'XMLHttpRequest' in window); | |
// ServiceWorker | |
displayResult('ServiceWorker', 'serviceWorker' in navigator); | |
// HTTPS support | |
displayResult('HTTPS', window.location.protocol === 'https:'); | |
// ECH support | |
displayResult('ECH Enabled', 'encrypt' in window.crypto.subtle); | |
// IP address leak | |
displayResult('IP Address Leak', 'RTCPeerConnection' in window); | |
// Alt-Svc | |
displayResult('Alt-Svc', 'AltSvc' in window); | |
// Blob | |
displayResult('Blob', 'Blob' in window); | |
// BroadcastChannel | |
displayResult('BroadcastChannel', 'BroadcastChannel' in window); | |
// Cache API | |
displayResult('Cache API', 'caches' in window); | |
// HSTS cache | |
displayResult('HSTS Cache', 'securityPolicy' in document); | |
// iframe cache | |
displayResult('iframe Cache', 'HTMLIFrameElement' in window); | |
// Image cache | |
displayResult('Image Cache', 'createImageBitmap' in window); | |
// Navigation tests | |
displayResult('Navigation tests', 'referrerPolicy' in document.createElement('a')); | |
// document.referrer | |
displayResult('document.referrer', 'referrer' in document); | |
// sessionStorage | |
displayResult('sessionStorage', 'sessionStorage' in window); | |
// window.name | |
displayResult('window.name', 'name' in window); | |
// HTTPS tests | |
displayResult('Insecure website', location.protocol === 'http:'); | |
// Upgradable address | |
displayResult('Upgradable address', 'upgrade' in location); | |
// Upgradable hyperlink | |
displayResult('Upgradable hyperlink', 'upgrade' in document.createElement('a')); | |
// Upgradable image | |
displayResult('Upgradable image', 'upgrade' in document.createElement('img')); | |
// Upgradable script | |
displayResult('Upgradable script', 'upgrade' in document.createElement('script')); | |
// Misc tests | |
displayResult('ECH enabled', 'echConfig' in navigator); | |
displayResult('GPC enabled first-party', 'globalPrivacyControl' in navigator); | |
displayResult('GPC enabled third-party', 'globalPrivacyControl' in navigator); | |
displayResult('IP address leak', 'RTCPeerConnection' in window); | |
displayResult('Stream isolation – Tor enabled', 'mozAnon' in window); | |
// Fingerprinting resistance tests | |
displayResult('Media query screen height', 'matchMedia' in window && matchMedia('(min-height: 1px)').matches); | |
displayResult('Media query screen width', 'matchMedia' in window && matchMedia('(min-width: 1px)').matches); | |
displayResult('outerHeight', 'outerHeight' in window); | |
displayResult('screen.height', 'screen' in window && 'height' in screen); | |
displayResult('screen.width', 'screen' in window && 'width' in screen); | |
displayResult('screenX', 'screen' in window && 'screenX' in screen); | |
displayResult('screenY', 'screen' in window && 'screenY' in screen); | |
displayResult('System font detection', 'fonts' in document); | |
// Tracking query parameter tests | |
const urlParametersToRemove = ['__hsfp', '__hssc', '__hstc', '__s', '_hsenc', '_openstat', 'dclid', 'fbclid', 'gclid', 'hsCtaTracking', 'mc_eid', 'mkt_tok', 'ml_subscriber', 'ml_subscriber_hash', 'msclkid', 'oly_anon_id', 'oly_enc_id', 'rb_clickid', 's_cid', 'vero_conv', 'vero_id', 'wickedid', 'yclid']; | |
displayResult('Remove tracking URL parameters', urlParametersToRemove.every(param => !(param in new URLSearchParams(window.location.search)))); | |
// Tracker content blocking tests | |
const trackingScripts = ['Adobe', 'Adobe Audience Manager', 'Amazon adsystem', 'AppNexus', 'Bing Ads', 'Chartbeat', 'Criteo', 'DoubleClick (Google)', 'Facebook tracking', 'Google (third-party ad pixel)', 'Google Analytics', 'Google Tag Manager', 'Index Exchange', 'New Relic', 'Quantcast', 'Scorecard Research', 'Beacon', 'Taboola', 'Twitter pixel', 'Yandex Ads']; | |
displayResult('Block tracking scripts', trackingScripts.every(script => !document.querySelector(`script[src*="${script}"]`))); | |
// Tracking cookie protection tests | |
const trackingCookies = ['Adobe', 'Adobe Audience Manager', 'Amazon adsystem', 'AppNexus', 'Bing Ads', 'Chartbeat', 'Criteo', 'DoubleClick (Google)', 'Facebook tracking', 'Google (third-party ad pixel)', 'Google Analytics', 'Google Tag Manager', 'Index Exchange', 'New Relic', 'Quantcast', 'Scorecard Research', 'Beacon', 'Taboola', 'Twitter pixel', 'Yandex Ads']; | |
displayResult('Block tracking cookies', trackingCookies.every(cookie => !document.cookie.includes(cookie))); | |
// Cross-session first-party tracking tests | |
displayResult('Cross-session first-party tracking', 'sessionStorage' in window && sessionStorage.getItem('firstPartyTracking') === null); | |
// Cross-session third-party tracking tests | |
displayResult('Cross-session third-party tracking', 'localStorage' in window && localStorage.getItem('thirdPartyTracking') === null); | |
// Misc tests (continued) | |
displayResult('Fingerprinting resistance tests (continued)', 'navigator' in window && 'platform' in navigator && navigator.platform === ''); | |
displayResult('Tracking query parameter tests (continued)', 'URLSearchParams' in window && new URLSearchParams('?test=value').has('test')); | |
displayResult('Tracker content blocking tests (continued)', 'crypto' in window && 'subtle' in crypto && 'digest' in crypto.subtle); | |
displayResult('Tracking cookie protection tests (continued)', 'cookieStore' in window && 'get' in cookieStore); | |
// Cross-session first-party tracking tests (continued) | |
if ('sessionStorage' in window) { | |
sessionStorage.setItem('firstPartyTracking', 'true'); | |
displayResult('Cross-session first-party tracking (continued)', sessionStorage.getItem('firstPartyTracking') === 'true'); | |
sessionStorage.removeItem('firstPartyTracking'); | |
} | |
// Cross-session third-party tracking tests (continued) | |
if ('localStorage' in window) { | |
localStorage.setItem('thirdPartyTracking', 'true'); | |
displayResult('Cross-session third-party tracking (continued)', localStorage.getItem('thirdPartyTracking') === 'true'); | |
localStorage.removeItem('thirdPartyTracking'); | |
} | |
// Language | |
const userLanguage = window.navigator.language || window.navigator.userLanguage; | |
displayResult('User Language', userLanguage); | |
// Other user-related information | |
displayResult('User Agent', window.navigator.userAgent); | |
displayResult('Platform', window.navigator.platform); | |
// **Cookie-related checks** | |
// Check for cookie presence | |
displayResult('Cookie Presence', document.cookie !== ''); | |
// **IndexedDB-related checks** | |
// Check for IndexedDB database access | |
displayResult('IndexedDB Database Access', indexedDB.open !== undefined); | |
// Check for IndexedDB database versioning | |
displayResult('IndexedDB Database Versioning', indexedDB.open !== undefined && indexedDB.open.onupgradeneeded !== undefined); | |
// **LocalStorage-related checks** | |
// Check for LocalStorage usage | |
displayResult('LocalStorage Usage', localStorage.length > 0); | |
// Check for LocalStorage items access | |
displayResult('LocalStorage Items Access', localStorage.getItem !== undefined); | |
// **WebSQL Database-related checks** | |
// Check for WebSQL Database usage | |
displayResult('WebSQL Database Usage', window.indexedDB || window.webkitIndexedDB); | |
// **XMLHttpRequest-related checks** | |
// Check for XMLHttpRequest usage | |
displayResult('XMLHttpRequest Usage', window.XMLHttpRequest); | |
// Check for XMLHttpRequest CORS headers | |
displayResult('XMLHttpRequest CORS Headers', window.XMLHttpRequest.withCredentials !== undefined); | |
// **ServiceWorker-related checks** | |
// Check for ServiceWorker support | |
displayResult('ServiceWorker Support', navigator.serviceWorker); | |
// Check for ServiceWorker registration | |
displayResult('ServiceWorker Registration', navigator.serviceWorker.register); | |
// **HTTPS-related checks** | |
// Check for HTTPS strict transport security (HSTS) | |
displayResult('HSTS', navigator.securityPolicy && navigator.securityPolicy.includes('strict-transport-security') ); | |
// Check for HTTPS content type options (CTO) | |
displayResult('CTO', document.contentType.startsWith('text/html') && document.contentType.includes('; charset=UTF-8')); | |
// **ECH-related checks** | |
// Check for ECH support (encrypted client hello) | |
displayResult('ECH Support (Continued)', window.crypto.subtle.encrypt !== undefined && window.crypto.subtle.decrypt !== undefined); | |
// Check for ECH key exchange mechanism | |
displayResult('ECH Key Exchange', 'ECDH' in window.crypto.subtle); | |
// **IP address leak checks** | |
// Check for IP address leak (RTCPeerConnection) | |
displayResult('IP Address Leak (Continued)', new RTCPeerConnection().createDataChannel !== undefined); | |
// **Alt-Svc checks** | |
// Check for Alt-Svc header content | |
displayResult('Alt-Svc Content', document.head.outerHTML.includes('h2o')); | |
// **Blob checks** | |
// Check for Blob usage | |
displayResult('Blob Usage', Blob !== undefined); | |
// Check for Blob slicing | |
displayResult('Blob Slicing', Blob.prototype.slice !== undefined); | |
// **BroadcastChannel checks** | |
// Check for BroadcastChannel usage | |
displayResult('BroadcastChannel Usage', BroadcastChannel !== undefined); | |
// Check for BroadcastChannel message posting | |
displayResult('BroadcastChannel Message Posting', BroadcastChannel.prototype.postMessage !== undefined); | |
// **Cache API checks** | |
// Check for Cache API usage | |
displayResult('Cache API (Continued)', caches !== undefined); | |
// Check for Cache API storage | |
displayResult('Cache API Storage', caches.open !== undefined); | |
// Check for screen resolution | |
displayResult('Screen Resolution', window.screen.width !== undefined && window.screen.height !== undefined); | |
// Check for color depth | |
displayResult('Color Depth', window.screen.colorDepth !== undefined); | |
// Check for pixel density | |
displayResult('Pixel Density', window.devicePixelRatio !== undefined); | |
// Check for available fonts | |
displayResult('Available Fonts', document.fonts !== undefined && document.fonts.length > 0); | |
// Check for installed plugins | |
displayResult('Installed Plugins', navigator.plugins !== undefined && navigator.plugins.length > 0); | |
// Check for time zone offset | |
displayResult('Time Zone Offset', new Date().getTimezoneOffset() !== undefined); | |
// Check for user agent string | |
displayResult('User Agent String', navigator.userAgent !== undefined); | |
// Check for browser language | |
displayResult('Browser Language', navigator.language !== undefined && navigator.language !== ''); | |
// Check for browser version | |
displayResult('Browser Version', navigator.appVersion !== undefined && navigator.appVersion !== ''); | |
// Check for browser vendor | |
displayResult('Browser Vendor', navigator.vendor !== undefined && navigator.vendor !== ''); | |
// Check for browser rendering engine | |
displayResult('Browser Rendering Engine', navigator.appName !== undefined && navigator.appName !== ''); | |
// Check for CPU architecture | |
displayResult('CPU Architecture', navigator.cpuClass !== undefined && navigator.cpuClass !== ''); | |
// Check for operating system | |
displayResult('Operating System', navigator.oscpu !== undefined && navigator.oscpu !== ''); | |
// Check for network connection type | |
displayResult('Network Connection Type', navigator.connection !== undefined && navigator.connection.type !== undefined); | |
// Check for browser cookie support | |
displayResult('Browser Cookie Support', navigator.cookieEnabled !== undefined); | |
// Check for browser JavaScript support | |
displayResult('Browser JavaScript Support', navigator.javaEnabled !== undefined); | |
// Check for browser geolocation support | |
displayResult('Browser Geolocation Support', navigator.geolocation !== undefined); | |
// Check for browser touch screen support | |
displayResult('Browser Touch Screen Support', navigator.maxTouchPoints !== undefined && navigator.maxTouchPoints > 0); | |
// Check for browser accessibility features | |
displayResult('Browser Accessibility Features', navigator.onLine !== undefined); | |
// Check for browser speech recognition support | |
displayResult('Browser Speech Recognition Support', navigator.mediaDevices.getUserMedia !== undefined); | |
// Check for browser hardware concurrency | |
displayResult('Browser Hardware Concurrency', navigator.hardwareConcurrency !== undefined); | |
// Check for browser rendering buffer size | |
displayResult('Browser Rendering Buffer Size', navigator.deviceMemory !== undefined && navigator.deviceMemory > 0); | |
// Check for browser full screen support | |
displayResult('Browser Full Screen Support', document.fullscreenEnabled !== undefined); | |
// Check for browser web worker support | |
displayResult('Browser Web Worker Support', window.Worker !== undefined); | |
// Check for browser shared worker support | |
displayResult('Browser Shared Worker Support', window.SharedWorker !== undefined); | |
// Check for browser web storage support | |
displayResult('Browser Web Storage Support', window.localStorage !== undefined && window.sessionStorage !== undefined); | |
// Check for browser indexed DB support | |
displayResult('Browser Indexed DB Support', window.indexedDB !== undefined); | |
// Check for browser WebSQL support | |
displayResult('Browser WebSQL Support', window.openDatabase !== undefined); | |
// Check for browser application cache support | |
displayResult('Browser Application Cache Support', window.applicationCache !== undefined); | |
// Check for browser offline support | |
displayResult('Browser Offline Support', window.applicationCache && window.applicationCache.addEventListener !== undefined); | |
// Check for browser performance navigation timing support | |
displayResult('Browser Performance Navigation Timing Support', window.performance !== undefined && window.performance.getEntries !== undefined); | |
// Check for browser timing API support | |
displayResult('Browser Timing API Support', window.performance.now !== undefined && window.performance.timing !== undefined); | |
// Check for browser memory API support | |
displayResult('Browser Memory API Support', window.performance.memory !== undefined); | |
// Check for browser navigation timing API support | |
displayResult('Browser Navigation Timing API Support', window.performance.getEntriesByType('navigation') !== undefined); | |
// **Device-related checks** | |
// Check for device model | |
displayResult('Device Model', navigator.userAgentData.mobile !== undefined && navigator.userAgentData.mobile.model !== undefined); | |
// Check for device type | |
displayResult('Device Type', navigator.userAgentData.mobile !== undefined && navigator.userAgentData.mobile.type !== undefined); | |
// Check for device brand | |
displayResult('Device Brand', navigator.userAgentData.mobile !== undefined && navigator.userAgentData.mobile.brand !== undefined); | |
// Check for device platform | |
displayResult('Device Platform', navigator.userAgentData.platform !== undefined && navigator.userAgentData.platform.toLowerCase() !== 'unknown'); | |
// Check for device architecture | |
displayResult('Device Architecture', navigator.userAgentData.platform !== undefined && navigator.userAgentData.platformArchitecture !== undefined); | |
// Check for device OS version | |
displayResult('Device OS Version', navigator.userAgentData.platform !== undefined && navigator.userAgentData.platformVersion !== undefined); | |
// Check for device battery level | |
displayResult('Device Battery Level', navigator.getBattery !== undefined); | |
// Check for CPU architecture | |
displayResult('CPU Architecture', navigator.cpuClass !== undefined && navigator.cpuClass !== ''); | |
// Check for CPU cores | |
displayResult('CPU Cores', navigator.hardwareConcurrency !== undefined); | |
// **Memory checks** | |
// Check for total system memory | |
displayResult('Total System Memory', navigator.deviceMemory !== undefined && navigator.deviceMemory > 0); | |
// Check for available system memory | |
displayResult('Available System Memory', navigator.memory && navigator.memory.available !== undefined); | |
// **Media device checks** | |
// Check for audio input devices | |
displayResult('Audio Input Devices', navigator.mediaDevices.enumerateDevices().then(devices => devices.filter(device => device.kind === 'audioinput'))); | |
// Check for audio output devices | |
displayResult('Audio Output Devices', navigator.mediaDevices.enumerateDevices().then(devices => devices.filter(device => device.kind === 'audiooutput'))); | |
// Check for video input devices | |
displayResult('Video Input Devices', navigator.mediaDevices.enumerateDevices().then(devices => devices.filter(device => device.kind === 'videoinput'))); | |
// Check for screen sharing support | |
displayResult('Screen Sharing Support', navigator.mediaDevices.getDisplayMedia !== undefined); | |
// Check for device accelerometer support | |
displayResult('Device Accelerometer Support', window.DeviceOrientationEvent !== undefined && window.DeviceOrientationEvent.prototype.addEventListener !== undefined); | |
// Check for device gyroscope support | |
displayResult('Device Gyroscope Support', window.DeviceOrientationEvent !== undefined && window.DeviceOrientationEvent.prototype.addEventListener !== undefined); | |
// Check for device light sensor support | |
displayResult('Device Light Sensor Support', window.AmbientLightSensor !== undefined && window.AmbientLightSensor.prototype.addEventListener !== undefined); | |
// Check for device proximity sensor support | |
displayResult('Device Proximity Sensor Support', window.ProximitySensor !== undefined && window.ProximitySensor.prototype.addEventListener !== undefined); | |
// **WebRTC-related checks** | |
// Check for WebRTC support | |
displayResult('WebRTC Support', RTCPeerConnection !== undefined); | |
// Check for STUN server support | |
displayResult('STUN Server Support', navigator.connection.getSTUNServer !== undefined); | |
// Check for TURN server support | |
displayResult('TURN Server Support', navigator.connection.getTURNServer !== undefined); | |
// Check for WebRTC media streaming support | |
displayResult('WebRTC Media Streaming Support', navigator.mediaDevices.getUserMedia !== undefined); | |
// **Misc checks** | |
// Check for screen orientation | |
displayResult('Screen Orientation', window.screen.orientation !== undefined && window.screen.orientation.type !== undefined); | |
// Check for default font family | |
displayResult('Default Font Family', document.body.style.fontFamily !== undefined && document.body.style.fontFamily !== ''); | |
// Check for user preference for reduced motion | |
displayResult('User Preference for Reduced Motion', window.matchMedia('prefers-reduced-motion').matches !== undefined); | |
// Check for user preference for high contrast | |
displayResult('User Preference for High Contrast', window.matchMedia('prefers-contrast').matches !== undefined); | |
// Check for user preference for dark theme | |
displayResult('User Preference for Dark Theme', window.matchMedia('prefers-color-scheme: dark').matches !== undefined); | |
} | |
// Function to display test results | |
const displayResult = (testName, result) => { | |
const status = result ? '☑️' : '📛'; | |
// Add the result and status to the dataTableResults object | |
dataTableResults.push({ | |
testName: testName, | |
result: result, | |
status: status, | |
}); | |
// Save the test result | |
testResults[testName] = result; | |
}; | |
// Function to calculate security score based on test results | |
function calculateSecurityScore(results) { | |
const totalTests = Object.keys(results).length; | |
const successfulTests = Object.values(results).filter(result => result === true).length; | |
return (successfulTests / totalTests) * 100; | |
} | |
document.addEventListener("DOMContentLoaded", () => { | |
checkSecurity(); | |
const resultsContainer = document.getElementById('results'); | |
// Security Chart | |
const ctx = document.getElementById('securityChart').getContext('2d'); | |
const result = calculateSecurityScore(testResults); | |
var BROWSER = new Array( | |
["Microsoft Edge", /edg/i], | |
["Microsoft Internet Explorer", /trident/i], | |
["Mozilla Firefox", /firefox|fxios/i], | |
["Opera", /opr\//i], | |
["UC Browser", /ucbrowser/i], | |
["Samsung Browser", /samsungbrowser/i], | |
["Google Chrome", /chrome|chromium|crios/i], | |
["Apple Safari", /safari/i], | |
["Unknown", /.+/i], | |
).find(([, value]) => value.test(window.navigator.userAgent)).shift(); | |
document.querySelector('#test-response').innerHTML = BROWSER + " - Score " + Math.round(result * 100) / 100; | |
const securityData = { | |
labels: ['Security', 'Insecurity'], | |
datasets: [{ | |
data: [result, 100 - result], | |
backgroundColor: ['#302b42', '#ef233c'], | |
}], | |
}; | |
const securityOptions = { | |
responsive: true, | |
maintainAspectRatio: false, | |
legend: { | |
position: 'bottom', | |
}, | |
title: { | |
display: true, | |
text: 'Browser Security Score', | |
}, | |
}; | |
const securityChart = new Chart(ctx, { | |
type: 'doughnut', | |
data: securityData, | |
options: securityOptions, | |
}); | |
// Function to create a sortable table | |
function createSortableTable(data) { | |
// Create table element | |
const table = document.createElement('table'); | |
table.id = 'sortable-table'; | |
// Create table header | |
const thead = document.createElement('thead'); | |
const headerRow = document.createElement('tr'); | |
const sortDirection = {}; // Object to store the sorting direction for each column | |
Object.keys(data[0]).forEach(key => { | |
const th = document.createElement('th'); | |
th.textContent = key; | |
th.addEventListener('click', () => sortTable(key, tbody, sortDirection)); | |
headerRow.appendChild(th); | |
}); | |
thead.appendChild(headerRow); | |
table.appendChild(thead); | |
// Create table body | |
const tbody = document.createElement('tbody'); | |
data.forEach(item => { | |
const row = document.createElement('tr'); | |
Object.values(item).forEach(value => { | |
const td = document.createElement('td'); | |
td.textContent = value; | |
row.appendChild(td); | |
}); | |
tbody.appendChild(row); | |
}); | |
table.appendChild(tbody); | |
// Append the table to the container | |
tableContainer.innerHTML = ''; | |
tableContainer.appendChild(table); | |
// Initial sorting by the 'Test Name' column | |
sortTable('testName', tbody, sortDirection); | |
// Function to sort the table by a specific column | |
function sortTable(column, tbody, sortDirection) { | |
const isNumeric = !isNaN(data[0][column]); | |
// Toggle sorting direction | |
sortDirection[column] = sortDirection[column] === 'asc' ? 'desc' : 'asc'; | |
data.sort((a, b) => { | |
const aValue = String(isNumeric ? parseFloat(a[column]) : a[column].toLowerCase()); | |
const bValue = String(isNumeric ? parseFloat(b[column]) : b[column].toLowerCase()); | |
if (sortDirection[column] === 'asc') { | |
return aValue > bValue ? 1 : (aValue < bValue ? -1 : 0); | |
} else { | |
return aValue < bValue ? 1 : (aValue > bValue ? -1 : 0); | |
} | |
}); | |
// Remove existing rows | |
while (tbody.firstChild) { | |
tbody.removeChild(tbody.firstChild); | |
} | |
// Add sorted rows | |
data.forEach(item => { | |
const row = document.createElement('tr'); | |
Object.values(item).forEach(value => { | |
const td = document.createElement('td'); | |
td.textContent = value; | |
row.appendChild(td); | |
}); | |
tbody.appendChild(row); | |
}); | |
// Update arrow indicators | |
updateArrowIndicators(); | |
} | |
// Function to update arrow indicators | |
function updateArrowIndicators() { | |
const headerCells = thead.querySelectorAll('th'); | |
headerCells.forEach(cell => { | |
const columnName = cell.textContent; | |
const indicator = sortDirection[columnName]; | |
// Remove existing arrow indicators | |
cell.innerHTML = columnName; | |
// Add arrow indicator if sorting | |
if (indicator) { | |
const arrow = document.createElement('span'); | |
arrow.textContent = indicator === 'asc' ? ' ▲' : ' ▼'; | |
cell.appendChild(arrow); | |
} | |
}); | |
} | |
} | |
// Call the function with the sample data | |
// the table container | |
const tableContainer = document.getElementById('table-container'); | |
createSortableTable(dataTableResults); | |
}); | |
</script> | |
</main> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment