Home > Articles > Home & Office Computing > The Web/Virtual Worlds/Social Networking

Developing HTML5 Applications, Part 3

HTML5 is a hot topic these days, and you’ll probably want to create your own HTML5 applications. If these applications are to be useful, they should work in a wide variety of browsers. Author and web developer Jeff Friesen concludes a three-part series that focuses on developing portable HTML5 applications. Specifically, he shows you how to design a utility-oriented application that detects the current browser and adapts to its HTML5 limitations. He also introduces you to File API: Writer and discusses error handling.

Editor's Note: Don't miss Part 1 and Part 2 of this 3-part series.

Like this article? We recommend

HTML5 has captured the attention of many web developers because of its exciting features for creating powerful browser-based applications. Imagine creating a smartphone application out of nothing more than HTML, JavaScript, CSS, and DOM, and have this application run in the smartphone's HTML5-compliant browser. Those developers not wanting to learn a smartphone technology (such as Apple's iOS or Google's Android) should find HTML5 to be an attractive (and simpler) alternative for creating smartphone apps.

Unfortunately, HTML5 is far from being finished—it will be years before this specification is finalized—and the various browsers that claim HTML5 support are inconsistent in how well they support various HTML5 features. Although this lack of consistency makes it difficult to create portable HTML5 applications, it's not an insurmountable problem, and this challenge to creating portable HTML5 applications is the impetus behind this article and its predecessors.

This article wraps up my three-part Developing HTML5 Applications series, which focuses on designing portable HTML5 applications. Part 1 provided an overview of HTML5, emphasizing features usdd by the applications presented in Parts 2 and 3 of this series. It also discussed testing these features in various browsers, and presented a JavaScript-based browser-identification library to help in writing portable HTML5 applications. Part 2 focused on design in the context of an example application called Animated Starry Diorama.

Part 3 continues to focus on design, but does so in the context of Online Image Processor (OIP, for short), a utility-oriented application whose only HTML5 dependencies are its canvas and File API features. After introducing OIP and focusing on its design, the article examines an HTML5 companion API to the File API for saving content to files in the local filesystem, and discusses various error scenarios that might arise when running OIP or other HTML5 applications.

Introducing Online Image Processor

As its name suggests, Online Image Processor is an HTML5 application that performs image-processing operations on loaded images. For brevity, OIP supports only convert-to-grayscale and sepia tone operations. It also supports an undo feature to restore the original image.

You can try out OIP by pointing your browser to my website. Alternatively, you can access this application from this article's accompanying code file. Figure 1 shows you what OIP's user interface looks like on Firefox.

Figure 1 The Firefox browser reveals that OIP has sepia-toned an image. Click to enlarge.

When the canvas cannot completely display an image, you can view different parts of the image by pressing a mouse button and dragging in an appropriate direction. Because you cannot drag past the viewer's dimensions, you might need to perform multiple drags.

If you run OIP from the accompanying code file on Chrome, you'll see a similar user interface. However, because of security restrictions (discussed later), you cannot load an image. This problem doesn't occur when you point your Chrome browser to my website URL for OIP.

In contrast to Firefox and Chrome, Safari's and Opera's support for HTML5's File API is much more limited. As a result, it's not possible to load an image in the same manner. Figure 2 shows you the web page that you'll initially encounter when you run OIP with Safari or Opera.

Figure 2 Select a file via the HTML form and click the Submit button. Click to enlarge.

Safari and Opera present a simple web page whose HTML form lets you choose an image file and then click the Submit button. Clicking this button uploads the selected image file to my server, where a PHP script saves the file and generates the web page shown in Figure 3.

Figure 3 You can perform the same image-processing operations from this web page. Click to enlarge.

Click the Save button when visible (it's not shown for Chrome, which doesn't support saving an image) to save the processed image to a file. Because there's no way to specify a filename with Firefox or Safari, the file is saved with a randomly generated filename.

Application Design

OIP is composed of OIP.html, OIP.js, OIP_kf.php, and OIP_uf.php, which collectively define this application. I organized OIP around the first two files to simplify maintenance. The latter two files are needed to support Opera and Safari.

OIP.html provides the necessary HTML structure and a script for integrating this application into a browser. Listing 1 presents the contents of this HTML file.

Listing 1—OIP.html

<html>
  <head>
    <title>
      Online Image Processor
    </title>
  </head>

  <body>
    <script type="text/javascript" src="BrowserDetect.js">
    </script>

    <script type="text/javascript">
      var version;
      if (((version = getFirefoxVersion()) != null &&
           cmp(version, "3.6") >= 0) ||
          ((version = getChromeVersion()) != null &&
           cmp(version, "6.0.472.63") >= 0))
      {
         document.write("<div style='text-align:center'>");
         document.write("<input type='file' id='input'");
         document.write("onchange=\"loadImage(this.files); "+
                        "document.getElementById('input').value = '';\">");
         document.write("<\/div>");
         document.write("<p>");
         var WIDTH = 640;
         var HEIGHT = 480;
         document.write("<div style='text-align: center'>");
         document.write("<canvas id='vcanvas' width='"+WIDTH+"' "+
                        "height='"+HEIGHT+"'><\/canvas>");
         document.write("<\/div>");
         document.write("<script type='text\/javascript' "+
                        "src='OIP.js'><\/script>");
         document.write("<p>");
         document.write("<div style='text-align: center'>");
         document.write("<select id='processor_list' "+
                        "onchange='process()'>");
         document.write("<option selected value='undo'>");
         document.write("Undo");
         document.write("<\/option>");
         document.write("<option value='ctgs'>");
         document.write("Convert to grayscale");
         document.write("<\/option>");
         document.write("<option value='st'>");
         document.write("Sepia tone");
         document.write("<\/option>");
         document.write("<\/select>");
         if (getFirefoxVersion() != null)
            document.write(" <input type='submit' value='save' "+
                           "onclick='saveToFile()'>");
         document.write("<\/div>");
      }
      else
      if (((version = getSafariVersion()) != null &&
           cmp(version, "5.0.2") >= 0) ||
          ((version = getOperaVersion()) != null &&
           cmp(version, "10.62") >= 0))
      {
         document.write("<form action='http:\/\/tutortutor.ca\/cgi-bin"+
                        "\/OIP_uf.php' method='post' "+
                        "enctype='multipart\/form-data' "+
                        "style='text-align: center'>");
         document.write("<label for='file'>Filename: <\/label>");
         document.write("<input type='file' name='file' id='file'>");
         document.write("<br>");
         document.write("<input type='submit' name='submit' "+
                        "value='Submit'>");
         document.write("<\/form>");
      }
      else
      {
         document.write("<div style='text-align: center'>");
         document.write("Your browser cannot properly execute this "+
                        "HTML5 application.");
         document.write("<\/div>");
      }
    </script>

  </body>
</html>

OIP.html includes the BrowserDetect.js JavaScript library that I presented in Part 1 of this series. This HTML file's small script subsequently calls most of this library's functions to verify that the current browser is capable of supporting the application's HTML5 features.

If the current browser is a recent version of Firefox or Chrome, the script writes the HTML for specifying the canvas element and including OIP.js's source code.

The <input> tag that precedes the canvas element is used to load an image. Loading takes place via the loadImage(this.files) call in this tag's onchange handler.

loadImage(this.files) is followed by document.getElementById('input').value = '' to clear the selected file. Before implementing undo, I used this assignment to force Firefox to reload an image when asked to do so after processing the image.

If the current browser is Opera or Safari, the script writes alternate HTML that describes a file-upload form. The HTML doesn’t also describe a canvas because that task is taken care of by a PHP script.

The file-upload form works with the PHP script located in OIP_uf.php to assist with a file upload. This form includes a pair of necessary items for uploading a file to this script:

  • The <form> tag’s enctype='multipart/form-data' attribute identifies the content type to use when submitting the form. multipart/form-data indicates that the form requires binary data to be uploaded.
  • The <input> tag’s type='file' attribute indicates that the input is to be processed as a file—a browse button appears next to the input field when the form is displayed in a browser.

OIP.js contains the core of this application. This file's JavaScript code consists of several global variables and functions, and a little bit of inline code. Listing 2 presents the contents of this JavaScript file.

Listing 2—OIP.js

// OIP.js
// Obtain viewer canvas object and its context.
var vcanvas = document.getElementById("vcanvas");
var vcontext = vcanvas.getContext('2d');
// Render a solid black viewer canvas -- black is the default fill.
vcontext.fillRect(0, 0, WIDTH, HEIGHT);
// Create original image canvas object and obtain its context.
var oicanvas = document.createElement("canvas");
var oicontext = oicanvas.getContext('2d');
// Create processed image canvas object and obtain its context.
var picanvas = document.createElement("canvas");
var picontext = picanvas.getContext('2d');
var image = new Image();
var dragging;
var dragox;
var dragoy;
var dragdx;
var dragdy;
var prevdragdx;
var prevdragdy;
// Load selected image from first entry in files sequence. When the image is
// loaded, handleReaderLoadEnd() is called to assign the image src to
// image.src. This ultimately results in the function assigned to image.onload
// executing, which initializes variables for performing picture movement on
// the viewer canvas, and renders the loaded image on the original image,
// processed image, and viewer canvases.
function loadImage(files)
{
   vcanvas.onmousemove = null;
   vcanvas.onmousedown = null;
   vcanvas.onmouseup = null;
   if (getSafariVersion() == null && getOperaVersion() == null)
   {
      var file = document.getElementById('input').files[0];
      var reader = new FileReader();
      reader.onloadend = handleReaderLoadEnd;
      reader.readAsDataURL(file);
   }
   else
   {
      image.src = filepath;
   }
   image.onload = function()
   {
      if (typeof(filename) != "undefined")
      {
         var img = new Image();
         img.src = "http://tutortutor.ca/cgi-bin/OIP_kf.php?file="+filename;
      }
      dragging = false;
      dragox = 0;
      dragoy = 0;
      dragdx = 0;
      dragdy = 0;
      prevdragdx = 0;
      prevdragdy = 0;
      vcanvas.onmousemove = doMouseMove;
      vcanvas.onmousedown = doMouseDown;
      vcanvas.onmouseup = doMouseUp;
      document.onmouseup = doMouseUp;
      oicanvas.width = image.width;
      oicanvas.height = image.height;
      oicontext.drawImage(image, 0, 0);
      picanvas.width = image.width;
      picanvas.height = image.height;
      picontext.drawImage(image, 0, 0);
      drawImage();
   }
}
// Convert image to grayscale.
function ctgs()
{
   var id = picontext.getImageData(0, 0, image.width, image.height);
   for (var i = 0; i < id.data.length; i += 4)
   {
      var val = id.data[i]*0.3+id.data[i+1]*0.59+id.data[i+2]*0.11;
      id.data[i] = val;
      id.data[i+1] = val;
      id.data[i+2] = val;
   }
   picontext.putImageData(id, 0, 0);
   drawImage();
}
// Handle mouse movement events by dragging image over viewer canvas if
// dragging is in effect.
function doMouseMove(evt)
{
   if (dragging)
   {
      if (image.width > WIDTH)
      {
         dragdx = evt.screenX-dragox+prevdragdx;
         if (dragdx < 0)
            dragdx = 0;
         else
         if (dragdx+WIDTH > image.width)
            dragdx = image.width-WIDTH;
      }
      if (image.height > HEIGHT)
      {
         dragdy = evt.screenY-dragoy+prevdragdy;
         if (dragdy < 0)
            dragdy = 0;
         else
         if (dragdy+HEIGHT > image.height)
            dragdy = image.height-HEIGHT;
      }
      drawImage();
   }
}
// Handle mouse button pressed events by initiating a drag operation if image
// does not completely fit on visible portion of viewer canvas.
function doMouseDown(evt)
{
   if (image.width < WIDTH && image.height < HEIGHT)
      return;
     
   dragging = true;
   dragox = evt.screenX;
   dragoy = evt.screenY;
}
// Handle mouse button released events to terminate a drag operation.
function doMouseUp(evt)
{
   dragging = false;
   prevdragdx = dragdx;
   prevdragdy = dragdy;
}
// Draw image on viewer canvas.
function drawImage()
{
   // Must clear viewer canvas. If canvas not cleared, if a larger image is
   // displayed, and if a smaller image is then displayed, parts of the larger
   // image will appear.
   vcontext.fillRect(0, 0, WIDTH, HEIGHT);
   vcontext.drawImage(picanvas,
                      dragdx,
                      dragdy,
                      Math.min(image.width, WIDTH),
                      Math.min(image.height, HEIGHT),
                      (image.width < WIDTH) ? (WIDTH-image.width)/2 : 0,
                      (image.height < HEIGHT) ? (HEIGHT-image.height)/2 : 0,
                      Math.min(image.width, WIDTH),
                      Math.min(image.height, HEIGHT));
}
// Respond to the end of the image-load operation that was initiated in
// loadImage(files).
function handleReaderLoadEnd(event)
{
   image.src = event.target.result;
}
// Process image by converting to grayscale or sepia toning image, or undo all
// image-processing operations.
function process()
{
   var op = document.getElementById('processor_list').value;
   if (op == "ctgs")
      ctgs();
   else
   if (op == "st")
      st();
   else
      undo();
}
// Save canvas contents to file with randomly generated filename.
function saveToFile()
{
   var strData = picanvas.toDataURL("image/png");
   document.location.href = strData.replace("image/png",
                                            "image/octet-stream");
}
// Sepia tone image.
function st()
{
   ctgs();
   var id = picontext.getImageData(0, 0, image.width, image.height);
   for (var i = 0; i < id.data.length; i += 4)
   {
      id.data[i] = Math.min(id.data[i]+40, 255);
      id.data[i+1] = Math.min(id.data[i+1]+20, 255);
      id.data[i+2] = Math.max(id.data[i+2]-20, 0);
   }
   picontext.putImageData(id, 0, 0);
   drawImage();
}
// Undo all image-processing operations.
function undo()
{
   var id = oicontext.getImageData(0, 0, image.width, image.height);
   picontext.putImageData(id, 0, 0);
   drawImage();
}

OIP.js begins by declaring several global variables. You typically want to avoid declaring global variables because name conflicts can arise when multiple script files are included. However, OIP avoids this problem because OIP.html includes only a single script file.

The following global variables are declared:

  • vcanvas stores a reference to the canvas element created in OIP.html or OIP_uf.php (discussed later). This viewer canvas serves as a viewport into an image whose dimensions might exceed this canvas’s dimensions. If the entire image is larger than what this canvas can display, OIP lets you drag the image around so that you can view different portions of the image on this canvas.
  • vcontext stores a reference to the viewer canvas’s 2D drawing context so that an image can be rendered on the canvas.
  • oicanvas stores a reference to a background canvas element that stores the entire image. This original image canvas exists to support OIP’s undo feature. The contents of this canvas replace the processed image canvas (discussed later) following an undo operation.
  • oicontext stores a reference to the original image canvas’s 2D drawing context so that an image that’s just been loaded can be rendered on the original image canvas.
  • picanvas stores a reference to a background canvas element that stores a processed image. This processed image canvas exists to support OIP’s convert-to-grayscale and sepia tone image-processing operations. A portion of this canvas is rendered to the viewer canvas following an image-processing operation.
  • picontext stores a reference to the processed image canvas’s 2D drawing context so that the processed image canvas’s contents can be replaced with a processed image or the original image.
  • image stores a reference to a JavaScript Image object that’s used to load the image to be processed and make it available to the OIP script. The image will be loaded from the local filesystem (Firefox or Chrome) or the server (Safari or Opera).
  • dragging is a Boolean variable that indicates whether (true) or not (false) the image is being dragged across the viewer canvas.
  • dragox stores the drag origin’s x value. The drag origin stores the mouse’s screen coordinates when the mouse button is pressed.
  • dragoy stores the drag origin’s y value.
  • dragdx stores a delta x value that’s calculated during each mouse movement. This value identifies the x component of a pixel in the image that should appear in the upper-left corner of the viewer canvas.
  • dragdy stores a delta y value that’s calculated during each mouse movement. This value identifies the y component of a pixel in the image that should appear in the upper-left corner of the viewer canvas.
  • prevdragdx stores the previous dragdx value for use when calculating a new dragdx value. Without this variable, a lengthy drag operation requiring several mouse presses and drags would result in each subsequent drag causing the viewer canvas to display the image such that the image’s upper-left corner coincides with the viewer canvas’s upper-left corner. You would be unable to drag parts of the image into view.
  • prevdragdy stores the previous dragdy value for use when calculating a new dragdy value.

OIP.js next declares several functions, beginning with loadImage(files). This function is responsible for making the user-selected image available to JavaScript via an Image object. loadImage(files) accomplishes the following tasks:

  • Deactivate the viewer canvas’s mouse handlers in case a mouse handler executes concurrently with loadImage(files). I cannot see this happening with the supported browsers in this article because JavaScript is single-threaded. However, should this script support a future browser where this parallelism happens, I want to be prepared for that possibility. (I’ve encountered many weird anomalies and wouldn’t be surprised if this situation was to arise at some point.)
  • If neither Safari nor Opera is the current browser, attempt to load the image and make its URL available to Image object image via the FileReader object named reader and its readAsDataURL(file) method. The handleReaderLoadEnd(event) function that’s assigned to reader’s onloadend property is invoked when image loading finishes—this function assigns the URL that’s stored in event.target.result to image’s src property via image.src = event.target.result;. (I’m not concerned about the scenario where the user chooses a non-image file in this version of the application. I’ll address this possibility later in the article, and I provide a second version of OIP in this article’s code archive that takes this possibility into account.)
  • However, if Safari or Opera is the current browser, the image is loaded via an image.src = filepath; assignment. I’ll explain where the filepath global variable comes from when I talk about PHP later on.

  • An anonymous function is assigned to image’s onload property. This function is called when the image is loaded and takes care of several tasks. The first task is to interact with a PHP script stored in an OIP_kf.php file—I’ll discuss this task later when I present the PHP portion of this application. The second task resets the image-dragging variables in anticipation of the user dragging a large image across the viewer canvas. The third task assigns mouse handlers to the viewer canvas and the enclosing document to support image dragging—the document.onmouseup = doMouseUp; assignment addresses the situation where the user releases the mouse button when the mouse cursor is not over the viewer canvas. The remaining tasks render the loaded image onto the original image canvas (to support undo), render the loaded image onto the processed image canvas (which gets copied to the viewer canvas), and call the drawImage() function to perform this copy so that (at least part of) the image is displayed.

The process(), ctgs(), st(), and undo() functions take care of image processing, as follows:

  • process() is the entry-point function into the image-processing portion of the script. It’s called from the <select> tag’s onchange handler (see Listing 1) and calls one of the other three functions based on the value that’s been selected by the user.
  • ctgs() performs the convert-to-grayscale operation. It first calls the processed image canvas context’s getImageData(x, y, width, height) method to obtain an array of the image’s RGB bytes. It then iterates over this array (taking into account that each pixel corresponds to 4 bytes—alpha is included), performing a grayscale operation on each pixel by multiplying each corresponding byte by a floating-point value. When the iteration finishes, ctgs() calls the complementary putImageData(imagedata, x, y) method to render the changed contents onto the processed image canvas. The drawImage() function is subsequently called to copy (at least part of) the processed image canvas to the viewer canvas, so that the user can see the resulting grayscaled image.
  • st() performs the sepia tone operation. It behaves in a similar fashion to ctgs(), but first calls this function to convert the image to grayscale prior to carrying out the sepia tone logic.
  • undo() cancels all previously performed image-processing operations by copying the contents of the original image canvas to the processed image canvas. It then calls drawImage() to make the original image visible to the user.

The final function that I'll discuss is saveToFile(), which is responsible for saving the contents of the processed image canvas to a file. Although the technique I've chosen for saving the file is simple (two lines), it's not very good and is not supported by Chrome.

The first line, var strData = picanvas.toDataURL("image/png");, calls the processed image canvas context's toDataURL(string) method to return a string containing a Base64-encoded PNG representation of the image's contents.

The second line, document.location.href = strData.replace("image/png", "image/octet-stream");, tells the browser to present a dialog box for saving the image. Unlike Firefox and Safari, Opera lets the user choose a storage location and enter a filename.

The PHP Alternative

I previously mentioned that OIP_uf.php is one of two PHP files needed to support the Opera and Safari browsers. When the user launches OIP from one of those browsers, selects a file via the resulting form, and clicks Submit, Listing 3's OIP_uf.php PHP script file executes.

Listing 3—OIP_uf.php

#!/usr/bin/php
<?php
   $type = $_FILES["file"]["type"];
   if (($type == "image/gif" || $type == "image/jpeg") && 
       ($_FILES["file"]["size"] < 3000000))
   {
      if ($_FILES["file"]["error"] > 0)
      {
         echo "Error: ".$_FILES["file"]["error"]."<br>";
      }
      else
      {
         $name = basename($_FILES["file"]["tmp_name"]);
         $name = $name . (($type == "image/gif") ? ".gif" : ".jpg");
         move_uploaded_file($_FILES["file"]["tmp_name"],
                            "/home/jfriesen/public_html/temp/".$name);
         echo "<h1 style='text-align: center'>";
         echo "Online Image Processor";
         echo "</h1>";
         echo "<div style='text-align: center'>\n";
         echo "<canvas id='vcanvas' width=640 height=480>\n";
         echo "</canvas>\n";
         echo "<script type='text/javascript' ".
              "src='/common/BrowserDetect.js'>\n";
         echo "</script>\n";
         echo "<script type='text/javascript'>\n";
         echo "var WIDTH = 640;\n";
         echo "var HEIGHT = 480;\n";
         echo "</script>\n";
         echo "<script type='text/javascript' src='/software/OIP/OIP.js'>\n";
         echo "</script>\n";
         echo "<script type='text/javascript'>\n";
         echo "var filename = '".$name."';\n";
         echo "var filepath = 'http://tutortutor.ca/temp/".$name."';\n";
         echo "loadImage(null);\n";
         echo "</script>\n";
         echo "<p>\n";
         echo "<div style='text-align: center'>\n";
         echo "<select id='processor_list' onchange='process()'>\n";
         echo "<option selected value='undo'>\n";
         echo "Undo\n";
         echo "</option>\n";
         echo "<option value='ctgs'>\n";
         echo "Convert to grayscale\n";
         echo "</option>\n";
         echo "<option value='st'>\n";
         echo "Sepia Tone\n";
         echo "</option>\n";
         echo "</select>\n";
         echo "&nbsp;<input type='submit' value='save' onclick='saveToFile()'>";
         echo "</div>\n";
      }
   }
   else
   {
      echo "Invalid file!";
   }
?>

Listing 3 begins with the #!/usr/bin/php preamble to identify the source file as a PHP file to my web server.

Listing 3 first accesses the MIME type ($type = $_FILES["file"]["type"]) of the uploaded file. It then verifies that this type is one of image/gif or image/jpeg—feel free to support additional image types. If verification fails, the PHP script outputs Invalid file!.

Because files are temporarily stored on my server, I've chosen to limit the maximum length of the image file that can be uploaded to 2,999,999 bytes. If the user uploads a file with a larger length, the script outputs Invalid file!.

Moving on, the script checks for any error that occurred during the upload ($_FILES["file"]["error"] > 0), and outputs a message identifying this error if the file could not be uploaded successfully. If there was no error, the script's real work begins.

The script obtains the uploaded file's temporary name ($name = basename($_FILES["file"]["tmp_name"])) and extension, and moves this file to my /home/jfriesen/public_html/temp/ directory so that it will hang around just long enough for OIP.js to access it.

The script then outputs HTML that's similar to Listing 1's HTML that's written when Firefox or Chrome is detected. I don't escape forward slashes like I do when outputting HTML with JavaScript (no <\/canvas> tag, for example) because this escaping isn't necessary in PHP.

Look carefully at Listing 3's HTML-generation code and you'll discover JavaScript filename and filepath global variable declarations. These variables identify the name of the temporary file without and with its http://tutortutor.ca/temp/ prefix, respectively.

Once an uploaded file is moved to /home/jfriesen/public_html/temp/, it remains on the server until deleted, which takes up server space. The temporary file should be deleted after OIS.js has loaded the file via the image object.

To solve this problem, I place the following code fragment at the start of the anonymous function assigned to image's onload property (see Listing 2):

if (typeof(filename) != "undefined")
{
   var img = new Image();
   img.src = "http://tutortutor.ca/cgi-bin/OIP_kf.php?file="+filename;
}

The if statement executes after the image has loaded, and prevents the rest of the code from executing in a Firefox or Chrome context. Instead, the code executes on Safari or Opera because only these browsers cause Listing 3's PHP script to run and declare global variable filename.

The code guarded by the if statement creates an Image object and uses this object to run the PHP script in OIP_kf.php (see Listing 4), which kills the temporary file (identified via filename). This code works because Image objects make HTTP GET requests.

Listing 4—OIP_kf.php

#!/usr/bin/php
<?php
   unlink("/home/jfriesen/public_html/temp/".$_GET['file']);
?>

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020