Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

Exercise: Building Tables

Exercise 45 Average

Prerequisites for the exercise

  1. HTML DOM — Elements
  2. All previous chapters

Objective

Create two variants of a function to build an HTML table based on a given array of items.

Description

Consider the following array of objects:

var languages = [
   {name: 'JavaScript', releaseDate: 1995, fileExtension: '.js', creator: 'Brendan Eich'},
   {name: 'Java', releaseDate: 1995, fileExtension: '.java', creator: 'James Gosling'},
   {name: 'PHP', releaseDate: 1995, fileExtension: '.php', creator: 'Rasmus Lerdorf'},
   {name: 'C++', releaseDate: 1985, fileExtension: '.cpp', creator: 'Bjarne Stroustrup'},
];

Each object represents information of a particular programming language, such as its name, its release date, its creator, its file extension, and so on.

If we were to convert this array into a table, we'd get the following:

NameRelease DateFile ExtensionCreator
JavaScript1995.jsBrendan Eich
Java1995.javaJames Gosling
PHP1995.phpRasmus Lerdorf
C++1985.cppBjarne Stroustrup

Notice the headings here. They are just the keys of the objects in the array, converted from camel case to title case. Apart from the table's header, each subsequent row represents an item of the array.

In this exercise, you have to define two variants of a function createTable() that creates a <table> based on a given array, as demonstrated above.

The function accepts two arguments: the first one is the array to use to create a table whereas the second argument is an element node where we wish to insert the table created.

Here's what the variants are about.

Variant 1

In the first variant of createTable(), you MUST ONLY use DOM mutation properties, such as innerHTML and textContent. You MUST NOT use any DOM mutation method such as appendChild(), insertBefore(), etc.

Variant 2

In the second variant of createTable(), you have do the opposite of variant 1.

That is, you MUST NOT use any DOM mutation properties, such as innerHTML and textContent. Instead, you MUST ONLY use DOM mutation methods such as appendChild(), insertBefore(), etc.

Both these variants only differ in their underlying implementation; the result produced is exactly the same.

View Solution

New file

Inside the directory you created for this course on JavaScript, create a new folder called Exercise-45-Building-Tables and put the .html solution files for this exercise within it.

Solution

Let's start by setting up the basic wireframe of the function, i.e. its definition including the parameters required:

function createTable(arr, element) {
   // Code to go here.
}

There isn't really much more to explore here, so let's dive right into implementing the first variant of the function.

Variant 1

Step one is to create the header of the table, using the keys of any object from the given array arr (we could use any item since, as per our assertion, all the items have the exact same keys in the exact same order).

There are technically two ways to accomplish this:

  1. Use a for loop to iterate over a given object and create a <th> element for each key (ignoring its value).
  2. Call the static method Object.keys() to obtain an array of all the keys of a given object.

We'll go with the latter here since it makes the implementation of the rest of the function a little bit simpler. We'll see how as this discussion progresses.

So, let's go ahead and create an array called keys holding the return value of Object.keys() as called on the first item of arr:

function createTable(arr, element) {
   var keys = Object.keys(arr[0]);
}

Great!

Now we ought to create the table's header using this keys array.

And for that, we'll employ the map() and join() array methods in addition to the camelToTitleCase() function, that we created in the JavaScript Arrays — Camel To Title exercise, in order to convert the keys from camel case to title case.

The way these utilities will be used is detailed as follows:

  • map() would be used to map each key of keys to a <th> element containing the key as its text content, converted into title case with the help of the camelToTitleCase() function.
  • join() would join this mapped array with the delimiter ''. This would simply form a string such as '<th>Key 1</th><th>Key 2</th>...'.

In the end, the output of join() would be sandwiched in between the strings '<tr>' and '</tr>', and in this way we'd get the header row of the table.

Seems simple to implement, doesn't it?

The code below accomplishes all of this:

function createTable(arr, element) {
   var keys = Object.keys(arr[0]);
   var html = '<table>';

   // Create the heading of the table.
   html += '<tr>' + keys.map(function(key) {
      return '<th>' + camelToTitleCase(key) + '</th>';
   }).join('') + '</tr>';
}

Perfect so far.

Now the next step is to create the rest of the rows of the table. And this just requires us to iterate over arr and create a table row for each object.

In the code below, we accomplish this using a similar map() and join() logic that we used above:

function createTable(arr, element) {
   var keys = Object.keys(arr[0]);
   var html = '<table>';

   // Create the header of the table.
   html += '<tr>' + keys.map(function(key) {
      return '<th>' + camelToTitleCase(key) + '</th>';
   }).join('') + '</tr>';

   // Create the rest of the rows.
html += arr.map(function(item) {
return '<tr>' + keys.map(function(key) {
return '<td>' + item[key] + '</td>';
}).join('') + '</tr>';
}).join(''); }

In the end, we just need to concatenate the ending tag '</table>' to the variable html and then set the innerHTML property of element to html:

function createTable(arr, element) {
   var keys = Object.keys(arr[0]);
   var html = '<table>';

   // Create the header of the table.
   html += '<tr>' + keys.map(function(key) {
      return '<th>' + camelToTitleCase(key) + '</th>';
   }).join('') + '</tr>';

   // Create the rest of the rows.
   html += arr.map(function(item) {
      return '<tr>' + keys.map(function(key) {
         return '<td>' + item[key] + '</td>';
      }).join('') + '</tr>';
   }).join('');

html += '</table>';
element.innerHTML = html; }

And this is the implementation of the first variant of createTable().

Let's try it out.

In the following code, we have a #main element where we create a table based on the same languages array that we saw at the start of this page:

<div id="main"></div>
function camelToTitleCase(str) {
   /* ... */
}

function createTable(arr, element) {
   /* ... */
}

var languages = [
   {name: 'JavaScript', releaseDate: 1995, fileExtension: '.js', creator: 'Brendan Eich'},
   {name: 'Java', releaseDate: 1995, fileExtension: '.java', creator: 'James Gosling'},
   {name: 'PHP', releaseDate: 1995, fileExtension: '.php', creator: 'Rasmus Lerdorf'},
   {name: 'C++', releaseDate: 1985, fileExtension: '.cpp', creator: 'Bjarne Stroustrup'},
];

var mainElement = document.getElementById('main');
createTable(languages, mainElement);

Live Example

Over to the second variant.

Variant 2

As stated in the exercise's description above, in the second variant of createTable(), we have to operate solely around nodes; we can't work with strings to create the table.

So, the tools that we have in our inventory right now are document.createElement(), document.createTextNode(), and the appendChild() method of the Element interface.

Technically, we could use any of the DOM mutation methods that accept nodes, such as insertBefore(), append(), insertAdjacentElement(), and so on. But appendChild() is clearly simpler and likewise we'll go with it.

Let's start coding.

As for the keys array defined above, we'll omit it in favor of using the for...in loop to iterate over each object of arr. This is because we won't be map()ping or join()ing any arrays and therefore there really isn't any point of having one.

Anyways, the first thing we'll do is create a <table> element node to hold all the nodes that we'll be creating later on in our code:

function createTable(arr, element) {
   var tableElement = document.createElement('table');
}

Now let's get done with the header of the table:

function createTable(arr, element) {
   var tableElement = document.createElement('table');

   // Create the header of the table.
var tableRowElement = document.createElement('tr');
for (var key in arr[0]) {
var tableHeadingElement = document.createElement('th');
tableHeadingElement.appendChild(
document.createTextNode(camelToTitleCase(key)));
tableRowElement.appendChild(tableHeadingElement);
}
tableElement.appendChild(tableRowElement); }

Next up, let's get done with the rest of the rows of the table.

function createTable(arr, element) {
   var tableElement = document.createElement('table');

   // Create the header of the table.
   var tableRowElement = document.createElement('tr');
   for (var key in arr[0]) {
      var tableHeadingElement = document.createElement('th');
      tableHeadingElement.appendChild(
         document.createTextNode(camelToTitleCase(key)));

      tableRowElement.appendChild(tableHeadingElement);
   }
   tableElement.appendChild(tableRowElement);

   // Create the rest of the rows.
for (var i = 0, len = arr.length; i < len; i++) {
tableRowElement = document.createElement('tr');
for (var key in arr[i]) {
var tableDataElement = document.createElement('td');
tableDataElement.appendChild(document.createTextNode(arr[i][key]));
tableRowElement.appendChild(tableDataElement);
}
tableElement.appendChild(tableRowElement);
} }

In the end, what's left is just to insert tableElement inside element. And this is as simple as calling element.appendChild(), passing in tableElement as an argument:

function createTable(arr, element) {
   var tableElement = document.createElement('table');

   // Create the header of the table.
   var tableRowElement = document.createElement('tr');
   for (var key in arr[0]) {
      var tableHeadingElement = document.createElement('th');
      tableHeadingElement.appendChild(
         document.createTextNode(camelToTitleCase(key)));

      tableRowElement.appendChild(tableHeadingElement);
   }
   tableElement.appendChild(tableRowElement);

   // Create the rest of the rows.
   for (var i = 0, len = arr.length; i < len; i++) {
      tableRowElement = document.createElement('tr');
      for (var key in arr[i]) {
         var tableDataElement = document.createElement('td');
         tableDataElement.appendChild(document.createTextNode(arr[i][key])); 
         tableRowElement.appendChild(tableDataElement);
      }
      tableElement.appendChild(tableRowElement);
   }

element.appendChild(tableElement); }

And this completes the implementation of the second variant of createTable().

Now for the testing part, we'll use the same code that we used above, shown as follows:

<div id="main"></div>
function camelToTitleCase(str) {
   /* ... */
}

function createTable(arr, element) {
   /* ... */
}

var languages = [
   {name: 'JavaScript', releaseDate: 1995, fileExtension: '.js', creator: 'Brendan Eich'},
   {name: 'Java', releaseDate: 1995, fileExtension: '.java', creator: 'James Gosling'},
   {name: 'PHP', releaseDate: 1995, fileExtension: '.php', creator: 'Rasmus Lerdorf'},
   {name: 'C++', releaseDate: 1985, fileExtension: '.cpp', creator: 'Bjarne Stroustrup'},
];

var mainElement = document.getElementById('main');
createTable(languages, mainElement);

Live Example

Amazing. It works flawlessly.

We must be proud of ourself to come this far as to be able to work with the DOM API in different ways and produce the exact same end result.

Great job! 👍