Man on track in running gear jumping over hurdle

Start Using HTML5 in Your Web Apps -- Today!

You can include HTML5 features in your web apps -- even if they run on older browsers

HTML5 is the direction for web-based applications. All you have to do is listen to the material from any technical conference or keep an eye on web developer online community discussions to know that HTML5 is important. Each day, I read about new and exciting HTML5 features and uses for those features -- witness the many new features in the latest versions of Internet Explorer (IE), Chrome, and Firefox.

Mobile application development is definitely where HTML5 has gotten its start, but HTML5 is not limited to mobile. Here, I will build on the information in "HTML5 for the ASP.NET Developer" along with many other great articles on HTML5 published in Dev Pro and delve into some of the HTML5 features that are available today and that you can use immediately to provide solutions for your customers now. In this article, we'll see what can be done in ASP.NET running in a modern web browser, such as Chrome, Firefox, Safari, and the recently released IE10.

Drag and Drop to Upload Files

Who isn't familiar with the concept of dragging an object from one place in a computer UI and dropping it somewhere else on screen, resulting in an action occurring? This capability has been around for a long time in Windows, Mac, and other operating systems. Although this fact isn't widely known, browser support for drag and drop is available for web apps. You may have seen this in Google+. Going further, there is also the ability to upload files via a drag-and-drop visual interface. I have found that drag and drop for file uploading works in the most recent versions of Chrome, Firefox, and IE10. Unfortunately, this means that IE9 and earlier don't support the necessary APIs for file upload.

The first step in building a drag-and-drop file-upload capability into your web application is to assemble the necessary libraries. We'll use the following libraries:

  • Modernizr: a library that allows a program to test the browser for capabilities
  • jQuery: We'll use jQuery for its ability to modify the document object model (DOM) in browsers.

The first step in creating the file-upload capability is to create a couple of divs that will be used for our file upload. Figure 1 shows the HTML that accomplishes this. In this example, we have two divs. The dropArea div will be used to accept drops. The oldUpload div will be used for browsers that don't support the necessary APIs to allow for the user to upload files via drag and drop.

Upload files:

The next step is to determine whether the browser supports the necessary drag-and-drop APIs along with the browser-based file-reading APIs. This is done by using the following code:

var supported = ((Modernizr.draganddrop) &&
    (typeof (window.FileReader) != "undefined"));

This code uses the Modernizr library to determine whether the browser supports the necessary drag-and-drop interface. If the browser in use supports drag and drop, the value true is returned. The next step is to check the browser for file-reader support. Once we have determined whether the browser supports drag and drop and file-reading APIs, our code will then really get going.

Figure 2 shows the sample code for determining whether the application should display the drag-and-drop file user interface or the old-style upload interface.

dz = $("#dropArea");
dropZone = dz[0];
dz.removeClass("error");
isdnds = IsDragAndDropFileUploadSupported();
old = $("#oldUpload");
if (isdnds) {
    dz.text(startUpText);
    old.hide();
}
else {
    dz.hide();
}

In this code, we need to get references to the dropArea div and the oldUpload div area. If the browser supports drag and drop as well as the FileReader APIs, the oldUpload div area is hidden. If the browser does not support drag and drop and the FileReader APIs, the dropArea div is hidden. Figure 3 shows how the upload interface will appear, depending on browser type.

Figure 3: File-upload interface for older and newer browsers
Figure 3: File-upload interface for older and newer browsers

Drag and Drop

HTML5's drag and drop is a powerful API for enabling users to copy, move, and delete items with just a few mouse clicks. Drag and drop provides a set of events that a developer's JavaScript code can monitor and respond to. These events are

  • drag: an event that is fired during the drag operation
  • dragend: an event that is fired when a drag operation is finished; this event typically occurs when the mouse button is released or the Esc key is pressed
  • dragenter: an event that occurs when the mouse is moved over an element while a drag is currently underway
  • dragleave: occurs when the mouse is moved over an element while a drag is currently underway
  • dragover: occurs when the mouse is moved over an element while a drag is currently underway
  • dragstart: an event that is fired when a user begins dragging an object
  • drop: an event that occurs when the dragged elements are dropped/released onto an element that accepts data

Your next question might be, What types of elements can accept drag/drop events? Elements that can accept these drag-and-drop events include editable controls and content areas, such as the

tag.

The next step is to set up the events for dragging and dropping files into a web browser, as shown in the code in Figure 4.

// Alter text and add a drag effect
dropZone.ondragover = function () {
    if (isdnds) {
    dz.addClass("hover");
    dz.text("Drop files here to watch the processing start.");
    }
    return false;
};
// Update the text
dropZone.ondragleave = function () {
    if (isdnds) {
    dz.text("Drop files here to begin processing.");
    }
}

// Remove the drag effect when stopping our drag
dropZone.ondragend = function () {
    if (isdnds) {
    dz.removeClass("hover");
    dz.text(startUpText);
    old.show();
    }
    return false;
};
// The drop event handles file upload.
dropZone.ondrop = function (event) {
    // Stop the browser from opening the file in the window
    event.preventDefault();
    if (isdnds) {
    dz.removeClass("hover");
    // Get the file and the file reader
    iFiles = event.dataTransfer.files.length;
    files = event.dataTransfer.files;
    var i = 0;
    var strOut = "";
    for (i = 0; i < iFiles; i++) {
        // Validate file size
        if (files[i].size > @MaxFileSize) {
        strOut += "File name:" + files[i].name +
        " is too large to upload. File size:" + files[i].size + ". ";
        }
    }
    if (strOut != "") {
        dz.text(strOut);
        dz.addClass("error");
        return false;
    }
    fileUploadMethod(0);
    }
};

These events are set up:

  • dragover: used to instruct the user what to do when the mouse is positioned over the dropArea
  • dragleave: used to provide additional instructions to the user when the mouse leaves the dropArea but is still in drag mode
  • drop: When the drop event occurs within the dropArea, this is where the fun begins; this event is called when the drop event occurs within the dropArea

The first line of the ondrop event method in Figure 4 is to call the .preventDefault(). This method will stop the browser from opening the file(s), which is the default action. We don't want the code to open the files, merely to send the files to the browser.

The next step is to note that we can upload multiple files with one drag. Because of this, the event.dataTransfer.files object is actually an array of file objects in what is called a FileList. When we get the files, we need to verify that the files are not too big to upload. As such, we can loop through the files and get information about these files -- for example, their size. We'll test the file size against the maximum size allowed for uploading files on the web server. If any file is too large, we won't perform an upload; otherwise we'll start the upload in the method fileUploadMethod, shown in Figure 5.

function fileUploadMethod(fileToUpload) {
    try {
    var file = files[fileToUpload];
       
    // Send the file
    var xhr = new XMLHttpRequest();
    xhr.upload.addEventListener("progress", uploadProgress, false);
    xhr.onreadystatechange = stateChange;
    xhr.open("POST", '@Href("~")UploadHandler.ashx', true);
    xhr.setRequestHeader("X-FILE-NAME", file.name);
    xhr.send(file);
    }
    catch (exc) {
    alert("exception: " + exc);
    }

}
// Show the upload progress
function uploadProgress(event) {
    var percent = parseInt(((event.loaded / event.total) * ( 1 / iFiles) +
    ( currentFile / iFiles) )* 100);
    dz.text("Uploading: " + percent + "%");
}
// Show upload complete or upload failed depending on result
function stateChange(event) {
    if (event.target.readyState == 4) {
    if (event.target.status == 200 || event.target.status == 304) {
        if ( currentFile == (iFiles - 1) ) {
        dz.text("Upload Completed! Upload some more files.");
        }
        else{
        currentFile++;
        fileUploadMethod(currentFile);
        }
    }
    else {
        dropZone.text('Upload Failed!');
        dropZone.addClass('error');
    }
    }
}

Remember when we got the array of files to be uploaded? The file array was saved off in a page-level object. Our code will pass in a reference to the first element in the array (the zeroth element). The code will then get it and manually create an XMLHttpRequest object. If you have peeked ahead in this article, you may be wondering why this is sent when the server-side code to accept the upload looks for the file's ContentType. The reason is that the ContentType does not always come through. For example, the current version of the Chrome browser and the preliminary developer version of IE10 don't seem to expose the ContentType, whereas Firefox seems to send the ContentType. Sending the filename enables the server side to also see that we have some data that we can pull out regarding the filename -- more specifically, the file extension.

Server-Side Code

Finally, let's look at the server code for uploading files, shown in Figure 6. Files in the upload process need to be saved to a location on the server. In this case, we'll get a directory to save the files via the Server.MapPath method. The next step is to create a unique filename, which we do with a GUID, and then get a file extension. We can always use the filename that is uploaded in the header. The content type is also available.

public void ProcessRequest (HttpContext context) {
    // Save Files
    var localDir = context.Server.MapPath("~/files");
    var contentType = context.Request.ContentType;
    string fileExtension = String.Empty;
    if (!String.IsNullOrEmpty(contentType))
    {
    fileExtension = context.Request.ContentType.Split("/".ToCharArray())[1];
    }
    else
    {
    var rhFileName = context.Request.Headers["X-FILE-NAME"];
    var sArray = rhFileName.Split(".".ToCharArray());
    if (sArray.Length > 0)
    {
        fileExtension = sArray[sArray.Length - 1];
    }
    }
    var saveTo = Path.ChangeExtension(Path.Combine(localDir, System.Guid.NewGuid().ToString()), fileExtension) ;
    FileStream writeStream = new FileStream(saveTo, FileMode.Create, FileAccess.Write);
    ReadWriteStream(context.Request.InputStream, writeStream);
    context.Response.Write("{ \"Success\": true }");
}

private void ReadWriteStream(Stream readStream, Stream writeStream)
{
    int Length = 256;
    Byte[] buffer = new Byte[Length];
    int bytesRead = readStream.Read(buffer, 0, Length);

while (bytesRead > 0)
    {
    writeStream.Write(buffer, 0, bytesRead);
    bytesRead = readStream.Read(buffer, 0, Length);
    }
    readStream.Close();
    writeStream.Close();
}

Security

If you're focused particularly on security, you might have looked at the preceding code examples with horror and thought, "Oh my, you mean a browser can read files on my system and magically upload them to a web server? This is horrible, and we never want to enable or turn on any HTML5 stuff ever." Or maybe you are running through the halls of your office screaming this in terror. To address security concerns, the World Wide Web Consortium (W3C) working draft for drag and drop includes the following information about implementing security in the browser:

  • Data is not added to the DataTransfer object until the drop event occurs. This keeps data from being made available during the other events.
  • A drop is only successful if a drop event is caused by the user. If a script attempts to implement a drop event, the browser is not supposed to fire the drop event.
  • Scripts should not be able to start drag-and-drop actions.

Thankfully, this area is the responsibility of the browser, not the JavaScript developer.

File Reading

The DataTransfer object is part of the drag-and-drop specification. The DataTransfer object contains the files that have been selected. The files are made available to us through the FileReader window interface, which is a part of the File API W3C Working Draft. In our file-upload code, we are using the .size and .name properties of the files, which are fairly self-explanatory. Some of the other properties that are available are type, mozSlice, mozFullPath, fileSize, webkitRelativePath, fileName, and webkitSlice. The properties that have the "moz" prefix are available on Firefox. The properties that have the "webkit" prefix are available on Chrome. I expect that other browser vendors have created their own properties as well.

Along Came Polyfills (and Modernizr and Script Loaders)

There is an unfortunate downside to HTML5 and all its new and cool features. This downside is that not every web browser supports it! For each of the latest HTML5-compliant copies of IE10, Firefox, Chrome, Safari, mobile Safari on iOS, mobile Android browser, and others that I have crammed onto my systems, there are hundreds of my clients' end users who run IE7 or IE8 and access my applications that have been running since 2003 or earlier (yes, I have a web app that has been running for more than eight years). The problem is, then, how can the application that I am authoring provide all these new and cool HTML5 features to users that have HTML5-capable web browsers while at the same time not alienating those who are using your applications over older browsers like IE7 running on Windows XP? Into this gap steps the polyfill.

Strictly speaking, a polyfill is a piece of software, or shim, that offers some fallback mechanism to provide support that is not natively available. Typically, the term polyfill is used with JavaScript. For example, a geolocation polyfill could be used to provide HTML5 standard geolocation for an older BlackBerry device that doesn't provide the native HTML5 geolocation support.

Now, I am sure that your next question is, "How does my program know that the browser supports an HTML5 feature?" You're probably thinking that you will need to check the browser along with its version and then code an application along those lines. This is referred to as browser detection . We've all probably done this in our development lives and understand what a nightmare this is from a support standpoint. We would rather be able to test to determine whether a browser supports a given feature, referred to as feature detection. If the browser supports the feature, then the native feature will be used. If the browser does not support a given feature natively, a polyfill can be used to provide the feature.

Thankfully, this detection problem has been solved. It's solved for us within many JavaScript libraries -- jQuery, for example, so we don't need to worry about things there. So how can we integrate feature detection into our code?

Thankfully, there is Modernizr. Modernizr is a JavaScript library that detects the features available natively in web browsers. These features can be based on HTML5 or Cascading Style Sheets level 3 (CSS3). Modernizr goes through the detection of features in the loaded browser. It does this for us by

  • creating a JavaScript object named Modernizr. This JavaScript object allows custom code to determine the features that are natively supported within the browser based on a set of properties that return Booleans.
  • adding classes to the HTML element that allows code to determine the features that are natively supported.
  • providing a script loader. This script loader can be used to load polyfills, so that older browsers can be supported as necessary.

Here are some of the ways that we can use Modernizr:

  • Modernizer can be used to test for input types. Modernizr can be used to test the functionality of a browser by doing something like this:
if (!Modernizr.inputtypes.date) {

}
  • You can test the browser on your own and load browser support as necessary. I've seen developers do things like this:
  • You can use Modernizr and a script loader named yepnope to load some JavaScript files. In this situation, our code will perform a test to see whether the browser supports HTML5 geolocation, as shown in Figure 7. If it does, the yep.js file is loaded. If the browser does not support HTML5 geolcoation, the nope.js file is loaded. Once the files are loaded, the complete event fires and the defined function is called, which will output the results to a div on the page. Figure 8 shows the output in IE.
yepnope([{
    test: Modernizr.geolocation,
    yep: ['js/yep.js'],
    nope: ['js/nope.js'],
    complete: function () {
    output(document.getElementById("outputDiv"));
    }
}]);
Figure 8: Output of yepnope geolocation test
Figure 8: Output of yepnope geolocation test

There is a polyfill for nearly every HTML5 feature. You can find a list of polyfills provided by the Modernizr folks on Github (see the HTML5 Resources sidebar at the end of this article for the link and a list of other helpful information sources).

A few words of warning with polyfills:

  • Don't try to use too many of them. If a user comes to your application using IE6 and you need to load eight polyfills to make your application work properly, the user might find the added delay caused by the polyfills' loading to be unacceptable.
  • Be aware that a polyfill doesn't magically add functionality. In this situation, it can be used to make existing functionality HTML5 compliant. For example, IE8 and older BlackBerry browsers don't support HTML5 geolocation. IE8 can be made to support geolocation via Google Gears. Older BlackBerry browsers do support geolocation, but not the HTML5-defined APIs. There are polyfills available that can help with this.

Input Types

Over the years, many mechanisms have been created to provide date and time support. Applications may use third-party Web Forms controls, jQuery UI plug-ins, or another type of third-party support. There's nothing wrong with this approach, but it's always good to make application developers' lives easier so that we can provide higher-value support to our customers. As more browsers support HTML5 input types, developers and users will have access to this functionality. For example, we would like some easy way to get the following:

In "HTML5 for the ASP.NET Developer," I mentioned that there are some new input types in HTML5, including support for various date and time inputs. How many web applications have you built that use time and dates in some form? I can't think of any app that I have built that doesn't use date or time. Opera was the first web browser to support the date and time HTML5 input types, and now iOS's mobile Safari has this support as well. ASP.NET MVC developers have immediate support for HTML5 because these developers are responsible for building the user interface. But what about ASP.NET Web Forms developers? HTML5 support will be built into .NET Framework 4.5, but it is not available currently for a production setting. What can developers do now?

Thankfully, there is a solution that Web Forms developers can use now to enable date and time support in their web apps. To do so, Web Forms developers can use the following server-side code:

In this code sample, the type of the asp:TextBox is set to date. If the browser doesn't support that input type, the browser will display the input element as an input type of "text". In cases where the browser does not support the date input type, you can use the following JavaScript code to provide date support:

$(document).ready(function () {
    if (!Modernizr.inputtypes.datetime) {
    $("#<%:dateText.ClientID %>").datepicker();
    }
});

Note that this code requires jQuery and jQuery UI, so you have to add in the necessary scripts to use it, as shown in Figure 9.




Figure 10 shows the results of using the datetime input type to display a calendar in iOS 5 (left) and IE9 (right). And there you have it: Web Forms developers can start using some of these new HTML input types now and not have to wait for .NET 4.5.

Figure 10: Using HTML5's datetime input type to display a calendar in iOS 5 and in IE9 using jQuery
Figure 10: Using HTML5's datetime input type to display a calendar in iOS 5 and in IE9 using jQuery

Debugging

The combination of Visual Studio and Internet Explorer results in a great experience for developers, but how can developers debug in Firefox and Chrome? After all, Visual Studio hooks directly into IE but doesn't seem to have the same support for other browsers. Thankfully, there are solutions to this. Firefox has Firebug (a Firefox plug-in), which provides JavaScript debugging as one of its features. Chrome has some debugging tools built into it as well. Figures 11 and 12 show examples of debugging using the Chrome and Firefox tools.

Figure 11: Debugger in Chrome
Figure 11: Debugger in Chrome

Figure 12: Debugger in Firefox
Figure 12: Debugger in Firefox

If you are within a browser that doesn't have support for debugging, you can turn to a very old-fashioned mechanism for interrogating objects that I learned a few years ago:

for(m in object)
{
   // do something with m, perhaps some
// oldstyle js debugging with an alert(...);
}

Though it's exceedingly simple, this code will allow you to determine the properties of an object. This is helpful when you are running in a browser for which you don't have an available debugger.

Tools for Modern Web-App Development

From this article, you've learned various methods that you can use to add HTML5 capabilities to your existing web applications for the desktop and laptop. We've looked at file uploading, script loading, what polyfills are, Modernizr, and a few other items. For your customers, the future is now in terms of the capabilities they want for their users. Fortunately, regardless of whether you're developing Web Forms apps for a mix of newer and older browsers or are building apps for the latest web technologies, you have plenty of options for adding into your apps some of the modern features that end users expect. Next in this series, Dan Wahlin will walk you through a helpful resource that simplifies the process of writing HTML5 compliant web pages in "Getting Started Using HTML5 Boilerplate."

HTML5 Resources

Learn more about HTML5 features you can use today in your web applications:

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish