HTML5 Download Attribute: Success!

HTML5 Download Attribute Success Meme

Providing direct and easy download of files can be simplified using the HTML5 download attribute. Historically, forcing a browser to save a file has been an over complicated programming task. New HTML5 specifications have identified this gap in browser technology and have mapped out a straightforward approach to downloading resources using the new download attribute on anchor and area elements. In this post, I will briefly outline the challenges that developers had before the download attribute was available and then show how simple it is to implement.

Direct Download Challenges

Historically, there have been a number of different methods developers could try to employ to force a file to be downloaded in the browser. One popular choice is to use JavaScript to open a new window pointed at the file. Unfortunately, if the Content Type header of the HTTP response from the server is a MIME Type that the browser knows how to handle, the browser will simply open the file in the newly opened browser window. This approach can work for files that a browser is not used to handling (executables etc.) but will not work for common web files like images.

docuement.getElementById("download-button").addEventListener("click", function() {
            window.open(urlToFileLocation);
        });

Another more complicated approach involves writing server side code (C#, PHP, Java) that would receive an HTTP request, process it based on whatever logic is needed, and return a response that included the file with a Content-Disposition field set to attachment. This is a more reliable approach and can handle any file type the server is capable of serving up, but requires access to server side code and greater level of effort.

// ASP.NET C# Example
Response.AppendHeader("content-disposition", "attachment; filename=" + fileName);
Response.ContentType = "application/octet-stream";
Response.WriteFile(filePath);
Response.Flush();
Response.End();

The download attribute

(At the time of this writing, the feature is not available in IE or Safari. Click here for existing support.)

Thankfully, with new HTML5 specifications for Downloading Resources, this additional effort can now be avoided by adding a single download attribute to any link.

<a href="download-success.jpg" download>Download Meme</a>

Here is the above link in action:  Download Meme

This approach is very straightforward and has the potential to save time and headaches for developers moving forward. Support for IE and Safari is still missing, but since the the target file is referenced in the href of the link, if a browser is missing support for the download attribute, it will attempt request the file like any other link.

Detect Device Battery Level via JavaScript

battery-half

I’ve recently come across a handy feature that allows web applications to detect device battery level via javascript from the browser. Currently, the spec for this interface (Battery Status API) is in Working Draft status with W3C but will hopefully see wider adoption in coming months. Google Chrome and Opera have already adopted this feature for desktop and there appears to be early support for Chrome on Android.

At first glance, this might seem like a trivial piece of information that a web app would want to access, but the more I think about it, I see a lot of potential especially in the context of mobile. If an application can detect battery level and charging status it could be smart enough to throttle data requests, save progress to avoid data loss. It could even be used behind the scenes as a diagnostic tool to determine where an application is a power suck.

The Battery Manager Interface

Detecting a device battery level and charging status is done through the Battery Manager interface via a Promise. This object is accessible via the navigator object in the browser. When the promise is returned there are a number of read only properties available are:

  • level (double): Current battery level from 0 to 1.0
  • charging (boolean): Represents if a device is charging
  • chargingTime (unrestricted double): Set to 0 if battery is full or there is no battery attached to the device. Set to Infinity if the battery is discharging.
  • dischargingTime (unrestricted double): Set to Infinity if the battery is charging.

The initial call is very straightforward:

navigator.getBattery().then(function (batt) {
    // Access to interface properties here                
});

Events Available

Along with the read only properties on the interface, there are a handful of events that get fired. They are fairly straight forward and mirror the available properties:

  • onlevelchange (levelchange): Event added to the browser queue when battery level property changes
  • onchargingchange (chargingchange): Event added to the browser queue when charging property changes
  • onchargingtimechange (chargingtimechange): Event added to the browser queue when chargingTime property changes
  • ondischargingtimechange (dischargingtimechange): Event added to the browser queue when dischargingTime property changes

Battery Level via Javascript Example

To accompany this post I’ve put together a quick example that illustrates accessing properties on the interface as well as firing the onlevelchange and onchargingchange events. You can see live demo here (Remember, you will need to use Chrome or Opera).

(function () {

            //#region Constants
            var NOT_APPLICABLE = "N/A";
            //#endregion

            //#region Helper Methods
            function setBatteryLevel(level, callback) {
                document.getElementById("level").innerHTML = "Battery Level: " + level;

                if (callback &amp;&amp; typeof (callback) === "function") {
                    callback();
                }
            };

            function setChargingStatus(status, callback) {
                document.getElementById("charging").innerHTML = "Charging Status: " + (status === NOT_APPLICABLE ? NOT_APPLICABLE : (status === true ? "Charging" : "Un-plugged"));

                if (callback &amp;&amp; typeof (callback) === "function") {
                    callback();
                }
            };
            //#endregion

            try {

                navigator.getBattery().then(function (battery) {
                    // When initial promise from navigator.getBattery() is recieved set the current statuses.
                    setBatteryLevel(battery.level);
                    setChargingStatus(battery.charging);

                    battery.onlevelchange = function (evt) { // Event info is available as param
                        var _level = battery.level;
                        setBatteryLevel(_level, function () {
                            console.log("Battery level changed: " + _level, battery);
                        });
                    };

                    battery.onchargingchange = function (evt) { // Event info is available as param
                        var _charging = battery.charging;
                        setChargingStatus(_charging, function () {
                            console.log("Battery charging status changed: " + _charging, battery);
                        });
                    }
                });
            }
            catch (e) {
                // Catch the error if navigator.getBattery isn't defined
                document.getElementById("error").innerHTML = "Unable to retrieve battery status. You may be using an incompatible browser.

\
            At the time of this demo, the Battery Status API is only available in Google Chrome 38+ or Opera 25+ and is in Working Draft status.

\
            Error Message: " + e;

                // Update
                setBatteryLevel(NOT_APPLICABLE);
                setChargingStatus(NOT_APPLICABLE);

            }
        })();

Want to Learn More?

The following are a few valuable links on the subject: