Menu

Building a Simple CSV File Preview with Pure JavaScript

Building a Simple CSV File Preview with Pure JavaScript

Ever wanted to let users preview CSV files right in their browser without any complex libraries?

In this guide, we'll build a simple yet effective CSV file preview using nothing but vanilla JavaScript. Perfect for developers who want to understand the basics of file handling and data parsing in the browser.

What We're Building

We'll create a simple web page that allows users to:

  • Upload a CSV file
  • See its contents in a clean, formatted table
  • Preview up to 100 rows of data
  • Handle basic CSV formatting including quoted fields

CSV File Preview

Upload a CSV file to preview its contents

The HTML Structure

Let's start with a clean, minimal HTML structure:

<div class="container">
        <div class="upload-zone">
            <h2>CSV File Preview</h2>
            <p>Upload a CSV file to preview its contents</p>
            <input type="file" accept=".csv" id="fileInput" class="file-input">
        </div>
        <div id="info" class="info"></div>
        <div id="preview" class="preview-container"></div>
    </div>

We're keeping it simple with just an upload input and a preview container.

Making It Look Good

A touch of CSS makes everything look professional:

.container {
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.upload-zone {
    border: 2px dashed #ccc;
    padding: 30px;
    text-align: center;
    margin-bottom: 20px;
    border-radius: 8px;
    background: #fafafa;
    transition: all 0.3s ease;
}

.upload-zone:hover {
    border-color: #2196f3;
    background: #f0f7ff;
}

.file-input {
    margin: 15px 0;
}

.preview-container {
    overflow-x: auto;
    margin-top: 20px;
    border-radius: 4px;
    border: 1px solid #eee;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin: 0;
    background: white;
}

th, td {
    border: 1px solid #eee;
    padding: 12px;
    text-align: left;
}

th {
    background: #f8f9fa;
    font-weight: 600;
    color: #444;
}

tr:nth-child(even) {
    background-color: #fcfcfc;
}

tr:hover {
    background-color: #f5f5f5;
}

.error {
    color: #d32f2f;
    background: #ffebee;
    padding: 12px;
    border-radius: 4px;
    margin: 10px 0;
}

.info {
    margin: 10px 0;
    color: #666;
}

The JavaScript

Here's where the real work happens. Let's break it down into three main parts:

1. Handling File Upload

First off, we'll need to capture the 'change' event which fires whenever a file is selected in the given input element.

If the file ends with a '.csv' file type, then we're going to need to get the contents of the file, using a FileReader, and we're going to call the createTableFromCSV() function which is responsible for formatting the final output.

// Get DOM elements
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
const info = document.getElementById('info');
        
fileInput.addEventListener('change', function(e) {
    const file = e.target.files[0];
    if (!file) return;

    if (!file.name.endsWith('.csv')) {
        preview.innerHTML = '<div class="error">Please upload a CSV file</div>';
        return;
    }

    const reader = new FileReader();
    reader.onload = function(e) {
        const text = e.target.result;
        const table = createTableFromCSV(text);
        preview.innerHTML = '';
        preview.appendChild(table);
    };

    reader.readAsText(file);
});

2. Creating the Table

In order to create the HTML table we'll first need to split the CSV file contents into rows, and we can do that with a quick regular expression.

Once we have the rows split, the rest of the function simply dynamically creates a table element and populates it row by row.

function createTableFromCSV(csv) {
    const rows = csv.split(/\r?\n/);
    if (rows.length === 0) return;

    const table = document.createElement('table');
    
    // Create header from first row
    const headers = parseCSVRow(rows[0]);
    const thead = document.createElement('thead');
    const headerRow = document.createElement('tr');
    
    headers.forEach(header => {
        const th = document.createElement('th');
        th.textContent = header;
        headerRow.appendChild(th);
    });
    
    thead.appendChild(headerRow);
    table.appendChild(thead);

    // Add data rows
    const tbody = document.createElement('tbody');
    const maxRows = Math.min(rows.length, 100);
    
    for (let i = 1; i < maxRows; i++) {
        if (rows[i].trim() === '') continue;
        
        const rowData = parseCSVRow(rows[i]);
        const tr = document.createElement('tr');
        
        rowData.forEach(cell => {
            const td = document.createElement('td');
            td.textContent = cell;
            tr.appendChild(td);
        });
        
        tbody.appendChild(tr);
    }

    table.appendChild(tbody);
    return table;
}

3. Parsing CSV Rows

Let's break down the parseCSVRow function that handles individual CSV row parsing:

function parseCSVRow(row) {
    const cells = [];           // Store parsed cells
    let currentCell = '';       // Build current cell content
    let inQuotes = false;       // Track if we're inside quotes
    
    for (let i = 0; i < row.length; i++) {
        const char = row[i];
        
        if (char === '"') {
            if (inQuotes && row[i + 1] === '"') {
                // Handle escaped quotes ("") as single quote (")
                currentCell += '"';
                i++;
            } else {
                // Toggle quote state
                inQuotes = !inQuotes;
            }
        } else if (char === ',' && !inQuotes) {
            // End of cell found - but only if not in quotes
            cells.push(currentCell.trim());
            currentCell = '';
        } else {
            // Add character to current cell
            currentCell += char;
        }
    }
    
    // Don't forget the last cell
    cells.push(currentCell.trim());
    
    return cells;
}

How It Works

  1. State Tracking

    • cells: Array to store all parsed values
    • currentCell: Builds up the current cell's content
    • inQuotes: Boolean flag to track if we're inside quoted content
  2. Character Processing

    • Processes each character one at a time
    • Handles three special cases:
      • Quote characters (")
      • Commas (,)
      • Regular text
  3. Quote Handling

    • Regular quotes: Toggle the inQuotes state
    • Double quotes ("") inside quoted text: Convert to single quote
    • Everything inside quotes is treated as literal text
  4. Field Separation

    • Commas outside quotes mark the end of a field
    • Commas inside quotes are treated as regular text

Example Processing

Name,Age,"Job, Title",Notes
"John Smith",30,"Senior, Developer","Loves ""coding"" a lot"

When processing "John Smith",30,"Senior, Developer","Loves ""coding"" a lot":

  1. "John Smith" → John Smith
    • Quotes removed, content preserved
  2. 30 → 30
    • No special handling needed
  3. "Senior, Developer" → Senior, Developer
    • Comma inside quotes kept as text
  4. "Loves ""coding"" a lot" → Loves "coding" a lot
    • Double quotes converted to single

This parsing handles common CSV complexities like:

  • Quoted fields
  • Commas within quoted fields
  • Escaped quotes
  • Leading/trailing whitespace

Remember that this is a basic implementation. Professional CSV parsing libraries handle additional cases like:

  • Different line endings (CR, LF, CRLF)
  • Various field delimiters
  • Character encodings
  • Malformed data
  • Performance optimization

Summary

When a user uploads a file:

  1. We check if it's a CSV file
  2. Use FileReader to read the file contents
  3. Split the content into rows
  4. Create a table header from the first row
  5. Add up to 100 data rows to the table
  6. Parse each row carefully, handling quoted fields

Pro Tips

  • We limit to 100 rows for performance - loading huge files could slow down the browser
  • The CSV parser handles quoted fields, so cells containing commas work correctly
  • Error handling tells users if they try to upload non-CSV files
  • The table is styled for readability with alternating row colors

Limitations

Keep in mind this is a basic implementation:

  • Only handles comma-separated files
  • Doesn't support all possible CSV formats
  • No progress indicator for large files
  • Limited error handling

Conclusion

Building a CSV preview doesn't always require complex libraries. With just a few lines of vanilla JavaScript, we can create a functional and user-friendly CSV preview tool. This code serves as a great starting point for understanding file handling and data parsing in the browser.

Want to extend this further? Consider adding:

  • Drag and drop support
  • Column sorting
  • Search functionality
  • Full pagination
  • Progress indicator for large files

Happy coding!

Source Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple CSV Preview</title>
    <style>
        /* Clean, modern styling */
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            padding: 20px;
            max-width: 1200px;
            margin: 0 auto;
            background: #f7f7f7;
            color: #333;
        }

        .container {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .upload-zone {
            border: 2px dashed #ccc;
            padding: 30px;
            text-align: center;
            margin-bottom: 20px;
            border-radius: 8px;
            background: #fafafa;
            transition: all 0.3s ease;
        }

        .upload-zone:hover {
            border-color: #2196f3;
            background: #f0f7ff;
        }

        .file-input {
            margin: 15px 0;
        }

        .preview-container {
            overflow-x: auto;
            margin-top: 20px;
            border-radius: 4px;
            border: 1px solid #eee;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin: 0;
            background: white;
        }

        th, td {
            border: 1px solid #eee;
            padding: 12px;
            text-align: left;
        }

        th {
            background: #f8f9fa;
            font-weight: 600;
            color: #444;
        }

        tr:nth-child(even) {
            background-color: #fcfcfc;
        }

        tr:hover {
            background-color: #f5f5f5;
        }

        .error {
            color: #d32f2f;
            background: #ffebee;
            padding: 12px;
            border-radius: 4px;
            margin: 10px 0;
        }

        .info {
            margin: 10px 0;
            color: #666;
        }

        /* Responsive design */
        @media (max-width: 768px) {
            body {
                padding: 10px;
            }

            th, td {
                padding: 8px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="upload-zone">
            <h2>CSV File Preview</h2>
            <p>Upload a CSV file to preview its contents</p>
            <input type="file" accept=".csv" id="fileInput" class="file-input">
        </div>
        <div id="info" class="info"></div>
        <div id="preview" class="preview-container"></div>
    </div>

    <script>
        // Get DOM elements
        const fileInput = document.getElementById('fileInput');
        const preview = document.getElementById('preview');
        const info = document.getElementById('info');

        // Handle file upload
        fileInput.addEventListener('change', function(e) {
            const file = e.target.files[0];
            if (!file) return;

            // Validate file type
            if (!file.name.endsWith('.csv')) {
                preview.innerHTML = '<div class="error">Please upload a CSV file</div>';
                info.textContent = '';
                return;
            }

            // Read file
            const reader = new FileReader();

            reader.onload = function(e) {
                const text = e.target.result;
                const table = createTableFromCSV(text);
                
                if (table) {
                    preview.innerHTML = '';
                    preview.appendChild(table);
                    info.textContent = `Showing preview of ${file.name}`;
                } else {
                    preview.innerHTML = '<div class="error">Error: File appears to be empty</div>';
                    info.textContent = '';
                }
            };

            reader.onerror = function() {
                preview.innerHTML = '<div class="error">Error reading file</div>';
                info.textContent = '';
            };

            reader.readAsText(file);
        });

        // Create HTML table from CSV content
        function createTableFromCSV(csv) {
            // Split into rows and handle common delimiters
            const rows = csv.split(/\r?\n/);
            if (rows.length === 0) return null;

            const table = document.createElement('table');
            
            // Create table header
            const headers = parseCSVRow(rows[0]);
            const thead = document.createElement('thead');
            const headerRow = document.createElement('tr');
            
            headers.forEach(header => {
                const th = document.createElement('th');
                th.textContent = header;
                headerRow.appendChild(th);
            });
            
            thead.appendChild(headerRow);
            table.appendChild(thead);

            // Create table body
            const tbody = document.createElement('tbody');
            
            // Process only first 100 rows for performance
            const maxRows = Math.min(rows.length, 101); // +1 because first row is header
            
            // Start from 1 to skip header
            for (let i = 1; i < maxRows; i++) {
                if (rows[i].trim() === '') continue;
                
                const rowData = parseCSVRow(rows[i]);
                const tr = document.createElement('tr');
                
                rowData.forEach(cell => {
                    const td = document.createElement('td');
                    td.textContent = cell;
                    tr.appendChild(td);
                });
                
                tbody.appendChild(tr);
            }

            table.appendChild(tbody);
            return table;
        }

        // Parse a single CSV row handling quotes and commas
        function parseCSVRow(row) {
            const cells = [];
            let currentCell = '';
            let inQuotes = false;
            
            for (let i = 0; i < row.length; i++) {
                const char = row[i];
                
                if (char === '"') {
                    if (inQuotes && row[i + 1] === '"') {
                        // Handle escaped quotes
                        currentCell += '"';
                        i++;
                    } else {
                        // Toggle quote state
                        inQuotes = !inQuotes;
                    }
                } else if (char === ',' && !inQuotes) {
                    // End of cell
                    cells.push(currentCell.trim());
                    currentCell = '';
                } else {
                    currentCell += char;
                }
            }
            
            // Push the last cell
            cells.push(currentCell.trim());
            
            return cells;
        }
    </script>
</body>
</html>
Walter Guevara is a Computer Scientist, software engineer, startup founder and previous mentor for a coding bootcamp. He has been creating software for the past 20 years.
AD: "Heavy scripts slowing down your site? I use Fathom Analytics because it’s lightweight, fast, and doesn’t invade my users privacy." - Get $10 OFF your first invoice.

Community Comments

No comments posted yet

Code Your Own Classic Snake Game – The Right Way

Master the fundamentals of game development and JavaScript with a step-by-step guide that skips the fluff and gets straight to the real code.

Ad Unit

Current Poll

Help us and the community figure out what the latest trends in coding are.

Total Votes:
Q:
Submit

Add a comment