THIS CODE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED;
THE AUTHOR DISCLAIMS ALL LIABILITY FOR ANY DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR STORE DISRUPTIONS ARISING FROM ITS USE.
Simple vanilla js to copy the contents of a table based on ID via click. Include a little toast that lets you know it worked.
<script>
/**
* copyTableToClipboard.js
*
* Copies the contents of a <table> (including <thead> and <tbody> rows)
* to the clipboard as tab-separated values, so it can be pasted directly
* into a spreadsheet with columns intact. Shows a brief confirmation
* toast anchored just below whatever element triggered the copy.
*
* Usage:
* <button onclick="copyTableToClipboard('report', this)">Copy Table</button>
*
* Where 'report' is the id of the <table> element, and `this` is the
* clicked element itself -- that's what lets the toast anchor to it.
* If you leave off the second argument, the toast falls back to
* bottom-center of the page instead of erroring.
*/
(function () {
'use strict';
var TOAST_ID = 'copy-table-toast';
var TOAST_DURATION = 2200; // ms before toast fades out
var TOAST_GAP = 8; // px between the trigger element and the toast
var toastEl = null;
var hideTimer = null;
/* ── Toast styles + element, created lazily on first use ────────── */
function ensureToast() {
if (toastEl) {
return;
}
var css = [
'#' + TOAST_ID + '{',
' position:fixed;',
' background:#2c1f0e;', /* --carto-ink */
' color:#f5edd8;', /* --carto-parchment */
' font-family:"Libre Baskerville",Georgia,serif;',
' font-size:0.82rem;',
' letter-spacing:0.04em;',
' padding:0.65rem 1.25rem;',
' border:1px solid #8b6e3c;', /* --carto-gold */
' border-radius:2px;',
' box-shadow:0 4px 18px rgba(0,0,0,0.35);',
' opacity:0;',
' pointer-events:none;',
' transition:opacity 0.2s ease;',
' z-index:9999;',
' white-space:nowrap;',
'}',
'#' + TOAST_ID + '.show{',
' opacity:1;',
'}'
].join('\n');
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
toastEl = document.createElement('div');
toastEl.id = TOAST_ID;
document.body.appendChild(toastEl);
}
/* ── Position the toast just under the trigger element, clamped
so it doesn't run off either edge of the viewport ─────────── */
function positionNear(anchorEl) {
var anchorRect = anchorEl.getBoundingClientRect();
var toastRect = toastEl.getBoundingClientRect();
var left = anchorRect.left + (anchorRect.width / 2) - (toastRect.width / 2);
var top = anchorRect.bottom + TOAST_GAP;
var maxLeft = window.innerWidth - toastRect.width - TOAST_GAP;
if (left > maxLeft) {
left = maxLeft;
}
if (left < TOAST_GAP) {
left = TOAST_GAP;
}
toastEl.style.transform = 'none';
toastEl.style.bottom = 'auto';
toastEl.style.left = left + 'px';
toastEl.style.top = top + 'px';
}
/* ── Fallback if no trigger element was passed in ────────────────── */
function positionFallback() {
toastEl.style.top = 'auto';
toastEl.style.left = '50%';
toastEl.style.bottom = '1.5rem';
toastEl.style.transform = 'translateX(-50%)';
}
function showToast(message, anchorEl) {
ensureToast();
toastEl.textContent = message;
if (anchorEl) {
positionNear(anchorEl);
} else {
positionFallback();
}
clearTimeout(hideTimer);
toastEl.classList.add('show');
hideTimer = setTimeout(function () {
toastEl.classList.remove('show');
}, TOAST_DURATION);
}
/* ── Main entry point ────────────────────────────────────────────── */
window.copyTableToClipboard = function (tableId, triggerEl) {
var table = document.getElementById(tableId);
if (!table) {
return;
}
var rows = table.querySelectorAll('tr');
var lines = [];
rows.forEach(function (row) {
var cells = row.querySelectorAll('th, td');
var line = [];
cells.forEach(function (cell) {
var text = cell.innerText || cell.textContent || '';
// Tabs/newlines inside a cell would break the column
// structure on paste, so collapse them to a single space.
text = text.replace(/\t/g, ' ').replace(/\r?\n/g, ' ').trim();
line.push(text);
});
lines.push(line.join('\t'));
});
var tsv = lines.join('\n');
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(tsv).then(function () {
showToast('Table copied to clipboard', triggerEl);
}).catch(function () {
showToast('Copy failed -- please try again', triggerEl);
});
} else {
// Fallback for older browsers / non-secure contexts
var textarea = document.createElement('textarea');
textarea.value = tsv;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
var ok = document.execCommand('copy');
document.body.removeChild(textarea);
showToast(ok ? 'Table copied to clipboard' : 'Copy failed -- please try again', triggerEl);
}
};
})();
</script>
https://www.scotsscripts.com/mvblog/copy-table-js-copy-table-contents-on-click.html