Tabular Data

Using tables for data

Displaying data in a table

A common way of adding features to a web application is to use tables to display data that is important to an application. Regular HTML tables are fine for just displaying the tables, but making the tables dynamic makes them more useful. This could be as simple as sorting the table by a column, or selecting some data to be viewed and/or edited.

We will start with a very simple web page again, and then slowly build to make use of a table. Then, we will look at some common ways to make use of dynamic tables. Here is the first version of the file, "using_tables.html":

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <script>
         addEventListener('DOMContentLoaded', init);

         function init() {
            console.log('init called');
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
   </body>
</html>

As you can see, this page does not do much. But, it does make sure that the init() function will be called after the HTML markup is completed loading. It is purposely simple to remind you of the bare minimum that goes into a web page that uses JavaScript.

Here is a screen shot showing "using_tables.html" with the DevTools panel open. Remember that hitting the F12 key will toggle the DevTools panel on/off.

view1

A simple HTML table

We want to eventually make dynamic tables. But, before we get to that, let’s start to set up a very basic table so that we have a fundamental understanding of what goes into a table.

We will start off with just some data in JSON (JavaScript Object Notation) format. This is how objects in JavaScript are stored. Here is the new version of "using_tables.html":

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <script>
         addEventListener('DOMContentLoaded', init);
         data = [
            { 'firstName': 'Jane', 'lastName': 'Doe', 'major': 'ICS'},
            { 'firstName': 'John', 'lastName': 'Smith', 'major': 'MATH'},
            { 'firstName': 'Bob', 'lastName': 'Data', 'major': 'CHEM'},
         ];
         function init() {
            console.log(data);
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
   </body>
</html>

The new lines are lines 6-10 and line 12. Lines 6-10 define a JSON object called data. You can see that data consists of an array of three objects. Each of those three objects has key:value pairs that represent properties of those objects.

Line 12 just prints data to the console. Here is a screenshot of "using_tables.html" with the DevTools panel open:

view2

This will give you an idea of how useful console.log() can be. If you click on the triangle to expand the view, you will see the following:

view3

As you can see, using console.log() to display an object allows you to see the objects within that object without having to use a for loop. This can help you to write the code to access properties of a complex object.

For example, you can type directly into the console to verify that you can access a property like a student’s major like this:

view4

That is, to get the second object in the array’s major, you use data[1].major

Placing the data into a HTML table

Now, let’s modify "using_tables.html" to display the data in a HTML table. Here is the new version of that file:

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <script>
         addEventListener('DOMContentLoaded', init);
         data = [
            { 'firstName': 'Jane', 'lastName': 'Doe', 'major': 'ICS'},
            { 'firstName': 'John', 'lastName': 'Smith', 'major': 'MATH'},
            { 'firstName': 'Bob', 'lastName': 'Data', 'major': 'CHEM'},
         ];

         function showData() {
            const students_table_body = document.getElementById('students_table_body');
            for (let student of data) {
               let tr = document.createElement('tr');  // table row
               let td = document.createElement('td');  // table data
               let contents = document.createTextNode(student.firstName);
               td.appendChild(contents);  // place first name inside td
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(student.lastName);
               td.appendChild(contents);
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(student.major);
               td.appendChild(contents);
               tr.appendChild(td);
               students_table_body.appendChild(tr);
            }
         }

         function init() {
            console.log(data);
            showData();
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

The new lines of code are lines 12-30, line 34 and lines 40-48. Lines 40-48 add the markup to use a heading for the table and the markup for all of the HTML table except for the table rows that will go into the <tbody> element.

Line 34 calls the showData() function defined on lines 12-30. The showData() function is used to generate the table rows that go into the <tbody> element, and place those rows into that element.

Within the showData() function, line 13 is used to obtain a reference to the <tbody> element. Lines 14-29 define a for loop that iterates over the array of objects in the data object. Note that the JavaScript for (.. of ..) style of a for loop is the equivalent of the for-each style for loop in most other programming languages. It is similar to using an index-based for loop that iterates over all the elements in the same way.

Line 15 creates a <tr> element, and line 16 creates <td> element. Line 17 creates a text node from the student’s firstName. Line 18 places this text node inside the <td> element, and that <td> element is placed inside the <tr> element on line 19. This process is repeated to get the <td> elements holding the student’s lastName and major. Finally, on line 28, the <tr> element is placed inside the <tbody> element.

Here is a screen shot of "using_tables.html" after these changes have been made:

view5

Using an index-based for loop

The for loop on lines 14-29 could have been an index-based for loop. The following code shows the lines that could have replaced those lines if an index-based for loop was used.

            for (let i = 0; i < data.length; i++) {
               let tr = document.createElement('tr');  // table row
               let td = document.createElement('td');  // table data
               let contents = document.createTextNode(data[i].firstName);
               td.appendChild(contents);  // place first name inside td
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(data[i].lastName);
               td.appendChild(contents);
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(data[i].major);
               td.appendChild(contents);
               tr.appendChild(td);
               students_table_body.appendChild(tr);
            }

Lines 14, 17, 21 and 25 are the lines that were changed to use the index-based for loop. If the for loop will do the same thing to all the elements of an array, it is simpler to use the for (.. of ..) style.

Don’t get the for (let key in obj) style of for loop in JavaScript confused with the for (let elem of array) style of for loop. The for (let key in obj) style for loop iterates over all keys of the object. Conversely, the for (let elem of array) style for loop iterates over all values of the object.
You should only use for (.. in ..) when you are iterating over all keys of an object. That is, don’t confuse the in operator with the of operator.

Using the jQuery plugin, DataTables 2

One of the ways to add functionality to a HTML table is to make use of the jQuery plugin, DataTables 2 https://cdn.datatables.net/2.0.0/. This plugin allows sorting by columns and searching (filtering) within the table. In order to make use of the plugin, we will need to use two JavaScript libraries and a CSS file. Here is the new version of "using_tables.html":

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <link rel="stylesheet" href="https://cdn.datatables.net/2.0.2/css/dataTables.dataTables.css">
      <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
      <script src="https://cdn.datatables.net/2.0.2/js/dataTables.js"></script>
      <script>
         addEventListener('DOMContentLoaded', init);
         data = [
            { 'firstName': 'Jane', 'lastName': 'Doe', 'major': 'ICS'},
            { 'firstName': 'John', 'lastName': 'Smith', 'major': 'MATH'},
            { 'firstName': 'Bob', 'lastName': 'Data', 'major': 'CHEM'},
         ];

         function showData() {
            const students_table_body = document.getElementById('students_table_body');
            for (let student of data) {
               let tr = document.createElement('tr');  // table row
               let td = document.createElement('td');  // table data
               let contents = document.createTextNode(student.firstName);
               td.appendChild(contents);  // place first name inside td
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(student.lastName);
               td.appendChild(contents);
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(student.major);
               td.appendChild(contents);
               tr.appendChild(td);
               students_table_body.appendChild(tr);
            }
         }

         function init() {
            console.log(data);
            showData();
            new DataTable('#students_table');
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

The new lines are lines 4-6 and line 38. Line 4 provides a link to a CSS file used for the default styling that DataTables uses. Line 5 includes the JavaScript source for jQuery and Line 6 includes the JavaScript source for DataTables.

Line 38 is used to construct a DataTable object using the HTML table identified by the attribute id="students_table"

Those four lines are all that are needed to get the default behavior of a DataTable. The following shows a screen shot showing the data sorted in descending order by major.

sort by major

The next screen shot shows the table filtered to show rows that contain the letter 'J':

filtered by j

Using an external data source with DataTables

The example as it stands is not very impressive because the number of rows of data is small. Let’s modify the example so that it uses an external JSON file for the data source.

Here is the contents of the file "students.json" that will be placed in the same directory as "using_tables.html":

students.json
{
   "students": [
      { "firstName": "Airi", "lastName": "Satou", "major": "MATH"},
      { "firstName": "Angelica", "lastName": "Ramos", "major": "EE"},
      { "firstName": "Ashton", "lastName": "Cox", "major": "PHYS"},
      { "firstName": "Bradley", "lastName": "Greer", "major": "CHEM"},
      { "firstName": "Brenden", "lastName": "Wagner", "major": "MATH"},
      { "firstName": "Brielle", "lastName": "Williamson", "major": "EE"},
      { "firstName": "Bruno", "lastName": "Nash", "major": "MATH"},
      { "firstName": "Caesar", "lastName": "Vance", "major": "BIOL"},
      { "firstName": "Cara", "lastName": "Stevens", "major": "ENG"},
      { "firstName": "Cedric", "lastName": "Kelly", "major": "PHYS"},
      { "firstName": "Charde", "lastName": "Marshall", "major": "BIOL"},
      { "firstName": "Colleen", "lastName": "Hurst", "major": "EE"},
      { "firstName": "Dai", "lastName": "Rios", "major": "ICS"},
      { "firstName": "Donna", "lastName": "Snider", "major": "PHYS"},
      { "firstName": "Doris", "lastName": "Wilder", "major": "EE"},
      { "firstName": "Finn", "lastName": "Camacho", "major": "EE"},
      { "firstName": "Fiona", "lastName": "Green", "major": "EE"},
      { "firstName": "Garrett", "lastName": "Winters", "major": "EE"},
      { "firstName": "Gavin", "lastName": "Cortez", "major": "MATH"},
      { "firstName": "Gavin", "lastName": "Joyce", "major": "ENG"},
      { "firstName": "Gloria", "lastName": "Little", "major": "ENG"},
      { "firstName": "Haley", "lastName": "Kennedy", "major": "ENG"},
      { "firstName": "Hermione", "lastName": "Butler", "major": "EE"},
      { "firstName": "Herrod", "lastName": "Chandler", "major": "PHYS"},
      { "firstName": "Hope", "lastName": "Fuentes", "major": "ENG"},
      { "firstName": "Howard", "lastName": "Hatfield", "major": "ENG"},
      { "firstName": "Jackson", "lastName": "Bradshaw", "major": "ICS"},
      { "firstName": "Jena", "lastName": "Gaines", "major": "ENG"},
      { "firstName": "Jenette", "lastName": "Caldwell", "major": "EE"},
      { "firstName": "Jennifer", "lastName": "Acosta", "major": "BIOL"},
      { "firstName": "Jennifer", "lastName": "Chang", "major": "EE"},
      { "firstName": "Jonas", "lastName": "Alexander", "major": "ICS"},
      { "firstName": "Lael", "lastName": "Greer", "major": "ENG"},
      { "firstName": "Martena", "lastName": "Mccray", "major": "CHEM"},
      { "firstName": "Michael", "lastName": "Bruce", "major": "CHEM"},
      { "firstName": "Michael", "lastName": "Silva", "major": "MATH"},
      { "firstName": "Michelle", "lastName": "House", "major": "CHEM"},
      { "firstName": "Olivia", "lastName": "Liang", "major": "ENG"},
      { "firstName": "Paul", "lastName": "Byrd", "major": "CHEM"},
      { "firstName": "Prescott", "lastName": "Bartlett", "major": "BIOL"},
      { "firstName": "Quinn", "lastName": "Flynn", "major": "MATH"},
      { "firstName": "Rhona", "lastName": "Davidson", "major": "ICS"},
      { "firstName": "Sakura", "lastName": "Yamamoto", "major": "BIOL"},
      { "firstName": "Serge", "lastName": "Baldwin", "major": "EE"},
      { "firstName": "Shad", "lastName": "Decker", "major": "BIOL"},
      { "firstName": "Shou", "lastName": "Itou", "major": "MATH"},
      { "firstName": "Sonya", "lastName": "Frost", "major": "ICS"},
      { "firstName": "Suki", "lastName": "Burks", "major": "BIOL"},
      { "firstName": "Tatyana", "lastName": "Fitzpatrick", "major": "PHYS"},
      { "firstName": "Thor", "lastName": "Walton", "major": "EE"},
      { "firstName": "Tiger", "lastName": "Nixon", "major": "BIOL"},
      { "firstName": "Timothy", "lastName": "Mooney", "major": "EE"},
      { "firstName": "Unity", "lastName": "Butler", "major": "PHYS"},
      { "firstName": "Vivian", "lastName": "Harrell", "major": "EE"},
      { "firstName": "Yuri", "lastName": "Berry", "major": "EE"},
      { "firstName": "Zenaida", "lastName": "Frank", "major": "EE"},
      { "firstName": "Zorita", "lastName": "Serrano", "major": "BIOL"}
   ]
}

Here is the new version of "using_tables.html" that will use "students.json" as the source for the table data:

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <link rel="stylesheet" href="https://cdn.datatables.net/2.0.2/css/dataTables.dataTables.css">
      <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
      <script src="https://cdn.datatables.net/2.0.2/js/dataTables.js"></script>
      <script>
         addEventListener('DOMContentLoaded', init);
         /*
         data = [
            { 'firstName': 'Jane', 'lastName': 'Doe', 'major': 'ICS'},
            { 'firstName': 'John', 'lastName': 'Smith', 'major': 'MATH'},
            { 'firstName': 'Bob', 'lastName': 'Data', 'major': 'CHEM'},
         ];
         */
         function showData() {
            const students_table_body = document.getElementById('students_table_body');
            for (let student of data) {
               let tr = document.createElement('tr');  // table row
               let td = document.createElement('td');  // table data
               let contents = document.createTextNode(student.firstName);
               td.appendChild(contents);  // place first name inside td
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(student.lastName);
               td.appendChild(contents);
               tr.appendChild(td);
               td = document.createElement('td');  // next td element
               contents = document.createTextNode(student.major);
               td.appendChild(contents);
               tr.appendChild(td);
               students_table_body.appendChild(tr);
            }
         }

         function init() {
            //console.log(data);
            //showData();
            new DataTable('#students_table', {
               ajax: { url: 'students.json', dataSrc: 'students' },
               columns: [
                  { data: "firstName" },
                  { data: "lastName" },
                  { data: "major" }
               ]
            });
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

The new lines are lines 9-15 and lines 37-46. Lines 9-15 just comment out the data object we had previously used. Line 37 and line 38 comment out the line that used to print the data to the console, and call the showData() function that is no longer used.

Lines 39-46 show the new lines for creating the DataTable object. Note that on line 39, instead of just ending the constructor for the DataTable class, there is an object (\{}) that encloses additional properties for creating the DataTable object. On line 40, the ajax property is set to an object that holds the url and dataSrc properties. The url property is set to the name of the source file, "students.json". The dataSrc property is set to the main key that points to the data in that file. This is the "students" key.

On lines 41-45, the columns property is set to an array of objects. Each of those objects is used to set up the columns of the table. Each column object needs to have a data property that specifies the name of the key (property) in the data file that holds the value for that column. Since the first column is for the first name, the property used is "firstName". Note that this is the key name used in the JSON objects, not the title of the table column. Recall that the JSON data file has objects that look like this:

{ "firstName": "Airi", "lastName": "Satou", "major": "MATH"}

So the first column should have data set to firstName. The second column should have data set to lastName, and the third column should have data set to major.

We can modify "using_tables.html" to remove any code that is not needed to display our data. Here is the new version of "using_tables.html":

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <link rel="stylesheet" href="https://cdn.datatables.net/2.0.2/css/dataTables.dataTables.css">
      <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
      <script src="https://cdn.datatables.net/2.0.2/js/dataTables.js"></script>
      <script>
         addEventListener('DOMContentLoaded', init);

         function init() {
            new DataTable('#students_table', {
               ajax: { url: 'students.json', dataSrc: 'students' },
               columns: [
                  { data: "firstName" },
                  { data: "lastName" },
                  { data: "major" }
               ]
            });
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

Making the table scrollable

Paginating the table is nicer than having a table that extends vertically forcing the entire page to become larger vertically. You can control the number of entries to show on a page. However, it can be clumsy to have to go through the pages to find the row(s) you want to see. To address this interface issue, you can make the table scrollable. This will allow you to control the amount of vertical space the table uses, and still allow seeing the contents of the table without having to use paging.

So, we will modify "using_tables.html" so that we can make the table scrollable. Here is the new version of the code:

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <link rel="stylesheet" href="https://cdn.datatables.net/2.0.2/css/dataTables.dataTables.css">
      <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
      <script src="https://cdn.datatables.net/2.0.2/js/dataTables.js"></script>
      <script>
         addEventListener('DOMContentLoaded', init);

         function init() {
            new DataTable('#students_table', {
               ajax: { url: 'students.json', dataSrc: 'students' },
               paging: false,
               scrollCollapse: true,
               scrollY: '220px',
               columns: [
                  { data: "firstName" },
                  { data: "lastName" },
                  { data: "major" }
               ]
            });
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

The new lines of code are lines 13-15. Line 13 prevents the pagination. Line 14 enables the scrolling. Line 15 sets the display height for the table. This is where you control the amount of vertical space the table takes up.

Here is a screen shot showing "using_tables.html" with vertical scrolling enabled as in the code above:

table scroll

Take a little time to scroll up and down in the table. This can be a useful feature to add to your tables, with very few additional lines of code.

Making rows selectable

For your applications, one of the main reasons for using a table is to make it easy to select a row of data. Thi can be for editing and/or jumping to a more detailed view of the data in that table row. So, it makes sense to allow a row to be selectable. One way to do this is to add an additional table column with a checkbox. Checking the checkbox and hitting a button could be used to select that row.

DataTables provides another way to select the table row that would just involve clicking anywhere in that row. This would take a fair amount of JavaScript code and CSS styles if you did this from scratch. But, DataTables provides you a relatively easy way to do this. Here is a first pass at showing that a table row has been selected:

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <link rel="stylesheet" href="https://cdn.datatables.net/2.0.2/css/dataTables.dataTables.css">
      <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
      <script src="https://cdn.datatables.net/2.0.2/js/dataTables.js"></script>
      <script>
         addEventListener('DOMContentLoaded', init);

         function init() {
            const table = new DataTable('#students_table', {
               ajax: { url: 'students.json', dataSrc: 'students' },
               paging: false,
               scrollCollapse: true,
               scrollY: '220px',
               columns: [
                  { data: "firstName" },
                  { data: "lastName" },
                  { data: "major" }
               ]
            });
            table.on('click', 'tbody tr', function(event) {
               event.currentTarget.classList.toggle('selected');
            });
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

The modified line is line 11. The new lines are lines 22-24. Line 11 has been modified so that we store a reference to the DataTable object, called table. Lines 22-24 add an event listener to the 'click' event that will cause the target row’s class to toggle between being selected and not selected.

You can reload "using_tables.html" and see that you can click on a row and that row will be highlighted. The problem is that you must click on that row again to deselect that row. Using something like this might be good for selecting multiple rows. But, let’s modify that event listener so that clicking on another table row will automatically deselect the previously selected row. That is, making the selection process select only one row at a time. Here is the modified version of "using_tables.html":

using_tables.html
<!DOCTYPE html>
<html>
   <head>
      <link rel="stylesheet" href="https://cdn.datatables.net/2.0.2/css/dataTables.dataTables.css">
      <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
      <script src="https://cdn.datatables.net/2.0.2/js/dataTables.js"></script>
      <script>
         addEventListener('DOMContentLoaded', init);

         function init() {
            const table = new DataTable('#students_table', {
               ajax: { url: 'students.json', dataSrc: 'students' },
               paging: false,
               scrollCollapse: true,
               scrollY: '220px',
               columns: [
                  { data: "firstName" },
                  { data: "lastName" },
                  { data: "major" }
               ]
            });
            table.on('click', 'tbody tr', function(event) {
                let classList = event.currentTarget.classList;

                if (classList.contains('selected')) {
                    classList.remove('selected');
                }
                else {
                    table.rows('.selected').nodes().each((row) => row.classList.remove('selected'));
                    classList.add('selected');
                }
                console.log(event.currentTarget);
            });
         }
      </script>
   </head>
   <body>
      <h1>Using Tables</h1>
      <h2>Students</h2>
      <table id="students_table" border="1">
         <thead>
            <tr>
               <th>First Name</th><th>Last Name</th><th>Major</th>
            </tr>
         </thead>
         <tbody id="students_table_body"></tbody>
      </table>
   </body>
</html>

The new lines are lines 23-32. Line 23 gets a reference to the classList property associated with the selected table row. This property will keep track of whether or not the 'selected' is an applied class for that element. The selection statement that follows will remove the 'selected' attribute for an already selected element, and add the 'selected' attribute for the currently selected element (table row). So, now only one table row can be selected at a time. You can test this out by viewing this new version in the browser.

Line 32 will display the selected table row element. Note that if you wanted to select the column being clicked on instead of the whole row, you could use event.target instead of event. currentTarget

The following screen shot shows the selection of a table row. Within the console, the object associated with event.currentTarget has been expanded.

single row selected

Note that the <tr> element has a class of 'selected'. If you looked inside the CSS file on line 4 of "using_tables.html", you would see how the background color has been changed to blue.

You can also see that the table has been sorted by the first column of the table by looking at which <td> element has the class "sorting_1". In this case the table has been sorted by the first column (First Name column).

If you are thinking ahead of this table data being used to edit data, you should think of how we could identify a database record for this row. That is, we would need to have some sort of id for this data as part of this table row element. That is something we will look at when we start working with a backend database and/or localStorage.