Read CSV with TypeScript

8 min read

Table of Contents:

Title Image for the Article

Introduction

In this tutorial, you will learn how to access a CSV file with HTML and TypeScript. We will use Snowpack as our tool to generate our files from TypeScript to JavaScript and create a build bundle that you can use in your next big app!

Data was generated by using an awesome tool called Mockaroo, visit their website at: https://www.mockaroo.com

The Setup

Let us start by opening the project folder. Ensure nothing else is in this folder. I am assuming you NodeJS installed and have the ability to access your terminal. I will be using Visual Studio Code. VS Code has a built in terminal feature that makes the process easier. If you are on Mac you will click terminal on the top bar (the equivalent button your OS) and select new terminal.

Once inside the terminal, you will type out:

npm init

Don't have NodeJS? Install it here!

Cannot Install Node

If you cannot install on your machine, or it is a school/work computer, you can follow along with the tutorial. The NPM/Snowpack/TypeScript is to help learn. It can be made in Vanilla JS.

Go through the options, the only thing I changed is the entry point from "index.js" to "app.js". Now lets create our folder structure:

Don't have Bootstrap 5.2? Install it here!

  • main_folder
    • src
      • app.ts: We create
      • bootstrap.min.css Downloaded from Bootstrap
      • bootstrap.min.css.map Downloaded from Bootstrap
    • index.html: We create
    • package-lock.json: auto generated
    • package.json: auto generated

Let us work on our HTML file to get everything connected and install Snowpack.

Snowpack

What is snowpack? Snowpack is a bundling tool much like Web-pack but with a couple differences. The main reason I have chosen it for my project is the simple install and run commands it offers. It is just an install and run (for basic projects). If you don't know what bundlers do, going through this project will give you a good idea.

Our HTML file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSV</title>
    <link rel="stylesheet" href="./src/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col">
                <h1>CSV File Uploader</h1>
            </div>
        </div>
        <div class="row">
            <div class="col-4"></div>
            <div class="col-4">
                <form id="csvForm">
                    <div class="mb-3">
                        <label for="csvFile" class="form-label">CSV File</label>
                        <input class="form-control" id="csvFile" type="file" accept=".csv"/>
                    </div>
                    <div class="mb-3">
                        <input class="btn btn-primary" type="submit" value="Submit" />
                    </div>
                </form>
            </div>
            <div class="col-4"></div>
        </div>
        <div class="row">
            <div class="col">
                <div id="displayArea">
                </div>
            </div>
        </div>
    </div>
    <script src="src/app.js"></script>
</body>
</html>

The classes on the files are used to decorate and make everything look nice with Bootstrap. The id's given to the form element ("csvForm") and div element ("displayArea") are used so we can easily get them in our TypeScript file.

Now that you have all of your files made and linked (through the link and script tags) let us run Snowpack!

Running Snowpack

Snowpack needs to be installed by running, in your terminal:

npm install --save-dev snowpack

--save-dev means it will be saved as a developer dependency, when it is complied, for production, its code will be thrown away.

Once the installation goes correctly, you will then run:

npx snowpack

npx is like an executable. This command will be ran once and it sets up the project to run with Snowpack. Once this command is successful then we run:

npm start

Package.json

In your package.json you will notice that Snowpack has added some tools for us that we just used. Under scripts you will see:

"scripts": {
"start": "snowpack dev",
"build": "snowpack build"
}

These allow us to use "start" and "build" to run the full commands of Snowpack. You can modify this file and add or remove custom scripts!

In the terminal you should see a running program and, if your OS allows it, it open a new browser tab with your site. Snowpack utilizes a live server which monitors file changes. When you change information in your files it will update the dev server with it.

Writing Our Script

If you are not able to run TypeScript, follow along with the tutorial but remove the typing (it will throw errors).

Grabbing the HTML Elements

const csvForm = document.getElementById('csvForm') as HTMLFormElement;
const csvFile = document.getElementById('csvFile') as HTMLInputElement;
const displayArea = document.getElementById('displayArea') as HTMLDivElement;

We are grabbing all the HTML items that are needed for our program. We are getting the form element, the file input and display area of where we want to show our information.

Declare Global Scoped Variables

For this file we will only need one!

let final_vals = [];

The variable is declared at a top level scope to ensure anything that needs access to it can. (It is the container for the entire project of information).

Functions

We just need one function:

const generate_table = async (arrayTable: [string[]]) : Promise<string> => {
    return `
        <table class="table table-striped">
            <thead>
                ${arrayTable[0].map(val => {
                    return `
                        <th scope="col">${val}</th>
                    `
                }).join('')}
            </thead>
            <tbody>
            ${arrayTable.map((val, index) => {
                if(index === 0) return;
                return `
                    <tr>
                        ${val.map(sub_val => {
                            return `
                                <td>${sub_val}</td>
                            `
                        }).join('')}
                    </tr>
                `
            }).join('')}
            </tbody>
        </table>
    `;
}

While it is only one function doing only a small amount of work there is a lot of formatting to it. We declare a function in the ES6 style, and we add a new word to it, "async". Async allows code to run to run on a separate track then the main event loop in JavaScript.

Have you ever gone to a website and while it is loading something it freezes? This can be avoided by using async functions. We are not handling a call to an external website; however, if the CSV file is thousands of records deep, it could lock up the website. Async is a mitigation tool for this and another issue we will see later on.

A Promise

A promise is the return type of an async function. It is like an agreement with the code that is going to run afterwards that it will return something. With TypeScript we are able to run a "then" function that does something with the format.

This function runs a couple map functions, once they are complete, it will return the entire format of file give to it. If an array of a couple thousand records is given to it, it can take a while to run (hence running in the background).

The Main Script

Now let us take everything and put it together in our main section of the program.

// Create an event listener for the form object
csvForm.addEventListener("submit", (e: Event) =>  {
    e.preventDefault(); // prevent HTML form submission
    let csvReader = new FileReader(); // generate a filereader from the JS API
    const input = csvFile.files[0]; // grab the first (only) file from the input
    // generating the function that will run on the action
    csvReader.onload = function(evt) {
        const text = evt.target.result; // this is the data generated from the csvReader reading the information in the file
        // Ensure the type of information from the file is a string
        if(typeof text === 'string' || text instanceof String) {
            const values = text.split(/[
]+/); // group the information by the CSV breakpoint 
 is a new line
            values.forEach(val => {
                // further split by each section by the CSV
                final_vals.push(val.split(','));
            });
            // create form
            generate_table(<[string[]]>final_vals)
                    .then(result => {
                    // async function is used to ensure the formatting does not try to occur after the table is created
                    displayArea.innerHTML = result;
                    const th_values = document.getElementsByTagName('th');
                    const td_values = document.getElementsByTagName('td');
                    const capitilize_table_column = (table_el: HTMLElement) => {
                        table_el.innerHTML = table_el.innerHTML[0].toUpperCase() + table_el.innerHTML.slice(1);
                    }
                    for (const th_val of th_values) {
                        capitilize_table_column(th_val);
                    }
                    for (const td_val of td_values) {
                        capitilize_table_column(td_val);
                    }
                });
        }
    }

Event Listener

  // Create an event listener for the form object
csvForm.addEventListener("submit", (e: Event) =>  {
    e.preventDefault(); // prevent HTML form submission

This is hooked to the form element within our form. We utilize this to stop the form from submitting and refreshing the page. When an event listener is fired it will give a parameter known as an event.

Internal Variable Setup

let csvReader = new FileReader(); // generate a filereader from the JS API
const input = csvFile.files[0]; // grab the first (only) file from the input

We are going to create two objects, one that holds a JavaScript API tool that reads files and the input from the file object.

Strings and Regex

Things can get a bit tricky here on what we are doing so I want to break it down to the lowest level. A CSV file using a delimiter (in our case a comma but it can be other objects) when a row is done in a CSV file it will use a special character that is \n. The \n means new line. We can use this as a tool to find out how many columns are CSV file has to generate dynamic content. We determine how many columns off of where that \n is used.

// generating the function that will run on the action
csvReader.onload = function(evt) {
    const text = evt.target.result; // this is the data generated from the csvReader reading the information in the file
    // Ensure the type of information from the file is a string
    if(typeof text === 'string' || text instanceof String) {
        const values = text.split(/[
]+/); // group the information by the CSV breakpoint 
 is a new line
        values.forEach(val => {
            // further split by each section by the CSV
            final_vals.push(val.split(','));
        });

The "onload" is a function that we declare to be used later with this object. The function we are making is given an object I have named "evt". On the first line I set a constant to the result of the file being read. The if statement is checking that the file is of type string and then runs the "split" function on it.

Split can either take a string (what I give it on the second split) or Regex object (what we give it on the first split). The Regex expression says when you find a new line object that is where you want to split the object. It will then create an array of strings that are broken up by their new line.

Then we separate everything by their commas into a subarray. We have now created a two dimensional array containing all rows and columns of data given.

Generate the Table

Now we create the code that will take the array, send it our formatting function, then format the table values to look nice (capitalize the first letter)

generate_table(<[string[]]>final_vals)
        .then(result => {
        // async function is used to ensure the formatting does not try to occur after the table is created
        displayArea.innerHTML = result;
        const th_values = document.getElementsByTagName('th');
        const td_values = document.getElementsByTagName('td');
        const capitilize_table_column = (table_el: HTMLElement) => {
            table_el.innerHTML = table_el.innerHTML[0].toUpperCase() + table_el.innerHTML.slice(1);
        }
        for (const th_val of th_values) {
            capitilize_table_column(th_val);
        }
        for (const td_val of td_values) {
            capitilize_table_column(td_val);
        }
    });

We run the function we made earlier. Then run the "then" function. Remember how we made that function async? We get access to this special function (then) that will run after it is done.

The final step is running the reader function:

csvReader.readAsText(input);

Why is this important?

JavaScript runs in an event loop. It will start executing a function and immediately move on to the next step. The code we have below relies on the events of the above function. If we ran this outside of the loop, it might work or it might not. Depending on the size of the file will determine if the table is made before the next lines are called.

Now that the table is made. We grab all the elements cut them apart and capitalize the first letter. It is done by creating an internal function than running all the elements through it.

Build with Snowpack

Now that we have it built, close the dev server (select the terminal and hit the key combo of CTRL+C or CMD+C). We will then run the build command:

npm run build

It will then create a build folder in your project that can be used in an application.

Conclusion

I hope this tutorial has helped you out, if you have any feedback please let know. Stay tuned for more content.

Recent Articles

Push Weight with Python

Create a simple terminal application that can let you know how much weight you need to put on the ba...

Validate Forms in TypeScript

Relying on HTML to validate your forms is just one step in ensuring visitors enter their information...

Automation with NodeJS

NodeJS can help you automate simple tasks. Being a web developer you can achieve the same goals as o...

Automation with Python

Python can take boring tasks away from you so you can continue to do other things. It is a scripting...

Speed With Cache in WordPress

Most WordPress sites use Apache as their server. Apache has a lot of tools built into it to speed up...

Social Media