Web browsers from the beginning have been designed to handle asynchronous input from users. A click of a mouse, entering a field on a form, even scrolling an object into view can be a signal to process some interesting bit of JavaScript code.
From the beginning of the Web, browsers have had a basic form of interactivity, viz. clicking on hyperlinks in a Web page to jump to another page.
This is what makes HTML “hypertext” because it allows you to mark up specific pieces of text with links to other Web pages, and it’s what makes the World-Wide Web possible.
For example, we can link to the source of the image of the Declaration, shown above, using an anchor element:
Like the image element, an anchor takes an attribute href that refers to the other web page.
Also like an image element, an anchor is displayed inline. And, like images (and any other element, really), it could be “floated”, here at the bottom of the document:
<p>Baltimore, in Maryland: Printed by Mary Katharine Goddard. <a style="float: right;" href="http://en.wikipedia.org/wiki/File:Goddard_broadside.jpg">Declaration Image</a> </p>
In your browser, this HTML will by default appear as underlined text:
Baltimore, in Maryland: Printed by Mary Katharine Goddard.
Declaration Image
and clicking on it will load the referenced page into your browser.
Can you make the full image of the Declaration shown above appear in place of the text “Declaration Image” while preserving the hypertext link? As a small “thumbnail” image, of course!
will recognize and jump to its location — its anchor!
Remember that the value for an attribute id is supposed to be unique within a document; searches such as this will only find the first instance if you have used the value more than once.
Originally anchors used the attribute name to identify them, but that practice is now discouraged in favor of the more general attribute id.
Pseudo-Class Styles
Since CSS was introduced in 1996, it has provided some interactivity itself in the form of pseudo-classes that distinguish between different states of any kind of element, but most commonly hyperlinks:
:hover
Link is being “pointed at” but not clicked on
:active
Link has been clicked on but not released
:visited
Target of link has been visited
Adding these classes to an element, class, or id selector allows you to define different styles for these different states, e.g.:
a { color: #2659BF; } /* blue, underlined by default */
a:hover { color: #00F; text-decoration: none; } /* bright blue, no underline */
a:active { color: #99F; text-decoration: none; } /* whitish blue, no underline */
a:visited { color: #666; } /* medium gray, underlined by default */
<a href="https://en.wikipedia.org/wiki/United_States_Declaration_of_Independence">The United States Declaration of Independence</a>
With the publication of the HTML 4 specification in 1999, most elements could have a title attribute, which Web browsers will usually display as a pop-up tooltip when the mouse cursor hovers over the element.
The previous set of behaviors are built-in responses to events that are generated in a Web browser by user actions:
Hyperlinks and the CSS pseudo-class :visited are responses to click events.
The pseudo-classes :hover and :active are responses to the events mouseover and mousedown, respectively.
A title tooltip is also a response to a mouseover event.
Common events include mousemove / mouseover / mouseout, click / mousedown / mouseup, focus, change, resize, scroll, select, and load / unload.
But there are many, many more! A complete list can be found here.
Every DOM element has some set of events predefined for it, which you can determine by looking them up in the HTML reference.
And for any given event you can build your own response with JavaScript.
As a simple example, we can respond to a <strong> element’s mouseover event by increasing the size of its text, and to its mouseout event by restoring its text to its original size:
The JavaScript code snippets that are the values for the onmouseover and onmouseout attributes are called event handlers, and they are evaluated whenever the mouse cursor moves over the paragraph and when it moves out away from it, respectively.
The keyword this is a reference to the element itself, so that ordinary DOM methods can be applied, in this case setting and removing the paragraph’s style attribute.
Every jQuery selection can use direct methods .click(), .dblclick(), .mouseenter(), .mouseleave(), .mousedown(), .mouseup(), .mousemove(), .hover(), .change(), etc. In each case, the event handler is in the form of a callback function:
After hyperlinks, the most obvious user interactions on a web page are with buttons, which are designed to capture click events. There are three types of buttons: unimodal buttons, checkboxes, and radio buttons.
Unimodal Buttons
A unimodal button is intended to initiate some kind of action, like the keys on your keyboard produce a character.
By default unimodal buttons are a graphic, usually rectangular, and possibly labeled. They are single in-line elements that are one type of input element:
<p id="truths">
We hold these truths to be self-evident, that all men are created equal,
that they are endowed by their Creator with certain unalienable Rights,
that among these are Life, Liberty and the pursuit of Happiness.
</p>
We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness.
For button inputs, the value attribute provides the label inside the button.
When a button is clicked, the browser evaluates the JavaScript code in the onclick attribute, which in this case are the functions magnifyText()and shrinkText():
<script>
function magnifyText(selection)
{
var element = $(selection);
if (element.css('font-size') == '')
element.css('font-size', '120%');
else
element.css('font-size', parseInt(element.css('font-size')) * 1.20);
}
function shrinkText(selection)
{
var element = $(selection);
if (element.css('font-size') == '')
element.css('font-size', '83%');
else
element.css('font-size', parseInt(element.css('font-size')) * 0.833);
}
</script>
Best Practice: The purpose of buttons should always be clear. This can be achieved by an appropriate value attribute, but a separate label is sometimes better, as in the above example. This provides an important reference point, especially for the visually impaired using screen readers. One advantage for all users, though, is that the label element extends the “clickable” area to include the label text.
Checkboxes
While unimodal buttons initiate some action when clicked, a checkbox is bimodal and intended to represent a toggle for a pair of states, such as on-off, yes-no, or true-false.
Checkboxes should always include labels that describe their “on” state.
<label>
<input type="checkbox" checked onchange="toggleAbuses(this)" /> Show Abuses
</label>
In this case it calls the function toggleAbuses() and hands a reference to itself, this.
The event handler must, of course, have been defined by the time the click occurs, e.g. if it’s placed directly or indirectly in the header of the document:
jQuery
d3
var abusesNodes;
function toggleAbuses(checkbox)
// button references DOM node
{
var abusesList = $('ul#abuses');
var abusesItems = abusesList.children();
// Test if the <li> nodes are linked in:
if (abusesItems.length > 0) // already in place
{
// save nodes for the <ul>
abusesNodes = abusesItems.detach();
checkbox.checked = '';
}
else // Add the <li> nodes back.
{
abusesNodes.appendTo(abusesList);
checkbox.checked = 'checked';
} }
var abusesNodes;
function toggleAbuses(checkbox)
// button references DOM node {
var abusesList = d3.select('ul#abuses');
var abusesItems = abusesList.selectAll();
// Test if the <li> nodes are linked in:
if (abusesItems.node()) // already in place
{
// save nodes for the <ul> abusesNodes = abusesItems.remove()[0];
checkbox.checked = 'checkbox.checked';
}
else // Add the <li> nodes back.
{
abusesNodes.forEach(
function(item) {
abusesList.append( function() { return item } )
}
);
checkbox.checked = 'checked';
} }
The effect is to first remove and then re-insert the list of abuses into the page as the button is clicked.
Radio Buttons
When you need to select from a few items that are mutually exclusive (usually more than two), you can use radio buttons rather than checkboxes.
For example, we might choose to highlight one state’s delegates in the table:
A set of radio buttons designed to work together will all have the same value for their name attribute. By default none of them will be selected, but you can include the attribute checked to select one initially, as for Massachusetts above.
Menus and Option Lists
When you have more than a few items to select from (and generally only when listing them all will take up too much screen room), you can use a menu or option list:
By default <select> elements display as a pop-up menu with only the first item visible, but the attribute size controls the number of options visible at once; if it’s less then the total number it appears as a scrollable option list.
The attribute selected can be used to provide an initial selection or set of selections, which corresponds to the attribute checked for checkboxes and radio buttons.
By default, only one option can be selected, like a set of radio buttons. To allow more than one of the options to be selected at once, like a set of checkboxes, include the boolean attribute multiple.
Best Practice: Don’t start with a blank option; always include some value, such as the word None above.
The radio buttons and menu above can both use the same function to highlight their targets in some way — a <tr> and a <td>, respectively. Do it! Can you make them distinguishable?
Hint: When you construct your own data set, you get to include identifiers that make it easy to look up elements.
Bonus: The option list is provided for you in the code, but you can create the option list directly from the doisigners.js file that you’re already loading for Problem 6. You’ll want to make use of array methods such as are described in the last chapter — including the sort method!
Text fields provide a way to type in information that can be processed by a Web page. There are two types, text inputs for small, one-line responses, and text areas for more general responses.
<input type=text placeholder="Type your answer here!" autofocus onchange=submit(this) />
<textarea rows=5 cols=80 onchange=submit(this)>Boilerplate language here.</textarea>
Notice that <input> is a single-tag element, while <textarea> is a dual-tag element; whatever is between its tags is placed in the text area and is editable.
<input> also allows for the placeholder attribute, but that is overridden by the text node.
Bootstrap claims to be “the world’s most popular front-end open source toolkit”, which will let you “quickly design and customize responsive mobile-first sites”.
So far we have just been loading our web pages directly with a web browser, as indicated by the address, or Uniform Resource Locater (URL), that begins with file:// and then a local file path starting with /, which references the top of the file system.
On the World-Wide Web, however, your web browser, known as a client, will request web pages from servers using the Hypertext Transfer Protocol, indicated by URLs beginning with http:// and then a remote server such as www.server.com followed by a file path starting with /, which references the web document origin or root, an isolated folder on the server. Web servers will then respond with a status header followed by a document, such as the famous “404 - web page not found”.
You will therefore want to test your web pages in an actual client-server environment, and one way to do that is by installing Node.js, a small and lightweight web server that runs on just about any computer platform.
One advantage of Node is that it uses JavaScript as a language to process requests coming in from web browsers asking for web pages, and you have complete control over these details.
A very simple server using Node looks like this JavaScript module server.mjs (download here and place the file in your web programming folder).
import http from 'http' // server library — built-in import fs from 'fs' // file system library — built-in import mime from 'mime' // MIME file-type library — add-in
// Create a web server that, when a page request comes in,
// e.g. "http://localhost:3000/index.html",
// passes it to a callback function to generate a response:
var server = http.createServer(
function(request, response)
{
var filePath = './' + (request.url == '/' ? '/index.html' : request.url);
// Check the file status: fs.exists(filePath,
function(exists)
{
if (exists)
fs.readFile(filePath,
function(error, data)
{
if (error)
{
response.writeHead(500,
{ 'Content-Type': 'text/plain' });
response.end('Error 500: Internal Server Error');
}
else
{
response.writeHead(200,
{ 'Content-Type': mime.getType(filePath),
'Content-Length': data.length });
response.end(data);
}
}
);
else
{
response.writeHead(404, { 'Content-Type': 'text/plain' });
response.end('Error 404: resource not found.');
}
}
);
}
);
// Start up the web server:
server.listen(3000, function() { console.log("Server listening on port 3000."); } );
Node provides, built-in, just the basic items necessary for a server, such as the server library and the file system library that are loaded with the require() function, but you really also need another add-on library that provides content types using the MIME (Multipurpose Internet Mail Extensions) standard. This makes many browsers happier by telling them the type of document being sent, for example:
'text/plain' — used for files typically ending with .txt, or for informational messages such as the error messages above.
'text/html' — used for files typically ending with .html, i.e. most documents requested from a web server.
'text/css' — used for files typically ending with .css, e.g. those referenced with a <link href="…">statement.
'text/javascript' — used for files typically ending with .js, e.g. those referenced with a <script src="…">statement.
'image/jpeg' — one of several image types we’ve seen, e.g. referenced with a <img src="…">statement.
The last three statements require that the web browser request additional documents from the web server as it builds and displays the web page that is first sent to it.
In Node, the server is defined by the first statement server = http.createServer(callback());, where the callback function runs each time an asynchronous request for a document comes in to provide the details about how to respond to it. The server is then launched with the last statement server.listen(3000, …); that tells the server to listen on port 3000 (one of more than 62,000 different ports an Internet program can use, such as port 80 for web pages, but most of which are unassigned.)
The possible status codes for HTTP can be viewed here.
Once you have installed Node and before you start it up, you will usually want to install add-on packages such as the MIME library, by running the Node Package Manager command in your terminal/command window. On the Macintosh you will find this in the Utilities folder inside the Applications folder; on Windows use the special Node.js command prompt in the Node.js item in the Start menu.
The server will look for files in whichever directory (folder) you start it in, so change directory to your Web programming folder, for example:
cd Desktop/doi
Note: on Windows computers, you must first select your disk by typing its drive letter before you can descend into it, e.g.
U:
cd doi
This establishes the current directory, which is kind of like the front-most window in the Mac Finder or the Windows File Explorer.
To install the mime library into node, type in the following command and press Return or Enter:
npm install mime
The package will be created in a folder named node_modules and be available only to the web pages in the current directory.
Then type in the following to start the server:
node server.mjs
Node should respond with Server listening on port 3000; to end this instance of Node, hold down the Control key and press C.
Loading a Web page in sequence can really slow it down when it includes a large data set, and may even prevent the page from being usable in the interim. So it’s now important to load remote content only as needed.
It’s also important to provide data in a standard format that will promote sharing.
The data structures described above are the basis of a web standard format that is readable by many other languages besides JavaScript, as well as by humans.
The JavaScript Object Notation (JSON) includes useful items like arrays and objects, though the properties in the latter must be double-quoted.
As an example, consider the table of signers of the Constitution; it can be structured very naturally as an object with states as properties and lists of signers as values:
Note that, unlike the previous file that is loaded directly as JavaScript, this one is not assigned to a variable; instead it’s essentially another text document that must be interpreted by handling it to JavaScript’s JSON parser, which reads it in and converts it to JavaScript code:
Why not just assign this dataset to a variable directly? Imagine it’s much, much bigger, e.g. instead of the 55 names it had our current 435 congressional representatives along with detailed information about them!
When a dataset is large and can take a while to load, it’s generally better to get something onto the web page quickly and insert the data later, so the viewer won’t be concerned about little or nothing appearing at all.
To this end we can make use of JavaScript’s asynchronous capabilities, meaning we make a request for the data and only deal with it when we receive a signal indicating that it’s finished loading:
var doi_signers;
var doi_url = 'doisigners.json';
var doi_request = new XMLHttpRequest();
doi_request.onreadystatechange =
function () {
var complete = 4, ok = 200;
if (doi_request.readyState == complete && doi_request.status == ok)
console.log(doi_request.responseText);
};
doi_request.open('GET', doi_url);
doi_request.send(null);
The JavaScript object XMLHttpRequest is created with the keyword new, and assigned to the variable doi_request. It allows background processing of data as follows
by opening the data location with the method doi_request.open()which includes a request to GET the contents of the URL;
by sending a null datum with the method doi_request.send() to indicate that there’s nothing else to send — null is a data type that represents “no value” or “nothing”;
and then waiting for the data to be received into the property doi_request.responseText;
after which it runs the method doi_request.onreadystatechange()with the parameter doi_request.readyState set to 4, indicating completion (earlier state changes and their values are listed here).
This last method is intended to be anything you want it to be, as suggested by the function assignment.
As written above the doi_request.onreadystatechange() method just displays the text from the file doi_url, using the method console.log(). This way we can ensure we are correctly accessing the data.
To do something more interesting, we’ll need to first parse the text into JavaScript:
doi_request.onreadystatechange =
function () {
var complete = 4, ok = 200;
if (doi_request.readyState == complete && doi_request.status == ok)
doi_signers = JSON.parse(doi_request.responseText);
};
The variable doi_signers could also be declared outside the function, which would make the value assigned to it accessible for other purposes, but in this case everything we want to do must happen inside this function when it is called.
We now need to insert the data from this object into the web page, and this requires that we have an identifiable container ready for it at the appropriate location in the HTML:
<table id="doi_table"></table>
Then the function becomes:
doi_request.onreadystatechange =
function () {
var complete = 4, ok = 200;
if (doi_request.readyState == complete && doi_request.status == ok)
{
var doi_signers = JSON.parse(doi_request.responseText);
var doi_rows = '\n';
for (state in doi_signers)
{
doi_rows += '<tr><td>' + state + ":</td><td>";
names = doi_signers[state];
for (n = 0; n < names.length - 2; n++)
doi_rows += names[n] + '<br />';
doi_rows += names[n] + '</td></tr>\n';
}
document.getElementById('doi_table').innerHTML = doi_rows;
}
};
Here we see another example of the for…in statement that is most useful for objects; it simply iterates through all of their properties, which in this case are the state names. So, in particular, the first assignment is state = 'Georgia', with doi_signers[state] = [ "Button Gwinnett", "Lyman Hall" ]. The latter list is processed by the inner loop, bracketing it with the appropriate HTML.
The HTML is accumulated into the character string doi_rows, and is inserted into the table <table id="doi_table"></table>, which is located by the method document.getElementById('doi_table').
This is an example of using the Document Object Model to locate existing pieces of the web page and modify them, in this case by copying to the table element’s property innerHTML, which is an unofficial standard.
Again, you can use the JavaScript console to view this newly-inserted HTML.
When using the object XMLHttpRequest, another property that is received along with responseText is responseXML; if the remote document is written in an XML format (in particular in HTML), it will be parsed automatically into a DOM “branch” that can be inserted directly into a web page element. This is the original approach to dealing with asynchronous data retrieval, before JSON became popular, and it’s where the “X” in AJAX comes from.
In the previous section you learned how to load JSON data using the object XMLHttpRequest; now we’ll see the easy way to do this, using D3!
var doi_url = 'doisigners.json';
d3.json(doi_url, function(json) { console.log(json) } );
D3 packages up all of the testing and parsing in a relatively automatic way, though it still requires that you hand it a function to process the object it creates. In this case we want a function that will turn object data into array data so that it can be handled by D3’s .data() method:
function doi_signers(json)
{
doi_signers = new Array;
for ( var state in json ) doi_signers.push([ state, json[state]]);
d3.select('#doi_table').selectAll('tr')
.data(doi_signers)
.enter().append('tr') // Each tr now has a copy of its own state data
.selectAll('tr')
.data(Array) // Necessary now to get .enter() method
.enter().append('td').text(function(state) { return state[0]; });
}
d3.json(doi_url, doi_signers);
Above we are again just displaying the imported object json in the console.
To do the previous job of writing into the document, we can use a slightly simpler function than before:
function processSigners(doi_signers) {
var doi_rows = '\n';
for (state in doi_signers)
{
doi_rows += '<tr><td>' + state + ":</td><td>";
names = doi_signers[state];
for (n = 0; n < names.length - 2; n++)
doi_rows += names[n] + '<br />';
doi_rows += names[n] + '</td></tr>\n';
}
document.getElementById('doi_table').innerHTML = doi_rows;
};
Node by itself is a very simple web server, as seen above. But with the addition of an extensive framework of objects, properties, and methods, such as that provided by Express, Node becomes a full-fledged Web server that can be used for just about any application, including processing data submitted from Web forms.
As before, change directory to where you want to build the framework, e.g. your doi folder (if you aren’t there already).
To add Express to your Node server, first install the Express generator:
npm install express-generator
Now, create an Express framework with a name such as doiexpress; this will create a new directory with that name:
express -e doiexpress
Express lets you use templates for your web pages, which means that your web page will be processed on the fly and have predefined information filled into the page. The default template engine in Express is called Jade, which requires that you write your HTML in a completely different way. Instead, we’ll use one called Embedded JavaScript (EJS), chosen with the -e option above, which lets you work with what you’ve learned so far. For example,
Here, the template engine running inside of Node will test for the presence of the variable user (it could be data pulled from a database), and if it’s present will insert the value user.name in the <h2> element. All code between <% and %> will then be removed, and the resulting Web page will be sent to the browser.
In the doiexpress directory you will need to edit the main Express application, app.js, with your normal text editor, so that it will use EJS instead of Jade when it sees a template with the .ejs extension:
You will also need to list EJS in Express’ configuration file, which is in package.json. While we’re at it, you should also add listings for the database driver for MongoDB and a related usability layer called Monk:
Now we can tell NPM to install all of these that aren’t already present, since by default it looks in files named package.json:
cd doiexpress
npm install
Finally, make a folder for your MongoDB data:
mkdir data
Looking at the doiexpress directory, you’ll see:
app.js bin data node_modules npm-debug.log package.json public routes views
The directory bin contains “middleware” applications that you might wish to include (we won’t) (the name comes from the fact that originally applications were always “binary” or non-text files).
The directory node_modules contains the new packages we’ve installed for Express.
The file npm-debug.log if present, will contain error messages.
The directory public contains static files such as images, scripts, and stylesheets.
The directory routes contains javascript that sets up handling for particular locations on your Web site.
The directory views contains your main web page files that are typically template files.
Example: routes/index.js contains the code for the index page:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
The reference to index is a reference to views/index.*, which by default is written in Jade but which we can replace with EJS:
<!DOCTYPE html> <html>
<head>
<meta charset="utf-8">
<title><%= title %></title>
<link href="stylesheets/style.css" rel="stylesheet">
</head>
<body>
<h1>Hello, World!</h1>
<h2>Welcome to <%- title %>!</h2>
</body>
</html>