git / excel-file-reader-editor.html
KEXEL's picture
1.1
6b87022 verified
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XLSX File Reader & Editor</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1/font/bootstrap-icons.css">
<!-- XLSX Library -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<!-- FileSaver.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<style>
.dropzone {
border: 2px dashed #0d6efd;
transition: all 0.3s ease;
cursor: pointer;
}
.dropzone.active {
border-color: #198754;
background-color: rgba(25, 135, 84, 0.05);
}
.table-container {
/*max-height: 70vh;*/
overflow-y: auto;
}
.editable-cell {
min-width: 120px;
border: 1px solid #dee2e6;
padding: 0.5rem;
}
.editable-cell:focus {
outline: 2px solid #0d6efd;
background-color: rgba(13, 110, 253, 0.1);
}
.sheet-tab {
cursor: pointer;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
margin-right: 0.5rem;
background-color: #f8f9fa;
transition: all 0.2s ease;
}
.sheet-tab:hover {
background-color: #e9ecef;
}
.sheet-tab.active {
background-color: #0d6efd;
color: white;
}
.file-info {
background-color: #f8f9fa;
border-radius: 0.375rem;
padding: 1rem;
}
.how-to-use-card {
height: 100%;
}
</style>
</head>
<body class="bg-light">
<div class="container py-5">
<div class="text-center mb-5">
<h1 class="display-5 fw-bold text-dark mb-3">Excel File Reader & Editor</h1>
<p class="lead text-muted">Upload, view, edit and export Excel files directly in your browser</p>
</div>
<div class="shadow-sm mb-5">
<div class="">
<div class="row">
<!-- File Upload Section -->
<div class="col-md">
<div id="dropzone" class="dropzone rounded-0 p-3 text-center mb-4">
<div class="d-flex flex-column align-items-center justify-content-center">
<i class="bi bi-file-earmark-excel text-primary fs-1 mb-3"></i>
<p class="text-muted mb-2">Drag & drop your Excel file here</p>
<p class="text-muted small mb-3">or</p>
<!--<label for="fileInput" class="btn btn-primary">
<i class="bi bi-upload me-2"></i> Browse Files
</label>-->
<input type="file" id="fileInput" class="d-none" accept=".xlsx, .xls, .csv" />
</div>
</div>
</div>
<div class="col-md-3">
<h5 class="fw-bold mb-3">File Information</h5>
<div class="file-info">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-file-earmark text-muted me-2"></i>
<span id="fileName" class="text-dark">No file selected</span>
</div>
<div class="d-flex align-items-center mb-2">
<i class="bi bi-collection text-muted me-2"></i>
<span id="sheetCount" class="text-dark">0 sheets</span>
</div>
<div class="d-flex align-items-center">
<i class="bi bi-table text-muted me-2"></i>
<span id="rowCount" class="text-dark">0 rows</span>
</div>
</div>
</div>
<!-- Sheet Navigation -->
<div class="col-ms">
<div id="sheetTabs" class="d-flex overflow-auto py-2 mb-3">
<!-- Sheets will be added here dynamically -->
</div>
<!-- Table Container -->
<div id="tableContainer" class="table-container border rounded-0 overflow-hidden">
<div class="text-center py-5 text-muted" id="emptyState">
<i class="bi bi-file-earmark-excel display-4 opacity-25 mb-3"></i>
<p class="h5">Upload an Excel file to get started</p>
</div>
<table id="dataTable" class="table table-bordered m-0 d-none overflow-auto">
<!-- Table content will be generated dynamically -->
</table>
</div>
<!-- Action Buttons -->
<div class="d-flex flex-wrap gap-2 mt-4">
<button id="exportBtn" class="btn btn-success" disabled>
<i class="bi bi-download me-2"></i> Export Excel
</button>
<button id="addRowBtn" class="btn btn-primary" disabled>
<i class="bi bi-plus me-2"></i> Add Row
</button>
<button id="addColBtn" class="btn btn-primary" disabled>
<i class="bi bi-plus me-2"></i> Add Column
</button>
<button id="resetBtn" class="btn btn-secondary">
<i class="bi bi-arrow-counterclockwise me-2"></i> Reset
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Instructions -->
<div class="card shadow-sm">
<div class="card-body p-4">
<h2 class="h4 fw-bold mb-4">How to use</h2>
<div class="row g-4">
<div class="col-md-4">
<div class="card bg-primary bg-opacity-10 how-to-use-card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="bg-primary bg-opacity-25 p-2 rounded-circle me-3">
<i class="bi bi-upload text-primary"></i>
</div>
<h3 class="h6 fw-bold mb-0 text-primary">Upload</h3>
</div>
<p class="card-text text-muted">Drag & drop or click to upload your Excel file (.xlsx, .xls, .csv)</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-success bg-opacity-10 how-to-use-card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="bg-success bg-opacity-25 p-2 rounded-circle me-3">
<i class="bi bi-pencil text-success"></i>
</div>
<h3 class="h6 fw-bold mb-0 text-success">Edit</h3>
</div>
<p class="card-text text-muted">Click on any cell to edit its content. Add/remove rows and columns as needed.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-info bg-opacity-10 how-to-use-card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="bg-info bg-opacity-25 p-2 rounded-circle me-3">
<i class="bi bi-download text-info"></i>
</div>
<h3 class="h6 fw-bold mb-0 text-info">Export</h3>
</div>
<p class="card-text text-muted">Download your edited file back to Excel format when you're done.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap 5 JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
// DOM Elements
const fileInput = document.getElementById("fileInput");
const dropzone = document.getElementById("dropzone");
const fileName = document.getElementById("fileName");
const sheetCount = document.getElementById("sheetCount");
const rowCount = document.getElementById("rowCount");
const tableContainer = document.getElementById("tableContainer");
const dataTable = document.getElementById("dataTable");
const emptyState = document.getElementById("emptyState");
const sheetTabs = document.getElementById("sheetTabs");
const exportBtn = document.getElementById("exportBtn");
const addRowBtn = document.getElementById("addRowBtn");
const addColBtn = document.getElementById("addColBtn");
const resetBtn = document.getElementById("resetBtn");
// Global variables
let workbook = null;
let currentSheetName = "";
let sheetData = [];
let headers = [];
// Event Listeners
fileInput.addEventListener("change", handleFileSelect);
dropzone.addEventListener("click", () => fileInput.click());
dropzone.addEventListener("dragover", handleDragOver);
dropzone.addEventListener("dragleave", handleDragLeave);
dropzone.addEventListener("drop", handleDrop);
exportBtn.addEventListener("click", exportToExcel);
addRowBtn.addEventListener("click", addNewRow);
addColBtn.addEventListener("click", addNewColumn);
resetBtn.addEventListener("click", resetApp);
// Functions
function handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
processExcelFile(file);
}
function handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
dropzone.classList.add("active");
}
function handleDragLeave(e) {
e.preventDefault();
e.stopPropagation();
dropzone.classList.remove("active");
}
function handleDrop(e) {
e.preventDefault();
e.stopPropagation();
dropzone.classList.remove("active");
const file = e.dataTransfer.files[0];
if (!file) return;
processExcelFile(file);
}
function processExcelFile(file) {
fileName.textContent = file.name;
const reader = new FileReader();
reader.onload = function (e) {
const data = new Uint8Array(e.target.result);
workbook = XLSX.read(data, { type: "array" });
updateSheetInfo();
renderSheetTabs();
loadFirstSheet();
enableButtons();
};
reader.readAsArrayBuffer(file);
}
function updateSheetInfo() {
if (!workbook) return;
sheetCount.textContent = `${workbook.SheetNames.length} sheet${
workbook.SheetNames.length !== 1 ? "s" : ""
}`;
}
function renderSheetTabs() {
if (!workbook) return;
sheetTabs.innerHTML = "";
workbook.SheetNames.forEach((sheetName) => {
const tab = document.createElement("div");
tab.className = "sheet-tab";
tab.textContent = sheetName;
tab.addEventListener("click", () => switchSheet(sheetName));
if (sheetName === workbook.SheetNames[0]) {
tab.classList.add("active");
}
sheetTabs.appendChild(tab);
});
}
function switchSheet(sheetName) {
if (!workbook || currentSheetName === sheetName) return;
// Update active tab
document.querySelectorAll(".sheet-tab").forEach((tab) => {
tab.classList.remove("active");
if (tab.textContent === sheetName) {
tab.classList.add("active");
}
});
currentSheetName = sheetName;
loadSheetData();
renderTable();
}
function loadFirstSheet() {
if (!workbook || workbook.SheetNames.length === 0) return;
currentSheetName = workbook.SheetNames[0];
loadSheetData();
renderTable();
}
function loadSheetData() {
if (!workbook || !currentSheetName) return;
const worksheet = workbook.Sheets[currentSheetName];
sheetData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "" });
if (sheetData.length > 0) {
headers = sheetData[0];
rowCount.textContent = `${sheetData.length - 1} row${
sheetData.length - 1 !== 1 ? "s" : ""
}`;
} else {
headers = [];
rowCount.textContent = "0 rows";
}
}
function renderTable() {
if (!sheetData || sheetData.length === 0) {
emptyState.classList.remove("d-none");
dataTable.classList.add("d-none");
return;
}
emptyState.classList.add("d-none");
dataTable.classList.remove("d-none");
// Clear existing table
dataTable.innerHTML = "";
// Create header row
const thead = document.createElement("thead");
const headerRow = document.createElement("tr");
headerRow.className = "table-light";
// Add empty cell for corner
const cornerCell = document.createElement("th");
cornerCell.className = "text-center";
cornerCell.style.padding = "0px 45px";
headerRow.appendChild(cornerCell);
// Add column headers
headers.forEach((header, colIndex) => {
const th = document.createElement("th");
th.className = "text-nowrap";
th.textContent = header || `Column ${colIndex + 1}`;
// Add delete column button
const deleteBtn = document.createElement("button");
deleteBtn.className = "btn btn-sm btn-link text-danger ms-2 p-0";
deleteBtn.innerHTML = '<i class="bi bi-x"></i>';
deleteBtn.addEventListener("click", () => deleteColumn(colIndex));
th.appendChild(deleteBtn);
headerRow.appendChild(th);
});
// Add new column header
const newColHeader = document.createElement("th");
newColHeader.className = "text-center";
newColHeader.style.width = "50px";
const addColBtn = document.createElement("button");
addColBtn.className = "btn btn-sm btn-link text-primary p-0";
addColBtn.innerHTML = '<i class="bi bi-plus"></i>';
addColBtn.addEventListener("click", addNewColumn);
newColHeader.appendChild(addColBtn);
headerRow.appendChild(newColHeader);
thead.appendChild(headerRow);
dataTable.appendChild(thead);
// Create table body
const tbody = document.createElement("tbody");
// Add data rows
for (let rowIndex = 1; rowIndex < sheetData.length; rowIndex++) {
const row = document.createElement("tr");
// Add row number cell
const rowNumCell = document.createElement("td");
rowNumCell.className = "text-center text-muted";
rowNumCell.textContent = rowIndex;
// Add delete row button
const deleteRowBtn = document.createElement("button");
deleteRowBtn.className = "btn btn-sm btn-link text-danger ms-2 p-0";
deleteRowBtn.innerHTML = '<i class="bi bi-x"></i>';
deleteRowBtn.addEventListener("click", () => deleteRow(rowIndex));
rowNumCell.appendChild(deleteRowBtn);
row.appendChild(rowNumCell);
// Add data cells
for (let colIndex = 0; colIndex < headers.length; colIndex++) {
const cell = document.createElement("td");
cell.className = "editable-cell";
cell.contentEditable = true;
// Set cell value (handle cases where row might be shorter than headers)
const cellValue =
sheetData[rowIndex][colIndex] !== undefined
? sheetData[rowIndex][colIndex]
: "";
cell.textContent = cellValue;
// Add event listener to save changes
cell.addEventListener("blur", () => {
if (!sheetData[rowIndex]) {
sheetData[rowIndex] = new Array(headers.length).fill("");
}
sheetData[rowIndex][colIndex] = cell.textContent;
});
row.appendChild(cell);
}
// Add empty cell for new column
const newColCell = document.createElement("td");
newColCell.className = "text-center";
row.appendChild(newColCell);
tbody.appendChild(row);
}
// Add new row button
const newRow = document.createElement("tr");
const newRowCell = document.createElement("td");
newRowCell.colSpan = headers.length + 2;
newRowCell.className = "text-center py-3";
const addRowBtn = document.createElement("button");
addRowBtn.className = "btn btn-sm btn-primary";
addRowBtn.innerHTML = '<i class="bi bi-plus me-2"></i>Add Row';
addRowBtn.addEventListener("click", addNewRow);
newRowCell.appendChild(addRowBtn);
newRow.appendChild(newRowCell);
tbody.appendChild(newRow);
dataTable.appendChild(tbody);
}
function addNewRow() {
if (!sheetData) return;
const newRow = new Array(headers.length).fill("");
sheetData.push(newRow);
rowCount.textContent = `${sheetData.length - 1} row${
sheetData.length - 1 !== 1 ? "s" : ""
}`;
renderTable();
}
function addNewColumn() {
if (!headers) return;
const newHeader = `Column ${headers.length + 1}`;
headers.push(newHeader);
// Add empty value for the new column in each row
for (let i = 0; i < sheetData.length; i++) {
sheetData[i].push("");
}
renderTable();
}
function deleteRow(rowIndex) {
if (!sheetData || rowIndex < 1 || rowIndex >= sheetData.length) return;
sheetData.splice(rowIndex, 1);
rowCount.textContent = `${sheetData.length - 1} row${
sheetData.length - 1 !== 1 ? "s" : ""
}`;
renderTable();
}
function deleteColumn(colIndex) {
if (!headers || colIndex < 0 || colIndex >= headers.length) return;
headers.splice(colIndex, 1);
// Remove the column from each row
for (let i = 0; i < sheetData.length; i++) {
if (sheetData[i].length > colIndex) {
sheetData[i].splice(colIndex, 1);
}
}
renderTable();
}
function exportToExcel() {
if (!workbook || !currentSheetName) return;
// Update the current sheet with edited data
const worksheet = XLSX.utils.aoa_to_sheet(sheetData);
workbook.Sheets[currentSheetName] = worksheet;
// Generate Excel file
const excelBuffer = XLSX.write(workbook, {
bookType: "xlsx",
type: "array"
});
const blob = new Blob([excelBuffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
});
// Suggest a filename
let exportName = "edited_" + (fileName.textContent || "spreadsheet");
if (!exportName.endsWith(".xlsx")) {
exportName += ".xlsx";
}
// Save the file
saveAs(blob, exportName);
}
function resetApp() {
// Reset UI
fileName.textContent = "No file selected";
sheetCount.textContent = "0 sheets";
rowCount.textContent = "0 rows";
emptyState.classList.remove("d-none");
dataTable.classList.add("d-none");
sheetTabs.innerHTML = "";
// Reset data
workbook = null;
currentSheetName = "";
sheetData = [];
headers = [];
// Disable buttons
exportBtn.disabled = true;
addRowBtn.disabled = true;
addColBtn.disabled = true;
// Reset file input
fileInput.value = "";
}
function enableButtons() {
exportBtn.disabled = false;
addRowBtn.disabled = false;
addColBtn.disabled = false;
}
});
</script>
</body>
</html>