Get up to 80 % extra points for free! More info:

Lesson 11 - Finishing the table editor in JavaScript

In the previous lesson, Table editor in JavaScript, we created the core of a table editor. In today's JavaScript tutorial, we're going to complete this web application.

Active cell's row index

In order to practice orienting in DOM variables, we'll implement 2 functions which we're going to use later. One of them is an activeCellRowIndex() function, which returns the index of the row (zero-based) in which the user's selected cell is located (activeCell). First, you need to realize where we get this index. We'll look between the rows of the table. The table is represented by the <table> element and the rows are right in it (cells are in rows, which can be sometimes confusing, keep it in mind).

We're going to look into: table.childNodes

And to define what we're looking for will be a little more complicated. We're looking for a row, but we know that activeCell is not the <td> element but <input> which is in <td>. To access <td>, we need to get the parentElement out of the cell and even that's not enough for us.

We don't need the cell, but the row, and we'll get it using parentElement once more.

We're looking for: activeCell.parentElement.parentElement

NodeList and indexOf()

If you remember, in the previous lesson I mentioned that DOM manipulating functions never return an array, but a NodeList or similar specialized collections. And that these objects are only (practically) simpler arrays. Unfortunately, NodeList has no indexOf() method which would return the element's index. The're 2 solutions. The first is to iterate through it by ourselves and the second is to use the indexOf() method on the Array object and call it within the NodeList context. You'll understand the second part of the previous sentence more clearly in the object-oriented programming course. For now, we just need to know that we'll call indexOf() on Array, but we'll "smuggle" a NodeList to it. This works because JavaScript doesn't care what it's working with as long it provides the required interface which NodeList does. When we want to call a method with a different context, we'll find it in the object's prototype (in our case it's Array.prototype) and we call it using the call() method. As the first parameter, we pass the new context and then we pass the method's parameters.

The resulting code could look like this:

function activeCellRowIndex() {
    let searchLocation = table.childNodes;
    let elementToFind = activeCell.parentElement.parentElement;
    return Array.prototype.indexOf.call(searchLocation, elementToFind);
}

In searchLocation we have in what we're searching. In elementToFind we have what we're looking for. searchLocation is a NodeList and elementToFind is the <tr> element. Then we return the value returned by indexOf() with the context changed to the NodeList and with the <tr> element passed as it's regular parameter.

Active cell's column index

It'll be a little easier to get the selected cell's column index. We need to get to the row where the cell is located and get its position. Again, we'll get the cell using parentElement of the activeCell and the row that it belongs in using parentElement.parentElement.

function activeCellColumnIndex() {
    let rowCells = activeCell.parentElement.parentElement.childNodes;
    let td = activeCell.parentElement;
    return Array.prototype.indexOf.call(rowCells, td);
}

It may have been a bit boring so far, because nothing interesting happened. Now it's going to be more enjoyable. Finally, we're going to start programming the application control functions.

Inserting rows above

The method that creates a new row (the <tr> element) is already done. We can get the index of the row in which the selected cell is located, and we can also create a new row. For the sake of clarity, we'll store a new row into a variable, the row's index as well, and again we'll start manipulating DOM. The rows (<tr>) are nested directly in the table element (<table>), so it's going to be easy. We'll call the insertBefore() method on the table. The first parameter is clear, it's the new row. The second parameter must be the <tr> in which the selected cell is located.

There's no need to go further, rows are nested straight in the table, therefore we'll just select the row from the NodeList according to the active cells's row index.

function insertRowAbove() {
    let row = createRow();
    let activeIndex = activeCellRowIndex();
    table.insertBefore(row, table.childNodes[activeIndex]);
}

In the function that creates the control buttons, we'll add the onclick event handler to the "Insert row above" button.

addButton("Insert row above", document.body).onclick = insertRowAbove;

Now we're going to go a little deeper, however, it still won't be anything we haven't done yet.

Inserting rows below

Here we'd proceed practically the same. Unfortunatelly, there's no insertAfter() method in JavaScript so we'll have to insert the row before the current + 1 to get it at the right place. We should remember we need to prevent inserting the row this way after the last one since there would be no current + 1 row there. We'll use a condition to check whether the last element of the table (table.lastChild) is the row containing the selected cell. If it is, we just append the new row using the appendChild() method. Otherwise, we'll insert the row using insertBefore() before the row after the one containing the selected cell.

function insertRowBelow() {
    let row = createRow();
    let selectedIndex = activeCellRowIndex();
    if (table.lastChild == table.childNodes[selectedIndex]) {
        table.appendChild(row)
    } else {
        table.insertBefore(row, table.childNodes[selectedIndex + 1])
    }
}

Set the event handler to the appropriate button:

addButton("Insert row below", document.body).onclick = insertRowBelow;

Now we can add rows to the table as we want.

Table editor
localhost

Inserting columns to the left

Adding columns will be a little bit more complicated. Tables in HTML contain rows with cells in them. Hey, there're no columns in the code! We're going to have to add a column by adding a cell to each of the existing rows. We'll iterate through all the table rows and insert a new cell to each row before the index of the selected cell.

Now it's time to realize what is where and what we'll need to select. We'll insert elements into <tr> of the <table> element. The table.childNodes expression contains an array of <tr> elements and we need to use a loop to insert a new cell into each of them.

We're going to insert into: table.childNodes[i]

We're inserting a new cell.

We're inserting: createCell()

And before what are we inserting? The referential element (the one before which we're inserting) must be inside the element we're inserting into. We simply start by passing the same element we're inserting into. Since we know it's a child element, we're going to find it in the NodeList of the childNodes and we know these childNodes are an <td> array (Hooray! We're looking for these!). So the index (which is constant for the loop) is going to be the cell index in the rows. If you got lost in the description, try going through the code below.

We're inserting before: table.childNodes[i].childNodes[selectedIndex]

It'll all look as following:

function insertColumnLeft() {
    let selectedIndex = activeCellColumnIndex();
    for (let i = 0; i < table.childNodes.length; i++) {
        table.childNodes[i].insertBefore(createCell(), table.childNodes[i].childNodes[selectedIndex]);
    }
}

If you find it complicated or confusing, before you go into the next section (because it's going to be even more complex), add appropriate comments to your code or split the code into multiple variables with clear descriptive names.

Inserting columns to the right

As you probably know, it'll be similar, but we have to bypass the missing insertAfter() method again as we did in the insertRowBelow() method. The only difference (again) is that we have to check whether we're not inserting at the end. Logically, we'll always end up checking the element we're inserting with the last element of the parent of the element we're inserting.

function insertColumnRight() {
    let selectedIndex = activeCellColumnIndex();
    for (let i = 0; i < table.childNodes.length; i++) {
        if (table.childNodes[i].childNodes[selectedIndex] == table.childNodes[i].lastElementChild) {
            table.childNodes[i].appendChild(createCell());
        } else {
            table.childNodes[i].insertBefore(createCell(), table.childNodes[i].childNodes[selectedIndex + 1]);
        }
    }
}

Let's add event handlers to the buttons and try it out.

addButton("Insert column to left", document.body).onclick = insertColumnLeft;
addButton("Insert column to right", document.body).onclick = insertColumnRight;

Try it out. After hard work, you can play the Gumoku game in your table. Simply insert a lot of columns and rows :)

Table editor
localhost

We may encounter a problem while using the application. The table may have more rows or columns than we wanted. We'll implement removing columns and rows.

Removing rows

We'll remove a row from the table. We'll get the appropriate element to remove by taking all elements of the table (childNodes) and selecting the one with the index of the selected cell.

function removeRow() {
    let selectedIndex = activeCellRowIndex();
    table.removeChild(table.childNodes[selectedIndex]);
}

Removing columns

We'll remove a column by removing a cell in each row. The cell's index will be the index of the selected cell.

function removeColumn() {
    let selectedIndex = activeCellColumnIndex();
    for (let i = 0; i < table.childNodes.length; i++) {
        table.childNodes[i].removeChild(table.childNodes[i].childNodes[selectedIndex]);
    }
}

We'll make the buttons work and we're done.

addButton("Remove row", document.body).onclick = removeRow;
addButton("Remove column", document.body).onclick = removeColumn;

After this example, you should already be able to work with DOM in JavaScript. All the magic from creating the table to the Gumoku game was just playing with a few elements that we'd normally write in HTML. Notice that at the beginning of everything, we have HTML code that does not have a single element in its <body>.

In the next lesson, More on Conditions in JavaScript, we'll work with images.

In the next lesson, More on Conditions in JavaScript, we will deepen our knowledge of data types and show other constructs for creating conditions.


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 31x (2.19 kB)
Application includes source codes in language JavaScript

 

Previous article
Table editor in JavaScript
All articles in this section
JavaScript Basic Constructs
Skip article
(not recommended)
More on Conditions in JavaScript
Article has been written for you by Michal Zurek
Avatar
User rating:
2 votes
Activities