Lesson 10 - Table editor in JavaScript

JavaScript Basics Table editor in JavaScript

In the previous lesson, DOM manipulation in JavaScript, we learned how to work with DOM and change the webpage contents. We already know everything we need to create a true web application. In today's tutorial, we're going to program a table editor as an example on which we're going to learn a few new things.

The table is a very interesting and specific website element. The table has rows (<tr>) and cells (<td>). <td> belong to <tr> and <tr> belong to <table> if the table doesn't have a more complex structure.

HTML

Let's create a page, a CSS file, and a JS file. We'll import the CSS and JS files in the <head> and our HTML page is done. We can leave the <body> element empty since we're going to generate all the content from the script. This approach is even better for us because if we decide to put the application on a different page, we won't have to modify its HTML code at all. Since we're going to work on this project, let's make our codes match. The HTML page should look like this. Notice the filenames.

<!DOCTYPE html>
<html lang="en">
<head>
        <title>Table editor</title>
        <meta charset="utf-8" />
        <script src="table-editor.js"></script>
        <link href="table-editor.css" rel="stylesheet" />
</head>
<body>

</body>
</html>

JavaScript

We'll create three variables in the table-editor.js file. The table variable will contain the table itself and then we'll have the variables defaultSizeX and defaultSizeY for the default table size. We'll leave table without a value and set some 2 reasonable numbers to the size variables. For example:

let table;
let defaultSizeX = 5;
let defaultSizeY = 3;

Later, we're going to need one more variable, but let's not bother with it now. We'll create functions to generate control buttons and the table itself. We'll create the function to generatel control buttons later.

The function to generate the table will be named generateDefaultTable(). It'll create a <table> element and append it to the <body>. We'll generate its cells using 2 nested loops. To make the cells editable, we'll insert classic text <input>s into them. Because we're going to need to create cells multiple times in this application, we'll create a createCell() function to do so. It'll create the <td> and <input> elements. It'll insert the <input> into the <td> and return the cell. Since we're going to program functions which assume there's a selected cell in the table, we'll store the selected cell somewhere when it's selected. There's the focus event which is triggered when the user selects an element, no matter if by clicking on it or using tab. Therefore, when creating a cell, we'll handle its focus event as well and store the cell to the activeCell variable (declared above the function) if the cell is selected. It's important to remember that the this keyword will contain the element which triggered the event in the event's callback. So we'll store this to the activeCell variable (it'll contain the <input> element because this element will trigger the event).

let activeCell;

function createCell() {
        let td = document.createElement("td");

        let tdInput = document.createElement("input");

        tdInput.type = "text";
        tdInput.onfocus = function () {
                activeCell = this;
        }
        td.appendChild(tdInput);

        return td;
}

Let's get back to the function that generates the default table. First, we're going to create the table itself.

function generateDefaultTable() {
        table = document.createElement("table");
        document.body.appendChild(table);
}

We'll store the newly created <table> to the table variable and append it into the <body> element. Note that if we want to get to <body>, we don't have to get use methods such as getElementsByTagName(), but the element is stored in the document.body property.

Now, let's focus on the loops. First, we'll create the <tr> elements (the Y loop) and append each new row to the table (it's always better to append the element as soon as we can to avoid forgetting to do it later). Then we'll create the row cells using a nested loop and append them to the row. We'll use the createCell() method which we've created just a while ago. We'll set the upper bounds of the loops to the defaultSizeX and defaultSizeY variables so the correct number of rows and cells is generated.

function generateDefaultTable() {
        table = document.createElement("table");
        document.body.appendChild(table);
        for (let y = 0; y < defaultSizeY; y++) {
                let tr = document.createElement("tr");
                table.appendChild(tr);

                for (let x = 0; x < defaultSizeX; x++) {
                        tr.appendChild(createCell());
                }
        }
}

The core of the app is done now. Let's call the function in the load event handler of the window object:

window.onload = function () {
        generateDefaultTable();
}

This kind of event handling should already be well known to you. It's an anonymous function assigned to the event handler of the event which is triggered when the page is loaded. When you start the app, you'll see a table (probably with no borders) and the inputs in it.

Table editor
localhost

CSS

Congratulations, you created your first application that generated a table. Before we proceed to the next part, let's polish the table a little bit to make it look better. I won't describe the following CSS. If you don't understand any part of it, check our manuals for a detailed explanation.

table {
        border-spacing:0;
        border: 1px solid black;
        width: 500px;
}
table, table td {
        padding:0;
        margin: 0;
}
table td {
        border: 1px solid black;
}
table td input {
        padding:0;
        margin: 0;
        width: 100%;
        border: 0px solid transparent;
        height: 100%;
}

The table will look like this:

Table editor
localhost

Adding features

We'll now implement features to our editor. We're going to do it step by step. To make our users able to use the features, we'll have to add some controls for them. Actually, I wanted to say we're going to need buttons :), but theoretically, we could use some other control elements as well.

Because there is going to be multiple features, it'd make no sense to write ...createElement("button") all over again. Therefore, we'll create the addButton() function for it. The function will accept 2 parameters - label and parent. It'll create a <button>, set its text, append it to the parent and return it. We only return the button to be able to set its event handler. Theoretically, we could pass the callback (the handler) through the third parameter, however, the source code might become confusing.

function addButton(label, parent) {
        let button = document.createElement("button");
        button.textContent = label;
        parent.appendChild(button);
        return button;
}

Now, we'll create concrete buttons for each feature. The goal of this project is to program the functions to insert a column and a row according to the selected cell (we have it stored in the activeCell variable) and to remove a column and a row. We'll call the method to create a button in the function. We don't even have to store the buttons since we can assign the event handler straight to the return value. For now, we'll keep the buttons as follows:

function generateControlButtons() {
        addButton("Insert row below", document.body);
        addButton("Insert row above", document.body);
        addButton("Insert column to left", document.body);
        addButton("Insert column to right", document.body);
        addButton("Remove row", document.body);
        addButton("Remove column", document.body);
}

We'll call the function in the window.onload event handler:

window.onload = function () {
        generateControlButtons();
        generateDefaultTable();
}
Table editor
localhost

Adding rows

Let's start with the simplest feature - adding a row. Because we'll have two editor features which somehow create a row and insert it somewhere, we'll write a function that creates the <tr> element and inserts as many cells into it as is in some of the existing rows. It'll be easiest to take the number of cells right from the first row.

function createRow() {
        let newRow = document.createElement("tr")

        for (let i = 0; i < table.firstElementChild.childNodes.length; i++) {
                newRow.appendChild(createCell());
        }
        return newRow;
}

It's important to realize what is stored in which variable. As the maximum value for the i loop is table.firstElementChild.childNodes.length, at first glance, we can only say that it has something to do with the table and the length. The rest of the expression is not that clear. It's ideal if we use comments to describe complex expressions such as this one or if we use multiple variables for the expression parts to make it more readable.

The <table> element contains <tr> elements. So we know that table.firstElementChild is the <tr> element. childNodes are all the <td> elements, so an array of them. And length carries the length of that array. This results in a fact that table.firstElementChild.childNodes.length contains the number of cells of the first table row. It's only up to you if you use multiple variables to make the expression more clear:

let firstRow = table.firstElementChild;
let firstRowCells = firstRow.childNodes;
let firstRowCellsCount = firstRowCells.length;

Or you can use a simple comment block to note what the expression means, so you'd be able to read it when you look at your code a year later:

/*
 * table = <TABLE>
 * table.firstElementChild = <TR>
 * table.firstElementChild.childNodes = [<TD>]
 * table.firstElementChild.childNodes.length = number
 *
 * table.       firstElementChild.      childNodes      .length
 * <TABLE>.     <TR>.                   [<TD>]          .length
 */

If you want, you can try practicing orientation in the code, like trying to figure out what's going to happen if we have the following condition (we're really going to add it in the next lesson):

if (table.childNodes[i].childNodes[indexOfSelected] == table.childNodes[i].lastElementChild) { }

We'll continue in the next lesson, Finishing the table editor in JavaScript.


 

Download

Downloaded 2x (1.78 kB)
Application includes source codes in language JavaScript

 

 

Article has been written for you by Michal Zurek
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Activities (7)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!