Miva Merchant Development by Scot's Scripts

COPY TABLE JS: Copy Table Contents on Click

Miva Knowledge Base
COPY TABLE JS: Copy Table Contents on Click

WARNING:

This information is offered "AS IS" for internal reference only. Use at your own risk.
Does AI actually understand your Miva Merchant store? Our smart JSON-LD schema generator makes sure it does. Contact us to get started. (more info)

COPY TABLE JS: Copy Table Contents on Click

Scot Ranney • July 20, 2026


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

mvkb_copy mvkb_js mvkb_select