Developing HTML5 Applications, Part 1
HTML5 is a hot topic these days, and justifiably so because this next major revision of the HTML standard has much to offer for developing applications that run in the web browser. As the HTML5 specification and browser implementations of this specification mature, developers experienced in HTML, JavaScript, CSS, and DOM will be able to create powerful HTML5 applications.
Although the HTML5 specification is years away from being finalized, you can start to explore its goodies now because recent versions of major browsers (such as Mozilla Firefox) are already implementing many HTML5 features. Unfortunately, the level of support for various features varies from browser to browser, making it difficult to write portable HTML5 applications that take advantage of these features.
This article launches a three-part series that focuses on developing portable HTML5 applications. Part 1 presents an overview of HTML5, emphasizing features used by the larger applications revealed later in this series. It then tests these features in various browsers to determine browser support, and introduces a JavaScript library whose browser-identification functions help in writing portable HTML5 applications.
HTML5 Overview
HTML5 is the successor to the current HTML 4.01 standard. Its history dates back to June 2004, at which time the Web Hypertext Application Technology Working Group (WHATWG) began to work on HTML 4.01's successor under the name Web Applications 1.0. The World Wide Web Consortium (W3C) subsequently adopted (in 2007) WHATWG's specification as a starting point for HTML5.
The W3C's HTML5: A Vocabulary and Associated APIs for HTML and XHTML document provides the latest working draft of the HTML5 specification. If you find this lengthy reference difficult to process, check out Wikipedia's HTML5 entry for an abbreviated overview of what sets HTML5 apart from its predecessors.
HTML5 has much to offer; some of its many features are described in the following list:
- A new audio element that represents a sound or audio stream.
- A new canvas element that provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics, or other visual images on the fly.
- A new nav element representing a section of a page that links to other pages or to parts within the page.
- A new video element for playing videos or movies.
- An API for selecting files from the local filesystem and reading all or part of their datasee the W3C's companion File API document for more information.
- An event-based drag-and-drop mechanism.
- Support for microdata, which offers a simple way to embed semantic (meaningful) markup into HTML documentssee the W3C's companion HTML Microdata document for more information.
- Support for spelling and grammar checking.
- Support for web storage, which makes it possible to store structured data in key/value pairs on the client sidesee the W3C's companion Web Storage document for more information.
- The elimination of the center and font elements because their effects can be achieved through CSS.
The need for brevity prevents me from exploring all of the new features being introduced by HTML5. Instead, I focus only on the aforementioned audio, canvas, and File API features in the following sections, because I take advantage of these features in the HTML5 applications presented in the second and third parts of this series.
Audio
The audio element is represented in HTML via the <audio> and </audio> tags. Furthermore, the <audio> tag is defined to accept the following attributes:
- src: A string attribute that must specify a valid nonempty URL (possibly surrounded by spaces) of an audio resource.
- preload: An enumerated attribute that specifies whether or how the audio is preloaded; its value is one of "none" (do not preload the audio), "metadata" (preload metadata only), or "auto" (preload the entire audio resource if possible).
- autoplay: A Boolean attribute that (when present) indicates automatic playback as soon as enough audio content has been downloaded so that playback can begin without stopping.
- loop: A Boolean attribute that (when present) indicates that the audio should be replayed as soon as it finishes.
- controls: A Boolean attribute that (when present) indicates that a user interface should be displayed so that the user can begin playback, pause playback, seek to an arbitrary position in the audio, change the volume, and so on.
Listing 1 codifies an audio element via the <audio> tag and its src and controls attributes. This listing's audio element plays an MP3-based audio resource of American composer Samuel Barber's Adagio for Strings composition.
Listing 1AudioDemo.html
<html> <head> <title> Audio Demo </title> </head> <body> <audio src="http://lemalapo.20minutes-blogs.fr/media/01/01/111491075.mp3" controls> </audio> </body> </html>
The presence of the controls attribute causes the browser to render an appropriate user interface for playing and controlling music play. Figure 1 reveals this user interface in the context of the Google Chrome browser.
Figure 1 Chrome's user interface lets you play and control the play of an audio resource.
It's possible to programmatically play and control audio resources by using JavaScript's Audio type, which provides access to the properties and methods of the HTMLAudioElement DOM interface (a subinterface of HTMLMediaElement). Listing 2 provides a demonstration.
Listing 2Playing Audio via JavaScript and the DOM
<script type="text/javascript"> var audio = new Audio(); audio.src = "http://lemalapo.20minutes-blogs.fr/media/01/01/111491075.mp3"; audio.play(); </script>
After constructing an Audio object via the Audio constructor, Listing 2 assigns the same URL to this object's src attribute as was specified in Listing 1. It then invokes Audio's play() method to start playing the audio resource.
Canvas
The canvas element is represented in HTML via the <canvas> and </canvas> tags. Furthermore, the <canvas> tag is defined to accept the following attributes:
- width: A valid nonnegative integer attribute that specifies the canvas's width in pixels; if this attribute is missing, a default value of 300 pixels is used.
- height: A valid nonnegative integer attribute that specifies the canvas's height in pixels; if this attribute is missing, a default value of 150 pixels is used.
Listing 3 codifies a canvas element via the <canvas> tag; its width and height attributes are set to 400 and 300 pixels, respectively. This listing also provides a script that accesses and draws on the canvas.
Listing 3CanvasDemo.html
<html> <head> <title> Canvas Demo </title> </head> <body> <canvas id="mycanvas" width="400" height="300"> The HTML5 canvas element is not supported by your browser. </canvas> <script type="text/javascript"> var canvas = document.getElementById("mycanvas"); var context = canvas.getContext("2d"); context.fillRect(0, 0, 400, 300); // Render a black-filled rectangle // over the entire canvas. context.fillStyle = "rgb(255, 0, 0)"; // Specify red as the fill color. context.fillRect(30, 30, 340, 240); // Render a red-filled rectangle // leaving a 30-pixel black border. </script> </body> </html>
Notice the text that appears between <canvas> and </canvas>. If a browser does not support the canvas element (Internet Explorer 8 is one example), the HTML located between these tags is rendered instead.
By itself, a canvas cannot generate graphics: It only defines the rectangular region on which graphics can be rendered. JavaScript (or another scripting language) is required to programmatically render graphics.
A script's first task is to obtain the DOM canvas object that corresponds to the <canvas> tag's id attribute. It accomplishes this task by calling the DOM's document object's getElementById(string) method with id's value as the method argument, as follows:
var canvas = document.getElementById("mycanvas");
After obtaining the canvas object, the script must obtain a drawing context for drawing on the canvas. It accomplishes this task by calling the getContext(string) method on the returned canvas object, as follows:
var context = canvas.getContext("2d");
This method assigns a nonnull object to context if the two-dimensional canvas API is supported. Otherwise, getContext() returns null. However, if a browser (such as Internet Explorer 8) doesn't support canvas, there is no getContext() method and the result is undefined.
Assuming that a nonnull context is returned, you can access various context properties (such as fillStyle, specify the current fill color, which defaults to black) and call various methods (such as fillRect(), draw a filled rectangle in the current fill style).
When you run this HTML5 application in a browser that supports the canvas element, you should observe the rendered canvas that appears in Figure 2.
Figure 2 Opera renders a red-filled rectangle over a black-filled rectangle.
File API
The File API makes it possible to select files in the local filesystem and read file content subject to the security considerations discussed in the W3C's File API document. This API consists of the following components:
- A FileList property of the HTMLInputElement DOM interface that stores the sequence of files selected by the user from the local filesystem as an array of File objects.
- A Blob DOM interface that represents raw binary data and allows access to ranges of bytes within the Blob object; this interface exposes a readonly size property that stores the size of a Blob object in bytes, and a slice(start, length) method that returns a new Blob object whose byte sequence begins at offset start and ends at offset start+length-1 within the current Blob object.
- A File DOM interface that extends Blob and describes a single file in the FileList sequence; this interface exposes readonly name, type, and urn properties that store the name of the file without its path, the ASCII-encoded MIME type of the file, and the file's uniform resource nameurn might be renamed in the future.
- A FileReader DOM interface that provides a noargument constructor, methods to asynchronously read a file (readAsBinaryString(blob) to read the file represented by the blob argument, for example), readonly properties (such as result, which stores the read file content in a string), and event handlers (such as onloadendthe assigned function is called when the read operation completes).
- A FileError DOM interface for reporting errors originating from the asynchronous FileReader file-reading methods; FileReader provides a readonly error property of type FileError that stores the error code.
- A FileReaderSync DOM interface that provides a noargument constructor, and methods to synchronously read a file (readAsDataURL(file) to read the file represented by the file argument, for example); apart from returning strings that contain file content, FileReaderSync's file-reading methods have the same method signatures as their FileReader counterparts.
- A FileException exception DOM interface for reporting errors originating from the synchronous FileReaderSync file-reading methods; this exception is thrown in lieu of FileReaderSync providing an error property.
The File API is much easier to use than it might first appear. For example, Listing 4 provides a demonstration of the FileList, File, and FileReader portions of the API.
Listing 4FileAPIDemo.html
<html> <head> <title> File API Demo </title> </head> <body> <input id="files list" type="file" multiple onchange="dumpFileInfo()"> <p> <div id="output"> </div> <script type="text/javascript"> var files; function dumpFileInfo() { files = document.getElementById("files list").files; var output = document.getElementById("output"); while (output.childNodes.length != 0) output.removeChild(output.firstChild); var table = document.createElement("table"); table.setAttribute("border", "4"); table.setAttribute("cellPadding", "5"); var tHead = document.createElement("tHead"); table.appendChild(tHead); table.tHead.appendChild(document.createElement("tr")); var td = document.createElement("td"); td.appendChild(document.createTextNode("Name")); table.tHead.rows[0].appendChild(td); td = document.createElement("td"); td.appendChild(document.createTextNode("Size")); table.tHead.rows[0].appendChild(td); var tbody = document.createElement("tbody"); table.appendChild(tbody); for (var i = 0; i < files.length; i++) { table.tBodies[0].appendChild(document.createElement("tr")); td = document.createElement("td"); var link = document.createElement("a"); link.setAttribute("href", "javascript: dumpFile("+i+")"); link.appendChild(document.createTextNode(files[i].name)); td.appendChild(link); table.tBodies[0].rows[i].appendChild(td); td = document.createElement("td"); td.appendChild(document.createTextNode(files[i].size)); table.tBodies[0].rows[i].appendChild(td); } output.appendChild(table); output.appendChild(document.createElement("p")); } function dumpFile(i) { var fr = new FileReader(); fr.onabort = function() { alert("abort"); } fr.onerror = function() { var msg = ""; switch (fr.error) { case FileError.NOT_FOUND_ERR: msg = "not found"; break; case FileError.SECURITY_ERR: msg = "security violation"; break; case FileError.ABORT_ERR: msg = "abort"; break; case FileError.NOT_READABLE_ERR: msg = "unreadable"; break; case FileError.ENCODING_ERR: msg = "bad encoding"; break; default: msg = "unknown"; } alert(msg); } fr.onload = function() { alert("load"); } fr.onloadstart = function() { alert("load start"); } fr.onloadend = function() { alert("load end"); if (output.lastChild.nodeName == "PRE") output.removeChild(output.lastChild); var pre = document.createElement("pre"); output.appendChild(pre); var size = Math.min(128, files[i].size); for (var j = 0; j < size; j++) { var hex = fr.result.charCodeAt(j).toString(16); if (hex.length == 1) hex += "0"; pre.appendChild(document.createTextNode(hex)); pre.appendChild(document.createTextNode(" ")); if (j%16 == 15) { pre.appendChild(document.createElement("br")); } } } fr.onprogress = function() { alert("progress"); } fr.readAsBinaryString(files[i]); } </script> </body> </html>
The <input id="files list" type="file" multiple onchange="dumpFileInfo()"> tag lets the user select one or more files via a file chooser dialog. The ability to select more than one file is indicated by the presence of Boolean attribute multiple.
The browser processes this tag by rendering a text field to display the selected file(s) and a button for activating the file chooser. The selection of a new file causes the JavaScript function assigned to the onchange attributedumpFileInfo()to run.
dumpFileInfo() begins by accessing the sequence of selected files via expression document.getElementById("files list").files:
- document.getElementById("files list") uses the DOM to obtain the input element.
- .files returns a FileList instance that contains this element's files sequence; the first file is stored in files[0].
Much of the code in dumpFileInfo() is occupied with dynamically creating an HTML table containing selected filenames and their sizes. I chose to use a table so that I could nicely align the values in the size column.
The File API-related code consists of a for loop where i ranges from 0 through files.length, files[i].name (access the ith entry in the files property and return its name field), and files[i].size (access the ith entry in the files property and return its size field.
Each filename is converted into a link that causes the dumpFile(i) function to run when the link is clicked; i is a zero-based index into the files property's array, and its value corresponds to the clicked filename.
After instantiating FileReader via var fr = new FileReader();, dumpFile(i) assigns anonymous functions to this object's various event handler properties. It then executes fr.readAsBinaryString(files[i]); to read the chosen file's contents.
The file is read asynchronously on a background thread. When this operation completes successfully, the file's contents are stored in the FileReader object's result field, and the function assigned to onloadend executes.
After removing the previous pre element (if present), onloadend() converts no more than the first 128 bytes of the content stored in result to hexadecimal via expression fr.result.charCodeAt(j).toString(16), and adds these bytes to a dynamically created pre element.
To try out this application for yourself, start up a more recent version of the Firefox or Chrome browser. Figure 3 reveals this application in a Firefox context.
Figure 3 Firefox reveals the File API's usefulness for selecting and reading local files.