Session 24: Generating HTML from Data
Download this starter code to use during the session.
Student Assessments
Setup First Task: IDS Bundles
Over the last few sessions, we have occasionally discussing how to use JavaScript to generate HTML elements for a page.
Even without an event in the browser, we can use JavaScript to generate HTML elements and add them to a page. We might prefer this approach to writing HTML by hand when the element, often a list or a table, is long and tedious to create.
So far, our examples have all been small:
- adding an image to a page
- generating a table of squares, twenty numbers total
In Session 23, we saw a more example in
the photo gallery web page,
in the div
with the anchors and images. It
contains a lots of repetitive code, which is tedious to write,
even with copy-and-paste.
But we could write those by hand!
How about the table of baby names we saw earlier this semester?. It contains 100 rows of five columns each.
That is worth generating.
This week's reading discusses some of the JavaScript tools we can use to access all that, working with strings and arrays.
Let's tackle a smaller problem first: generating an HTML list with all of the "bundles" available in UNI's Interactive Digital Studies program. We encountered these bundles when we learned how to create collapsible lists in Session 13. There, we worked with only three of the ten bundles, and even then the copying and pasting was tedious and prone to typos.
We can find a list of the bundles and their courses in the UNI catalog. I have the full list in a text file that lists each bundle on a separate line in CSV format.
Our goal is to produce HTML for a list like this, but with all ten bundles included.
Let's do it.
Creating a List of IDS Bundles
Let's start with
this page.
It has a title, an empty main
, and a footer. We
will generate a ul
element and add it to the
main
section.
Create list of bundles
First, load the data into the script as a string. Use backticks to define a multi-line string. This string is long. If we store it in a variable, it has to appear at the top of the file, where it will obscure code the code that follows. Instead, let's turn it into a function. We can place function definition at the bottom of the file.
function listData() { return `Digital Visualization, COMM DM 1611, COMM DM 3659, COMM DM 4655, COMM 3558, COMM 4558 ... Digital Mapping, GEOG 1310, GEOG 2320, GEOG 2410, GEOG 4335, GEOG 4360`; }
Now, let's use split('\n')
to create a list of
rows, each a string. Then, for each row, use
split(',')
to create a list of fields.
Find main
in the document, and make a
ul
element that we can build.
Next, loop through the rows, create an li
element, set its text to the value of item 0 in the array,
and add the element to the to the ul
element.
Finally, add the ul
element to main.
// split the big string into a list of rows (strings) let rows = listData().split('\n'); // split each row into a list of strings, trimmed for (let i = 0; i < rows.length; i++) { rows[i] = rows[i].split(','); } let mainElement = document.querySelector("main"); let ulElement = document.createElement("ul"); for (let i = 0; i < rows.length; i++) { let liElement = document.createElement("li"); liElement.innerText = rows[i][0]; ulElement.appendChild(liElement); } mainElement.appendChild(ulElement);
We can combine the loops, if we'd like. This will create the list item right after we split string into an array. Be sure to move the find/create lines before the first loop, and then merge the loops.
Create sub-lists of bundle requirements
Inside the loop that creates the li
elements,
make an inner ul
element. Loop through the
rest of the row's array, starting at 1. For each field,
create an inner li
and add it to the inner
ul
. Finally, add the inner ul
element to outer li
element.
// inside the for-loop let innerUL = document.createElement("ul"); for (let j = 1; j < rows[i].length; j++) { let innerLI = document.createElement("li"); innerLI.innerText = rows[i][j]; innerUL.appendChild(innerLI); } liElement.appendChild(innerUL);
There are leading spaces on the strings that are the bundle
requirements. They were left behind after the
split(',')
, because the data file had spaces.
This is not a problem here, because the browser will ignore
any white space in the inner text of an element. In other
situations, though, they could be a problem. It's good to
be in the habit of trimming leading and trailing spaces from
data fields, using the trim()
method.
innerLI.innerText = rows[i][j].trim();
In the end, this code may look complex. But you understand all of the parts. Even better, if we build the script step by step, each addition to the code seems reasonable. The complexity grows out of the individual pieces.
Setup Second Task: Iowa Cities
We could almost write the HTML for that list by hand.
But the table of baby names we saw earlier is much too long to type.
And then consider this table of Iowa cities from Session 6. I used a 4-row, 3-column subset of this list there and in Session 22. The complete table contains 1021 rows with 8 columns each.
No one wants to type this table by hand.
Knowing JavaScript, we do not have to you. The web page has a link to download the data from that website using the download icon. It gives us a CSV file. You can use that file to generate the table.
Exercise: Generate the Iowa Cities Table
Start with
this page.
It has a title and a table with a caption, a header, a footer,
and an empty tbody
. You will generate the rows
of the table and add them to the tbody
element.
The table columns correspond to columns 8, 19, 0, 13, and 15 in the CSV file.
tr
elements to fill the
table.
Use the contents of the us-cities-table.csv
file to create the multiline string you will process. Put
it in a function at the bottom of the file, as we did for
the IDS bundles.
Follow the same process we did on that example:
- create the string
- split string into an array of rows
-
loop over that array to split each array into an array
of fields, creating
td
elements and adding them to atr
element - add the row to the
tbody
element
Hint: Start your main loop at i = 1, in order to skip the header row.
A Possible Solution
Here is a possible solution: script.js .
Notice that our data has double quotes on the city names. This is surprisingly common in CSV files, to guard against cases in which the data value has a comma in it.
We can use the string method replace
to find the
"
s and replace them with the empty string.
rows[i][8].replace(/"/g, ''); // Remove double quotes
This is a long list, which results in a long scroll that hides
the page footer. In
Session 11,
we learned about CSS's overflow
property. We can
use it to implement a quick fix to our page:
table { display: block; height: 510px; overflow: auto; }
Another nice change might to "fix" the header row at the top of the table and scroll only the body.
Bonus: Some data sources provide data in JSON format. JSON data is a JavaScript list of objects!
Bonus: It would be nice to read the data file into a string, rather than do it ourselves. JavaScript supports this, but web browsers do not. They prevent web pages from accessing local files on the client's computer, for security reasons. However, you can run such code on the server or in Node.js.
Closing
At the end of the session, zip up your 24-starter
folder and submit it for a quick review using the course
submission system. Again, I am not grading these for
correctness, only to provide a little extra motivation to
follow along in class.
Homework 10 is due tomorrow. It asks you to practice write functions and scripts that modify a web page. This is our last weekly homework assignment.
For the remainder of the course, you will work on a final project. You will implement a small website using all of the tools you have learned this semester: HTML, CSS, and JavaScript. You get to decide what to design and build! I will offer some suggestions in case you don't have anything in mind. Watch for the spec to go live in the next day or two.
Next two session we will practice JavaScript and learn more about the kind of events our scripts can handle. Our second midterm exam 2 is one week from today.