// We are done with the test element, so delete it.
testInput = null;
</script>
</body>
</html>
This example also includes a demonstration that it will fail in the case of an unsupported attribute (in this case you just make up a fake attribute and test for it).
Clearly this technique requires the target object to implement a setAttribute method. As a result it cannot be used to detect features on the window or navigator elements, which do not have a setAttribute method.
Building a Feature Detection Script
Now that you know how to test for various HTML5 features, you can build a single script that tests for everything. Start by creating a constructor function that, when called, will run the feature detection tests and return an object that contains all of the results. Each result will be a named property on the object set to either true or false depending on the support for that feature. The object will also have three convenience methods:
• getTests: This method will return an alphabetized array of all of the features that were tested.
• getTestResults: This method will return an array consisting of all of the results for all of the features that were tested. A single result will consist of an object with a feature property set to the name of the feature and an isSupported property that will be set to true or false depending on whether or not the feature is supported.
• getFailedTestResults: This method will return an array consisting of all of the results for all of the features that failed their tests and are not supported in the current browser.
Listing 6-11 gives the full listing of the detection script.
Listing 6-11. HTML5 Feature Detection Script
<!DOCTYPE html>
<html>
<head>
<title>The HTML5 Programmer's Reference</title>
<style>
<h1>Feature Detection</h1>
<table id="supported">
<tr><th>Feature</th><th>Support</th></tr>
</table>
<script>
/**
* Detects support for various HTML5 features.
* @constructor
* @returns {Object} An object with properties for each feature, each * set to true or false depending on support.
*/
function DetectHTML5Support() { var returnVal = {};
// Test for HTML5 APIs on the window object.
var apisToTest = ['EventSource', 'postMessage', 'sessionStorage', 'localStorage', 'Worker', 'requestAnimationFrame',
'cancelAnimationFrame', 'DeviceMotionEvent', 'DeviceOrientationEvent'];
for (i = 0; i < apisToTest.length; i++) { var currApi = apisToTest[i];
returnVal[currApi] = (window[currApi] != undefined);
}
// Test for HTML5 APIs on the navigator object.
var apisToTest = ['geolocation'];
for (i = 0; i < apisToTest.length; i++) { var currApi = apisToTest[i];
returnVal[currApi] = (navigator[currApi] != undefined);
}
// Test for HTML5 APIs on the document object.
var apisToTest = ['querySelector', 'querySelectorAll'];
for (i = 0; i < apisToTest.length; i++) { var currApi = apisToTest[i];
returnVal[currApi] = (document[currApi] != undefined);
}
// Test for suport for the new HTML5 elements.
var unsupported = 'HTMLUnknownElement';
var elementsToTest = ['article', 'aside', 'nav', 'footer', 'header', 'section', 'figure', 'figcaption', 'main', 'bdi', 'data', 'mark', 'ruby', 'rp', 'rt', 'time', 'wbr', 'dialog', 'details', 'summary', 'datalist', 'meter', 'output', 'progress', 'audio', 'canvas', 'video'];
for (i = 0; i < elementsToTest.length; i++) { var currItem = elementsToTest[i];
var testEl = document.createElement(currItem);
returnVal[currItem] = (testEl.toString().indexOf(unsupported) == -1);
testEl = null;
}
// Test for support for new input properties that are booleans.
var propsToTest = ['autofocus', 'draggable'];
var inputEl = document.createElement('input');
// For variety we'll use Array.forEach to run these tests instead of an // explicit for loop.
propsToTest.forEach(function(currProp) { var testValue = true;
inputEl.setAttribute(currProp, testValue);
returnVal[currProp] = (inputEl[currProp] === testValue);
}, this);
// Test for support for new input properties that are strings.
propsToTest = ['autocomplete', 'placeholder'];
propsToTest.forEach(function(currProp) { var testValue = 'testval';
inputEl.setAttribute(currProp, testValue);
returnVal[currProp] = (inputEl[currProp] === testValue);
}, this);
inputEl = null;
/**
* Returns a sorted array of all features that were tested for.
* @returns {Array.<string>}
*/
returnVal.getTests = function() {
// Get all of the properties and methods we've added to returnVal and // sort them.
var allPropsAndMethods = Object.keys(this).sort();
// This list will contain all the properties and methods, but we only want // properties, so filter out the methods.
var allTests = [];
allPropsAndMethods.forEach(function(currItem) { if (typeof this[currItem] != 'function') {
* Returns an array consisting of all test results. Each result is an object * with the feature property set to the name of the test and the isSupported * property set to true or false, depending on the support for that feature.
* @returns {Array.<Object>}
*/
returnVal.getTestResults = function() { var tests = this.getTests();
var allResults = [];
tests.forEach(function(currTest) { var currResult = {
feature: currTest,
isSupported: this[currTest]
};
allResults.push(currResult);
}, this);
return allResults;
};
/**
* Returns an array of test results for all failed tests. Each result is an * object as described in getResults.
* @returns {Array.<Object>}
*/
returnVal.getFailedTestResults = function() { var tests = this.getTests();
var failures = [];
tests.forEach(function(currTest) {
// Return the object with all the results.
return returnVal;
}
// Test for supported features.
var supportedFeatures = new DetectHTML5Support();
// Fill the table with support information.
var supportTable = document.getElementById('supported');
var allResults = supportedFeatures.getFailedTestResults();
allResults.forEach(function (currTest) { var newRow = document.createElement('tr');
var featureCell = document.createElement('td');
var supportCell = document.createElement('td');
featureCell.innerHTML = currTest.feature;
supportCell.innerHTML = currTest.isSupported;
newRow.appendChild(featureCell);
The script groups together similar tests to make it easier to add or remove tests as fits your needs.
In each case you define a set of things to test as an array of simple strings that are the names of the feature to test: the name of the API, the name of the element, or the name of the property. Then each section loops through the arrays and applies the appropriate test and records the result. Note that throughout these tests you are making use of the fact that in JavaScript you can access properties either by dot notation (Object.property) or by bracket notation (Object['property']), as explained in Chapter 2.
The script demonstrates the detection process by calling the constructor to run the tests and get a new results object, and then uses the getFailedTestResults method to fetch a list of unsupported features and builds a table to show them. (You could easily alter this to use the getTestResults method instead to see all the results.) If you run this in different browsers you’ll see variations in what isn’t supported, especially if you have access to older versions of browsers . . . or Internet Explorer, as shown in Figure 6-1.
As you can see, support for some features is still missing even in modern browsers. It’s particularly sad that Firefox and Internet Explorer do not support the dialog, summary, or details elements; that Firefox doesn’t support the autocomplete property; and that Internet Explorer doesn’t support server-sent events.