modern-table-js
Version:
Modern, lightweight, vanilla JavaScript table library with zero dependencies. 67% faster than DataTables with mobile-first responsive design.
285 lines (249 loc) • 8.13 kB
JavaScript
/**
* Export utilities for ModernTable.js
*/
/**
* Download file helper
*/
export function downloadFile(content, mimeType, filename) {
const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
/**
* Get table data for export
*/
export function getExportData(table, options = {}) {
const columns = options.columns || table.options.columns.map((_, index) => index);
const data = table.data || [];
// Get headers
const headers = columns.map(colIndex => {
const column = table.options.columns[colIndex];
return column ? column.title : '';
}).filter(Boolean);
// Get rows data
const rows = data.map(rowData => {
return columns.map(colIndex => {
const column = table.options.columns[colIndex];
if (!column) return '';
let cellValue = table.getCellValue(rowData, column.data);
// Apply render function but strip HTML for export
if (column.render && typeof column.render === 'function') {
cellValue = column.render(cellValue, 'export', rowData);
}
// Strip HTML tags for clean export
if (typeof cellValue === 'string') {
cellValue = cellValue.replace(/<[^>]*>/g, '').trim();
}
return cellValue || '';
});
});
return { headers, rows };
}
/**
* Format data for CSV
*/
export function formatCSV(data) {
const lines = [];
// Add headers
if (data.headers && data.headers.length > 0) {
const csvHeaders = data.headers.map(header => `"${String(header).replace(/"/g, '""')}"`);
lines.push(csvHeaders.join(','));
}
// Add data rows
data.rows.forEach(row => {
const csvRow = row.map(cell => {
const cellStr = String(cell || '');
const escaped = cellStr.replace(/"/g, '""');
return `"${escaped}"`;
});
lines.push(csvRow.join(','));
});
return lines.join('\n');
}
/**
* Format data for Excel (XML format)
*/
export function formatExcel(data) {
let xml = '<?xml version="1.0"?>\n';
xml += '<?mso-application progid="Excel.Sheet"?>\n';
xml += '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n';
xml += ' xmlns:o="urn:schemas-microsoft-com:office:office"\n';
xml += ' xmlns:x="urn:schemas-microsoft-com:office:excel"\n';
xml += ' xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n';
xml += ' xmlns:html="http://www.w3.org/TR/REC-html40">\n';
xml += '<Worksheet ss:Name="Sheet1">\n<Table>\n';
// Add headers
if (data.headers && data.headers.length > 0) {
xml += '<Row>\n';
data.headers.forEach(header => {
xml += `<Cell><Data ss:Type="String">${escapeXml(String(header))}</Data></Cell>\n`;
});
xml += '</Row>\n';
}
// Add data rows
data.rows.forEach(row => {
xml += '<Row>\n';
row.forEach(cell => {
const cellValue = String(cell || '');
const isNumber = !isNaN(cellValue) && !isNaN(parseFloat(cellValue)) && cellValue.trim() !== '';
const dataType = isNumber ? 'Number' : 'String';
xml += `<Cell><Data ss:Type="${dataType}">${escapeXml(cellValue)}</Data></Cell>\n`;
});
xml += '</Row>\n';
});
xml += '</Table>\n</Worksheet>\n</Workbook>';
return xml;
}
/**
* Format data for print
*/
export function formatPrint(data, title = 'Table Data') {
let html = `
<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f5f5f5; font-weight: bold; }
tr:nth-child(even) { background-color: #f9f9f9; }
@media print {
body { margin: 0; }
table { font-size: 12px; }
}
</style>
</head>
<body>
<h1>${title}</h1>
<table>
`;
// Add headers
if (data.headers && data.headers.length > 0) {
html += '<thead><tr>';
data.headers.forEach(header => {
html += `<th>${escapeHtml(String(header))}</th>`;
});
html += '</tr></thead>';
}
// Add data rows
html += '<tbody>';
data.rows.forEach(row => {
html += '<tr>';
row.forEach(cell => {
html += `<td>${escapeHtml(String(cell || ''))}</td>`;
});
html += '</tr>';
});
html += '</tbody>';
html += `
</table>
<p style="margin-top: 20px; font-size: 12px; color: #666;">
Generated on ${new Date().toLocaleString()}
</p>
</body>
</html>
`;
return html;
}
/**
* Copy data to clipboard
*/
export async function copyToClipboard(data) {
const text = formatClipboard(data);
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.warn('Clipboard API failed, using fallback');
}
}
// Fallback for older browsers
return copyToClipboardFallback(text);
}
/**
* Format data for clipboard (tab-separated)
*/
function formatClipboard(data) {
const lines = [];
// Add headers
if (data.headers && data.headers.length > 0) {
lines.push(data.headers.join('\t'));
}
// Add data rows
data.rows.forEach(row => {
lines.push(row.map(cell => String(cell || '')).join('\t'));
});
return lines.join('\n');
}
/**
* Fallback clipboard copy for older browsers
*/
function copyToClipboardFallback(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
const successful = document.execCommand('copy');
document.body.removeChild(textarea);
return successful;
} catch (err) {
document.body.removeChild(textarea);
return false;
}
}
/**
* Escape XML characters
*/
function escapeXml(text) {
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Escape HTML characters
*/
function escapeHtml(text) {
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Show notification
*/
export function showNotification(message, type = 'success') {
// Create notification element
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Auto remove after 3 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}