Building Your First Polyfill, Part 1: Getting Started - Building Polyfills - Building Polyfills: Web Platform APIs for the Present and Future (2014)

Building Polyfills: Web Platform APIs for the Present and Future (2014)

Part I. Building Polyfills

Chapter 3. Building Your First Polyfill, Part 1: Getting Started

Over the last two chapters, we’ve covered why polyfilling is still important, as well as some principles for responsible polyfill development. In this chapter, I’m going to walk through some practical steps for building cross-browser polyfills via an actual, real-world project that I created. We won’t go through every line of code or every excruciating detail, but I will introduce you to some practical examples and considerations that you’ll want to keep in mind for your own polyfill development, no matter the web platform feature being targeted. We’ll start with a discussion on project setup and structure before diving into setting up your API and deciding what you plan to build and not to build. Then we’ll explore adding basic features and refactoring your polyfill as you expand scope over time. We’ll then wrap up the chapter with a look at manual cross-browser testing, and some tips for streamlining your cross-browser testing workflow.

The HTML5 Forms Polyfill

First, let’s take a look at our candidate library for the next two chapters: an HTML5 Forms polyfill. When I first set out to write this book, I considered a lot of different options for a guinea pig polyfill to use. I wanted to target something that was reasonably complex, but straightforward enough to introduce in bits and pieces, via text. I also wanted to choose a technology whose implementation status across browsers was more than just “supported in everything but IE.” And even though IE will factor into some of the hairier aspects of our polyfill development, I wanted to walk through an example with quirks in as many browsers as possible. For those purposes, there’s no better technology to attempt to polyfill than HTML5 Forms.

What we know of today as “HTML5 Forms” was actually the first technology to be proposed under the umbrella of what eventually became HTML5. First introduced outside the W3C as Web Forms 2.0 by a consortium of browser vendors that included Google, Mozilla, Opera Software, and Apple, this proposal arrived at a time when the W3C was still pouring much of its effort into the now-defunct XHTML 2.0 specification. As an outflow of Web Forms 2.0, these vendors formed the Web Hypertext Application Technology Working Group (WHATWG), a sibling standards body to the W3C that still exists today.

And while it is outside of the scope of this book to discuss the politics of HTML5, various collected standards, and competing standards bodies, it is important to note that HTML5 Forms has not only been around a while, but it’s still one of the most hotly debated aspects of the HTML5 spec. While certain aspects of the Forms spec have near universal support (for example, forms constraint validation), some of the more visual aspects of the spec (new input types like number, color, and datetime) have yet to be consistently implemented across all browsers, as illustrated inFigure 3-1. What’s more, in some cases, for those browsers that do support certain types, the specifics of said support are often inconsistent from one implementation to the next. This means that you, the developer, might not get exactly the behavior you expect, every time, in every browser.

HTML5 Forms is a minefield, and I can’t think of a better area in which to venture as a polyfill developer. It’s a bit hairy, for sure, but this area of the spec is sufficiently complex enough to expose many of the polyfilling practices that I introduced in the preceding chapter.

In addition to targeting HTML5 Forms with my polyfill, I’ve chosen to leverage Kendo UI to provide much of the functionality that my polyfill will deliver to older browsers, from visual elements like color and date pickers, to key framework-level features like form validation. All of the UI widgets and framework features I need for my polyfill are available in the open source version of Kendo UI, which is available from its website. That said, if you’re following along and prefer to instead use a UI library like jQuery UI, you’re welcome to do so. In fact, I’ve built a version of this polyfill using jQuery UI, and you can find it on GitHub. Very little of what I cover in this chapter is dependent upon and applicable to only Kendo UI.

Color inputs viewed in Opera, Chrome, and Firefox

Figure 3-1. Color inputs viewed in Opera, Chrome, and Firefox

Why Use a Library?

At this point, you may be asking yourself why I’ve chosen to use a library like jQuery UI or Kendo UI at all. Why not just build the UI for each input type from scratch? It’s a valid question, and in fact there are several great HTML5 Forms libraries out there that take this approach. If you’re interested in taking a look at the code required to deliver UI widgets from scratch, I encourage you to check one of these out.

In our case, however, I’ve chosen to use a UI library because it allows me to focus on the process of building polyfills, which is, after all, what this book is about. By starting with a library that I can plug in, I’m able to focus on tips and tricks that are common to all types of polyfills, without getting bogged down in the very complex specifics of building form UI by hand.

NOTE

The HTML5 Forms polyfill we will be building over the next three chapters can be found in its current form online on GitHub. The project is actively maintained, and you’re welcome to view the source after reading this book if you want to dig deeper into any specifics that I was unable to cover here.

Finally, it’s worth mentioning that I’ve decided to build my HTML5 Forms polyfill as an opt-in polyfill, as opposed to a drop-in library. While it’s certainly possible to do the latter, I think the former is a better approach, in my case, for a couple of reasons. For starters, since I’m using Kendo UI to power my widgets, and Kendo UI requires explicit widget initialization, I feel that it’s appropriate for me to do the same with my polyfill and require that the developer activate the capabilities of my polyfill. I’ve also chosen to pursue an opt-in approach because the HTML5 Forms spec is so broad and complex that I’m not sure if my library will ever be able to support every single corner of the spec to the letter. By taking an opt-in approach, and documenting what I do and don’t support, I can make sure that the developers know what they are buying into.

That said, even though my polyfill is opt-in, it doesn’t mean that I can’t future-proof my library. Just as with a drop-in polyfill, my goal is to provide a polyfill that can be easily removed (along with a line of code) when all browsers support the HTML5 Forms spec.

Setting Up Your Polyfill Project

Regardless of the technology you choose to target with your polyfill, the two most important choices that you can make early on are:

§ Which source control solution you plan to use

§ What your initial project structure will look like.

Sure, you can start with “Source Control by Dropbox” at the beginning and name your polyfill mylib.js if you’re just itching to get started, but a little bit of foresight and planning is, in my opinion, just as important to polyfill development as the code you write. As such, I’ll use the next few sections to talk about these early considerations.

Choosing a Source Control Solution

The first step is to think about how and where you’ll want to host your code so that you’ve got a backup and full project history in case things go wrong so other developers can find, leverage, and contribute to your project. For the first part, you’ll want to choose a source control solution that’s open source and widely used. Two examples are Git and Mercurial. These two systems have a similar command-line syntax for working with files and code repositories, so some developers will be comfortable working with either. That said, Git is far and away the most popular source control system in use today, so you’ll reach a larger body of potential collaborators by choosing that system.

When paired with a solid code-sharing site, your source control solution also gives you a platform for making your polyfill available to the world. You can not only store your polyfill source and history in these sites, but also make that source and history available for others, which is hopefully your goal as a polyfill developer. Just as Git is the most popular source control option around today, GitHub is the most popular option for hosting and collaborating on all manner of Git-based open source software projects. Other options worth considering, if GitHub is not your cup of tea, are Bitbucket, which allows you to host both Git and Mercurial projects, and Microsoft’s CodePlex, which supports Mercurial projects only. For the examples in this book, I’ve chosen Git as my source control solution and GitHub for code sharing and distribution.

Setting Up the Initial Project Structure

Once I’ve chosen a source control solution, I’m ready to set up my project. I’ll start by creating a new folder for my polyfill, called kendo-ui-forms in my usual development folder on my machine. Then, I’ll open a terminal window in that directory and initialize a new GitHub repo by running the git init command. Once you’ve initialized your own local Git repository, you’ll want to connect it to the remote repository that will be hosting your project online, which you can do by running the following command: git remote add git@github.com:yourusername/your-project-name.git. If you’re not familiar with setting up a new remote repository, head over to GitHub, create a new repository for your project, and follow the on-screen instructions.

With that done, your local and remote repositories will be all set up, and it will be time to add some essential project files. Here’s the basic project structure I recommend for most open source polyfills (assuming a view from the folder root):

dist/

The minified and concatenated files you plan to distribute (the “binaries” for your polyfill).

lib/

Any third party libraries that your polyfill depends on (jQuery, Underscore).

sample/

Sample and example code for the polyfill.

spec/

Home for your unit tests, which we’ll discuss in Chapter 4.

src/[js,css]

Source JavaScript and CSS files.

gitignore

A list of files for Git to ignore when found in your project. GitHub can create one of these files for you when creating a new project. If you go that route, select the Node template.

CHANGELOG.md

A laundry list of minor and breaking changes for each version of your library.

CONTRIBUTING.md

Essential if you plan to accept contributions for your project.

README.md

The readme file for your project. GitHub automatically treats this file as the main entry point when anyone visits your project. It should describe the purpose and goals of the polyfill—as I discussed in Chapter 1—features of the project, a road map for the project, installation and usage instructions, and anything else you think might be useful to consumers of or collaborators on your polyfill.

LICENSE.md

Every open source project needs a license. Which license you choose is up to you, but permissive licenses, such as MIT and Apache 2.0, will garner more interest and, possibly, participation from other developers. GitHub can also generate this file for you, based on the license chosen at project creation.

NOTE

Though I did take the LSAT once upon a time, please note that I am not an attorney, nor do I play one on the Internet. As such, my opinions on licensing should not be confused for legal expertise. Your best bet is to consult with a legal expert before choosing an open source license. You can also visit Choose A License for more information about the dizzying array of open source licenses available. But still, you should talk to a lawyer if you want an expert legal perspective.

Specifying the API

Once your polyfill project is set up, your next important decision is determining how other developers will “call” your polyfill, if at all. As you’ll recall, two of the important subtypes of polyfills are the opt-in and drop-in types. If you’re building a drop-in polyfill, that essentially means that developers need only include a reference to your library in their projects. When your script is loaded, it automatically activates and goes to work. For these types of polyfills, the API of your library is straightforward and should match the API of the specified functionality you’re emulating as much as possible.

If, on the other hand, you’re building an opt-in polyfill, you’ll need to provide some mechanism for developers to activate your library. How you choose to expose your polyfill to developers is up to you, but I recommend considering your audience, specifically what they are likely to expect and be comfortable with, as you design your API. For examples of how other libraries expose their opt-in APIs, you can check out examples on the Modernizr Polyfill List. Since the HTML5 Forms polyfill I’m building will leverage Kendo UI, I’ve chosen to build my polyfill as a custom Kendo UI widget. By doing so, I can use Kendo UI’s ready-made options for initializing my library. As a result, I gain an initialization API for my polyfill that’s easy to use and familiar to developers, especially those already familiar with Kendo UI.

Initializing an Opt-In Polyfill

Kendo UI allows developers to initialize widgets in one of two ways. I can use jQuery-style widget initialization, as illustrated in Example 3-1, or I can use a declarative-style declaration, which hinges on placing data-role attributes on relevant elements in my markup. This approach is illustrated in Example 3-2. Note that the next few examples assume the inclusion of jQuery, Kendo UI JavaScript, and Kendo UI CSS in the page.

Example 3-1. Initializing my Forms polyfill using JavaScript

<form id="myForm">

<!-- Rest of form declaration -->

</form>

<script>

$('#myForm').kendoForm();

</script>

Example 3-2. Initializing my Forms polyfill via declarative initialization

<form action="input.html" data-role="form">

<!-- Rest of form declaration -->

</form>

<script>

kendo.init(document.body);

</script>

To support both of these approaches in my polyfill, I’ll need to follow Kendo UI’s recommended approach for creating custom widgets. First, I’ll create the core source file for my polyfill in the src/ folder for my project. I’ll call it kendo.forms.js, which follows a naming convention similar to other Kendo UI source files. Then, in my new source file, I’ll include the code in Example 3-3.

Example 3-3. Initial skeleton for the Kendo UI Forms polyfill

(function($, kendo) {

var ui = kendo.ui,

Widget = ui.Widget;

var Form = Widget.extend({

init: function(element, options) {

// base call to widget initialization

Widget.fn.init.call(this, element, options);

},

options: {

// the name is what will appear in the kendo namespace (kendo.ui.Form).

// The jQuery plug-in would be jQuery.fn.kendoForm.

name: 'Form'

}

});

ui.plugin(Form);

} (jQuery, kendo));

As illustrated here, my polyfill starts with an Immediately-Invoked Function Expression (IIFE) that specifies my dependencies (jQuery and Kendo UI in this case). Next, I create some local lookup variables to cache key parts of the Kendo UI namespace. Then, I create a new Form variable by calling the kendo.ui.Widget.extend() method, which takes care of handling the initialization types I specified. Finally, I’ll call the kendo.ui.plugin() method and pass in my Form widget, which adds my polyfill to the widget registry for runtime lookup and evaluation.

For my HTML5 Forms polyfill, this is all I need to create a public API for initializing my library. With this skeleton code in place, I can now use either initialization method described in Example 3-1 and Example 3-2, and things will resolve. My polyfill won’t do anything at this point, but it will run without errors, so that’s progress!

With the opt-in API of our library set, we can move on to building out the core functionality of our polyfill. Regardless of the type of polyfill you’re building, much of the API you’ll be exposing should already be decided for you via the specification for the technology you’re targeting. As discussed in the preceding chapter, it’s important to adhere to this specification as much as possible. If you’re planning to support an aspect of the spec, you should try your best to support it as specced. You should also be clear in your documentation and in-source comments about which aspects of the spec you support and which you don’t.

Deciding What to Build

Speaking of which, the next important decision you need to make in your library is what to build. Even if you do plan to support every nook and cranny of a spec with your polyfill, you probably won’t be able to bang out full support over a weekend. You need a plan, and if you’re anything like me, you probably want to target simple features and “quick wins” first. This establishes a good foundation and a working polyfill before you tackle the hairier aspects of support. If you’d rather target the hard stuff, that’s OK too!

In the context of HTML5 Forms, the simpler features are those new input types like color, number, and datetime. Because Kendo UI Web has widgets for these, supporting them is a simple matter of adding the Kendo UI widget when one of these types is found on a form. Validation support, on the other hand, is a bit trickier, so I’m going to put that off for later, perhaps after the first couple of releases.

Speaking of releases, this is probably a good time to think about the road map for your polyfill. Assuming you’re talking about a complex feature, you’ll probably want to write down what you plan to support, and when. For the HTML5 Forms polyfill, I chose to include a road map in thereadme for the project, which I’ve also included in Table 3-1.

Table 3-1. Road map for the Kendo UI Forms polyfill

Release

Features

v0.1

Support upgrading all HTML5 input types (color, numeric, range, file, datetime, time, month, week).

v0.1.1

Button support and date type support.

v0.2

Add support for progress and datalist elements; add a placeholder fallback and search box UI; autocomplete attribute support.

v0.3

Add validation support.

In addition to creating a road map and plan for your polyfill, you’ll want to consider whether there’s anything under the technology umbrella of your polyfill that you don’t plan to or cannot support. Sometimes it’s not possible to reliably polyfill an aspect of a specification, so you’ll want to avoid even trying to support it. Other times, adding support for a given feature is possible, but not something you’re prepared to take on. No matter the reason, be sure that your road map is clear about what you’re not planning to polyfill so that developers are informed when considering your library.

Adding Basic Features

So we’ve got our basic polyfill skeleton in place, an API for calling it, and a road map for which features we plan to add. Now it’s time to get to work and add our first, real feature. Of course, if we’re going to add features to our polyfill, we also need ways to test them out, don’t we? InChapter 4, I’ll discuss setting up unit and cross-browser testing in depth, but in the meantime, let’s create a sample form that we can use to test out our library as we work on it. This sample will serve as a live demonstration and part of our docs when we publish our polyfill, so it’s something you’ll want to add to your projects even if you’re also performing automated testing. Consider it a way to show off all your hard work to your potential users.

Creating a Sample Form

To that end, let’s create a new HTML page in the samples/ folder and call it form.html. Since our library is an HTML5 Forms polyfill, it makes sense that the sample page itself contain a form showing off all of our bells and whistles. Since this sample page will also serve as a part of my docs, the HTML page, which you can view in the online repo for this project, will include references to bootstrap and some additional markup that I’ve not included in Example 3-4. The relevant portion of this sample page, the form itself, is shown in Table 3-1.

Example 3-4. Sample markup for the Forms polyfill

<form action="#" id="sampleForm">

<fieldset>

<legend>Essentials</legend>

<div>

<label for="name">Name</label>

<input type="text" required placeholder="ex. Hugo Reyes" />

</div>

<div>

<label for="email">Email</label>

<input type="email" required placeholder="ex. hugo@dharma.com" />

</div>

<div>

<label for="phone">Phone</label>

<input type="tel" placeholder="ex. 555-555-5555"

pattern="^[2-9]\d{2}-\d{3}-\d{4}$"

title="Use a XXX-XXX-XXXX format" />

</div>

<div>

<label for="phone">Gratuitous Search</label>

<input type="search" id="search" />

</div>

</fieldset>

<fieldset>

<legend>Dates and Times</legend>

<div>

<label for="birthday">Birthday</label>

<input type="date" />

</div>

<div>

<label for="doctor">Next Doctor's Appointment</label>

<input type="datetime-local" value="2012-12-14T19:00"/>

</div>

<div>

<label for="favMonth">What month is it?</label>

<input type="month" />

</div>

<div>

<label for="favMonth">When is Shark Week?</label>

<input type="week" />

</div>

<div>

<label for="favMonth">What time is Beer O'Clock?</label>

<input type="time" />

</div>

</fieldset>

<fieldset>

<legend>Other Stuff</legend>

<div>

<label for="age">Age</label>

<input type="number" min=13 max=128 required placeholder="13 - 128" />

</div>

<div>

<label for="color">Favorite Color</label>

<input type="color" value="#fd49eb" />

</div>

<div>

<label for="GPA">College GPA</label>

<input type="range" min=0.0 max=4.0 value=3.0 step=0.25 />

<span id="rangeValue"></span>

</div>

<div>

<label for="browser">Favorite Browser</label>

<input type="text" list="browsers" />

<datalist id="browsers">

<option value="Chrome">

<option value="Firefox">

<option value="Internet Explorer">

<option value="Opera">

<option value="Safari">

</datalist>

</div>

<div>

<label for="picture">Recent Photo</label>

<input type="file" />

</div>

</fieldset>

<hr />

<div>

<div>Progress

<progress id="completionPct" min=1 max=12 value=3></progress>

</div>

<br />

<input type="submit" value="Submit this mess!" />

<input type="submit" formnovalidate value="Save for later" />

</div>

</form>

As you can see from the sample, it’s a pretty robust form, and it also uses all of the new HTML5 Forms features introduced in the spec, like new input types (such as color and datetime), new attributes (like autocomplete, pattern, or required) and form validation features. To give you an idea of what this form looks like in various browsers, Figure 3-2 shows what our form looks like, by default, in Chrome 29, while Figure 3-3 shows what the form looks like in Safari 6.1. Notice the difference in the date fields, the favorite color field, and others. We’ve certainly got our work cut out for us with this polyfill, even without taking oldIE into account!

Sample form as viewed in Google Chrome 29

Figure 3-2. Sample form as viewed in Google Chrome 29

Sample form as viewed in Safari 6.1

Figure 3-3. Sample form as viewed in Safari 6.1

With my sample form in place, I’ll next need to add a reference to my polyfill source file. In Example 3-3, we created the main kendo.forms.js file, which included the skeleton for our Forms widget and polyfill. I’ll add a reference to that file in my sample form, and then add a script block or new file reference to activate the sample form, as illustrated in Example 3-5.

Example 3-5. Activating our Forms opt-in polyfill via JavaScript

(function($, kendo) {

$('#sampleForm').kendoForm();

}(jQuery, kendo));

Now, if I refresh the page in my browser, I’ll see that nothing is different. I’m not getting any console errors, though, which means that my polyfill is being properly initialized. All that’s left is to add some real functionality. So, without further ado, let’s add that much-anticipated first feature.

Color Support

When I created the road map for my polyfill, I decided to tackle new input types first, and build up in complexity from there. The first type I’ll add support for is the color option, which is covered in section 4.10.7.1.15 of the HTML5 Forms spec. Here’s the text of that section:

Color State Section of the HTML5 Forms Spec

4.10.7.1.15 Color state

When an input element’s type attribute is in the Color state, the rules in this section apply.

The input element represents a color well control, for setting the element’s value to a string representing a simple color.

Note: In this state, there is always a color picked, and there is no way to set the value to the empty string.

If the element is mutable, the user agent should allow the user to change the color represented by its value, as obtained from applying the rules for parsing simple color values to it. User agents must not allow the user to set the value to a string that is not a valid lowercase simple color. If the user agent provides a user interface for selecting a color, then the value must be set to the result of using the rules for serializing simple color values to the user’s selection. User agents must not allow the user to set the value to the empty string.

The value attribute, if specified and not empty, must have a value that is a valid simple color.

The value sanitization algorithm is as follows: If the value of the element is a valid simple color, then set it to the value of the element converted to ASCII lowercase; otherwise, set it to the string “#000000”.

Bookkeeping details

§ The following common input element content attributes, IDL attributes, and methods apply to the element: autocomplete and list content attributes; list, value, and selectedOption IDL attributes.

§ The value IDL attribute is in mode value.

§ The input and change events apply.

§ The following content attributes must not be specified and do not apply to the element: accept, alt, checked, dirname, formaction, formenctype, formmethod, formnovalidate, formtarget, height, maxlength, max, min, multiple, pattern, placeholder, readonly, required, size, src, step, and width.

§ The following IDL attributes and methods do not apply to the element: checked, files, selectionStart, selectionEnd, selectionDirection, valueAsDate, and valueAsNumber IDL attributes; select(), setSelectionRange(), stepDown(), and stepUp() methods.

As noted in the preceding text, the “Color” type is intended to offer a simple “color well” control that supports visual selection of simple colors and retrieval of sRGB or hexadecimal equivalents of these. There’s a lot of W3C-speak in the section, but the rules for implementing a color input type in a browser are pretty straightforward:

§ The value attribute of an input with a type of color is a seven character string (# and six characters for the color value) that represents a valid sRGB color.

§ A valid color value must always be selected; null and empty string values are not permitted.

§ The default color value string is #000000, or black.

§ The UI for the color type must be a mask input—that is, it will not accept values that do not represent a valid sRGB color.

§ The string value for the color input should always be represented by a lowercase string, and converted to a lowercase string upon input, if uppercase characters are used.

You can see in Figure 3-2 that Chrome supports this attribute, while Safari 6.1 does not, as shown in Figure 3-3 (Safari simply shows the hex value I set in the sample form). Coincidentally, Kendo UI Web provides a ColorPicker widget that follows all of the preceding rules, so this control is a great first addition to our polyfill.

When I initialize my polyfill by calling kendoForm() (or via the declarative approach), the init() method in Example 3-3 will be fired, so that’s the right place to start adding my functionality. Inside that method, and just after the call to Widget.fn.init, I can add my color type code, as illustrated in Example 3-6.

Example 3-6. Adding color type support to the Forms polyfill

(function($, kendo) {

var ui = kendo.ui,

Widget = ui.Widget;

var Form = Widget.extend({

init: function(element, options) {

// base call to widget initialization

Widget.fn.init.call(this, element, options);

var form = $(element);

**`form.find('input[type=color]').kendoColorPicker({ palette: 'basic' });`**

},

options: {

// the name is what will appear in the kendo namespace (kendo.ui.Form).

// The jQuery plug-in would be jQuery.fn.kendoForm.

name: 'Form'

}

});

ui.plugin(Form);

} (jQuery, kendo));

In this sample, I’m looking for every input on my form with the attribute type=color and initializing a kendoColorPicker for each, using the basic palette option. The HTML5 specification doesn’t have anything to say about what the color control should look like or how it should behave, visually, so I’ve chosen a sensible default for the ColorPicker. Now, when I view the sample form in Safari, Firefox, or Internet Explorer (all browsers that do not support the color type at the time of writing), I’ll see a Kendo UI ColorPicker in place of the default text input, as seen in Figure 3-4.

Color support in the Forms polyfill (Safari 6.1)

Figure 3-4. Color support in the Forms polyfill (Safari 6.1)

To Feature Detect or Not to Feature Detect

Of course, there’s a catch. As it happens, if you view the sample page in a browser that does support the color type (like Chrome or Opera), you’ll notice that a ColorPicker was created in these browsers as well. This is because my current implementation doesn’t bother to perform feature detection for the color type, instead overriding every occurrence of the type on every browser.

NOTE

Feature detection is the practice of executing code in the browser for the purpose of determining whether that browser supports a given feature. The practice is considered superior to the classical practice of browser or user-agent sniffing because, rather than making wholesale decisions about which features to provide based on the user’s browser, you can enable or disable functionality at the feature level, based on support, regardless of the browser in use.

When building a cross-browser polyfill, you’ll need to consider how you wish to approach feature detection for your library. You have two choices:

1. Require that the user perform feature detection before including or opting in to your polyfill.

2. Perform feature detection on behalf of (or in addition to) the user.

The first approach is common for polyfills that cover a limited feature-set, or those that are activated on a per element or frequent basis. As Modernizr is widely used by developers, it’s common to see polyfills used in a manner similar to Example 1-3. In this example, I’m using Modernizr to query for CSS border-radius support and, if it’s not available in the user’s browser, I’ll opt in to PIE for a given set of elements.

When building polyfills that are a bit more expansive, or even more “intrusive” in the functionality they provide, I recommend performing feature detection on the developer’s behalf. In the case of HTML5 Forms, my polyfill is instantiated at the form level, so asking the user to perform feature detection before calling my library would be an all-or-nothing proposition that would lead to my library being used for all HTML5 Forms features, or none of them at all. Instead, I’d rather provide the ability for the polyfill to selectively upgrade only those features not supported in the browser.

To check for support for the color input type, I’ll create a function inside my init function to test for support for individual form types, as shown in Example 3-7:

Example 3-7. Testing for input type support

function isFormTypeSupported(type) {

if (type === 'text') { return true; }

var input = document.createElement('input');

input.setAttribute('type', type);

return input.type !== 'text';

}

First, I’ll create an in-memory input element. Then I’ll set its type attribute to the type variable provided by the caller. Finally, I’ll check the type attribute. If its value is still text even after I set it to another value, such as color, that means that the browser does not support this input type. As such, I’ll return false. If the value is retained, browser support is available, and I’ll return true.

To leverage this home grown feature-detection method, I’ll modify the code in Example 3-6 to first check for support, as shown in Example 3-8. Now if I refresh Chrome or Opera, the built-in browser support is back, while custom widget support provided by my polyfill will be leveraged for all other browsers.

Example 3-8. Checking for color type support before adding a ColorPicker widget

if (!isFormTypeSupported('color')) {

form.find('input[type=color]').kendoColorPicker({ palette: 'basic' });

}

Adding Opt-In Overrides to Your Polyfill

Once I add feature detection to my polyfill, the color type will be “upgraded” only when the browser doesn’t support this type. This is excellent for a default behavior, but what if the developer wants to author HTML5 Forms markup and have all of their form fields upgraded to widgets, regardless of browser support? This is obviously a case that falls outside specified HTML5 Forms behavior, but it’s a feature I’ve chosen to add in my forms polyfill, for two reasons:

§ With an opt-in polyfill, allowing developers to pass in options is easy.

§ Since the visual aspects of HTML5 forms vary greatly from one browser to the next, even between browsers that support a new type, some developers may prefer the ability to author HTML5 Forms markup while gaining a consistent look and feel for visual widgets across browsers. It’s downright “prollyfill-esque,” but we’ll get to that.

If you recall that one of our “responsible polyfilling” principles in Chapter 2 is “mind (only) the gaps,” you probably think I’m contradicting myself right now by adding override capabilities to my library. And while an argument can be made for leaving out a feature such as this, I believe that it’s a feature that adds value to the developer and end user by providing the ability to apply a consistent form UI across browsers. As such, I think it’s appropriate. What’s more, since the feature I’m adding doesn’t “break” the end-user experience on supporting browsers if the polyfill is removed—it merely changes the look and feel of HTML5 Forms fields—I don’t see it as a violation of the principle. Bottom line: These are principles, not rules. As the polyfill developer, you get to decide which ones to follow and which to discard, with good reason. If consumers of your library don’t agree, they’ll let you know.

To add an override for visual elements to my polyfill, I can leverage the built-in options object required by all Kendo UI widgets. In Example 3-3, we used this object to specify the name of our widget, Form, which Kendo UI uses when adding our polyfill to the library namespace. I can use this object to specify any number of developer-defined features, and I’ll use it now to add an alwaysUseWidgets Boolean value. Once I’ve added that option, I’ll modify my isFormTypeSupported method to check for this property. If alwaysUseWidgets is true, I’ll skip the feature-detection test and return false. The full listing for our polyfill source, including color type support and the override, is shown in Example 3-9.

Example 3-9. Polyfill source with color type support and an alwaysUseWidgets option

(function($, kendo) {

var ui = kendo.ui,

Widget = ui.Widget;

var Form = Widget.extend({

init: function(element, options) {

var form = $(element),

that = this;

// base call to widget initialization

Widget.fn.init.call(this, element, options);

function isFormTypeSupported(type) {

if (type === 'text') { return true; }

if (that.options.alwaysUseWidgets) { 1

return false;

}

var input = document.createElement('input');

input.setAttribute('type', type);

return input.type !== 'text';

}

if (!isFormTypeSupported('color')) {

form.find('input[type=color]').kendoColorPicker({ palette: 'basic' });

}

},

options: {

// the name is what will appear in the kendo namespace (kendo.ui.Form).

// The jQuery plug-in would be jQuery.fn.kendoForm.

name: 'Form',

alwaysUseWidgets: false 2

}

});

ui.plugin(Form);

} (jQuery, kendo));

1

Test the override property to determine if the element should always be upgraded

2

Specify the override property and set the default value to false

With this functionality in place, I can modify my initialization code to pass in the alwaysUseWidgets option:

$('#sampleForm').kendoForm({ alwaysUseWidgets: true });

Now, the Kendo UI ColorPicker widget will be used in all browsers.

Beefing Up Your Polyfill with Additional Features

So far, we’ve added basic support for the color input type, feature detection for that type, and the ability to override detection and always upgrade the type to use a UI widget. And while it’s nice to have support for a single type, it doesn’t make for a terribly useful polyfill. Let’s expand our polyfill by adding support for an additional input type.

Adding Support for the Number Type

The next feature for which I’ll add support in my polyfill is the number input type, which is covered in section 4.10.7.1.13 of the HTML5 Forms specification:

Number State Section of the HTML5 Forms Spec

4.10.7.1.13 Number state

When an input element’s type attribute is in the Number state, the rules in this section apply.

The input element represents a control for setting the element’s value to a string representing a number.

If the element is mutable, the user agent should allow the user to change the number represented by its value, as obtained from applying the rules for parsing floating-point number values to it. User agents must not allow the user to set the value to a nonempty string that is not a valid floating-point number. If the user agent provides a user interface for selecting a number, then the value must be set to the best representation of the number representing the user’s selection as a floating-point number. User agents should allow the user to set the value to the empty string.

The value attribute, if specified and not empty, must have a value that is a valid floating-point number.

The value sanitization algorithm is as follows: If the value of the element is not a valid floating-point number, then set it to the empty string instead.

The min attribute, if specified, must have a value that is a valid floating-point number. The max attribute, if specified, must have a value that is a valid floating-point number.

The step scale factor is 1. The default step is 1 (allowing only integers, unless the min attribute has a noninteger value).

When the element is suffering from a step mismatch, the user agent may round the element’s value to the nearest number for which the element would not suffer from a step mismatch. If there are two such numbers, user agents are encouraged to pick the one nearest positive infinity.

The algorithm to convert a string to a number, given a string input, is as follows: If applying the rules for parsing floating-point number values to input results in an error, then return an error; otherwise, return the resulting number.

The algorithm to convert a number to a string, given a number input, is as follows: Return a valid floating-point number that represents input.

Bookkeeping details

§ The following common input element content attributes, IDL attributes, and methods apply to the element: autocomplete, list, max, min, readonly, required, and step content attributes; list, value, valueAsNumber, and selectedOption IDL attributes; stepDown() and stepUp() methods.

§ The value IDL attribute is in mode value.

§ The input and change events apply.

§ The following content attributes must not be specified and do not apply to the element: accept, alt, checked, dirname, formaction, formenctype, formmethod, formnovalidate, formtarget, height, maxlength, multiple, pattern, placeholder, size, src, and width.

§ The following IDL attributes and methods do not apply to the element: checked, files, selectionStart, selectionEnd, selectionDirection, and valueAsDate IDL attributes; select() and setSelectionRange() methods.

As detailed in the spec, the number type is basically an edit mask that ensures that a user enters only numeric values into fields given the type="number" attribute value. Let’s take the preceding wall of text and translate it into some simple rules:

§ The value attribute of an input with a type of number is a floating-point number.

§ A valid number value is not required at all times. If a valid number is not selected, the value is empty string.

§ If the user attempts to enter an invalid or non-numeric value, the input value should be set to empty string.

§ The min and max attributes are allowed on this input, and should both represent valid floating-point numbers.

§ The step attribute is allowed and has a default value of 1, which allows only integers to be specified.

§ If the value of the input is set programmatically and that value violates the specified step property (i.e., step is 1 and a value of 2.88 is set), the control should round the set value up or down based on rounding rules (in this example, the value would be set to 3).

Just as with the color type, Kendo UI Web has a NumericTestBox widget that functions as a nice edit mask control for floating-point values, so we’ll use this widget to polyfill nonsupporting browsers. Example 3-10 contains the code specific to number that I’ll add to kendo.forms.js, just after my color type code.

Example 3-10. Adding number input type support to my Forms polyfill

if (!isFormTypeSupported('number')) {

form.find('input[type=number]').kendoNumericTextBox();

}

To test this feature, I can load my sample form in a browser that doesn’t support the number type, like IE9 or Firefox, or use the alwaysUseWidgets option. It works like a charm, and you’ll also notice that attributes like min and max, which I specified for the Age field on my sample form in Example 3-4, were preserved by the Kendo UI NumericTextBox widget. I get that for free, which is awesome.

Even still, I can’t help but get this creeping feeling that things could be better. To see what I mean, let’s look at our two features together in Example 3-11:

Example 3-11. Color and number type support

if (!isFormTypeSupported('color')) {

form.find('input[type=color]').kendoColorPicker({ palette: 'basic' });

}

if (!isFormTypeSupported('number')) {

form.find('input[type=number]').kendoNumericTextBox();

}

Repetition everywhere! And while it doesn’t look terrible with only two features, I can’t even bear the thought of what my polyfill will look like once I add support for all of the 12+ visual types and features. So, before we add our next input type, it’s time to refactor!

Refactoring is the practice of reorganizing code for maintenance, readability, and ease of use, while leaving its behavior unchanged. It’s most often associated with the agile discipline of TDD (where the phrase “Red, Green, Refactor” was born), but it’s a useful practice regardless of your specific development workflow. That said, refactoring is worlds easier when your production code is covered by a good suite of unit tests. And though I’m going to perform my refactor without a safety net now, I’ll be covering unit testing, as well as some performance-driven refactoring in Chapters 4 and 5.

Refactoring Type Support

When refactoring JavaScript code, I prefer to think not just of the refactor that will benefit my current code, but the code I plan to add next. This might sound like a bit of “you ain’t gonna need it” (or YAGNI) to you, but there are cases when I do indeed know “but I’m gonnna need it, and soon.” (I tried to coin the acronym BIGNIAS for this, but it doesn’t quite roll off the tongue.) Such is the case with my polyfill, where I know that much of the process of adding support for additional types will be consistent from one type to the next, with only a few, specific differences.

Because of this, the first step in my refactor is to move my color and number type upgrades into a lookup table, essentially just an array of objects that contains the type name and the upgrade function to execute for that type. My initial lookup object can be found in Example 3-12.

Example 3-12. Type lookup table for the color and number input types

var typeUpgrades = [

{

type: 'color',

upgrade: function(inputs) {

inputs.kendoColorPicker({ palette: 'basic' });

}

},

{

type: 'number',

upgrade: function(inputs) {

inputs.kendoNumericTextBox();

}

}];

Once I have my lookup table, I can refactor the code in Example 3-11 into something more like Example 3-13, where I iterate over each type in my lookup table, test for support, and perform the upgrade specified in the upgrade function for each.

Example 3-13. Using the lookup table to add input type support

var i, len;

for (i = 0, len = typeUpgrades.length; i < len; i++) {

var typeObj = typeUpgrades[i];

if (!isFormTypeSupported(typeObj.type)) {

var inputs = form.find('input[type=' + typeObj.type + ']');

typeObj.upgrade(inputs);

}

}

If I rerun the sample page in a browser, I’ll note that things still work just as before. That’s nice, but the real benefit to refactoring comes when I add features to my polyfill, which I’ll do next.

Before I move on, it’s worth mentioning that refactoring doesn’t have to stop with the simple changes I’ve detailed. While it’s out of the scope of this short book to belabor the refactoring conversation any further, it’s worth mentioning that, in the production version of my polyfill, I did perform some additional refactoring, including breaking my type upgrades and feature tests into two additional files, which I combine during my build process. If you’re interested in seeing those additional changes, you can view the types and features source files in the online GitHub repofor my polyfill.

Adding Input Types 3-n

Now that we’ve refactored things a bit, let’s add support for a third input type: the range type. The range input type enables developers to capture numeric data via a slider control with built-in min, max, and step values. Here’s the text from section 4.10.7.1.14 in the HTML5 spec:

Number State Section of the HTML5 Forms Spec

4.10.7.1.14 Range state

When an input element’s type attribute is in the Range state, the rules in this section apply.

The input element represents a control for setting the element’s value to a string representing a number, but with the caveat that the exact value is not important, letting UAs provide a simpler interface than they do for the Number state.

Note: In this state, the range and step constraints are enforced even during user input, and there is no way to set the value to the empty string.

If the element is mutable, the user agent should allow the user to change the number represented by its value, as obtained from applying the rules for parsing floating-point number values to it. User agents must not allow the user to set the value to a string that is not a valid floating-point number. If the user agent provides a user interface for selecting a number, then the value must be set to a best representation of the number representing the user’s selection as a floating-point number. User agents must not allow the user to set the value to the empty string.

The value attribute, if specified, must have a value that is a valid floating-point number.

The value sanitization algorithm is as follows: If the value of the element is not a valid floating-point number, then set it to a valid floating-point number that represents the default value.

The min attribute, if specified, must have a value that is a valid floating-point number. The default minimum is 0. The max attribute, if specified, must have a value that is a valid floating-point number. The default maximum is 100.

The default value is the minimum plus half the difference between the minimum and the maximum, unless the maximum is less than the minimum, in which case the default value is the minimum.

When the element is suffering from an underflow, the user agent must set the element’s value to a valid floating-point number that represents the minimum.

When the element is suffering from an overflow, if the maximum is not less than the minimum, the user agent must set the element’s value to a valid floating-point number that represents the maximum.

The step scale factor is 1. The default step is 1 (allowing only integers, unless the min attribute has a noninteger value).

When the element is suffering from a step mismatch, the user agent must round the element’s value to the nearest number for which the element would not suffer from a step mismatch, and which is greater than or equal to the minimum, and, if the maximum is not less than the minimum, which is less than or equal to the maximum. If two numbers match these constraints, then user agents must use the one nearest to positive infinity.

For example, the markup <input type="range" min=0 max=100 step=20 value=50> results in a range control whose initial value is 60.

The algorithm to convert a string to a number, given a string input, is as follows: If applying the rules for parsing floating-point number values to input results in an error, then return an error; otherwise, return the resulting number.

The algorithm to convert a number to a string, given a number input, is as follows: Return a valid floating-point number that represents input.

Bookkeeping details

The following common input element content attributes, IDL attributes, and methods apply to the element: autocomplete, list, max, min, and step content attributes; list, value, valueAsNumber, and selectedOption IDL attributes; stepDown() and stepUp() methods.

§ The value IDL attribute is in mode value.

§ The input and change events apply.

§ The following content attributes must not be specified and do not apply to the element: accept, alt, checked, dirname, formaction, formenctype, formmethod, formnovalidate, formtarget, height, maxlength, multiple, pattern, placeholder, readonly, required, size, src, and width.

§ The following IDL attributes and methods do not apply to the element: checked, files, selectionStart, selectionEnd, selectionDirection, and valueAsDate IDL attributes; select() and setSelectionRange() methods.

This is much more complex than the section for our color and number types, but there’s some overlap with the number type, especially around attributes. Let’s break this down into some rules, as we’ve done for the other two:

§ The value attribute of an input with a type of range is a floating-point number.

§ A valid range value is required at all times. If a valid range is not selected, the default value is used.

§ The range value cannot be set to an empty string.

§ If the user attempts to enter an invalid or non-numeric value, the input value should be set to the default value.

§ The min attribute is allowed, and should represent a valid floating-point number. The default min value is 0.

§ The max attribute is allowed, and should represent a valid floating-point number. The default max value is 100.

§ The default value of the range input, if no value is set, is the minimum plus half the difference between the minimum and maximum: d = min + 0.5(max - min). If the default min and max values are used, the default value is 50: 0 + 0.5(100-0).

§ When the set value is smaller than the minimum, the value should be automatically set to the minimum.

§ When the set value is larger than the maximum, the value should be automatically set to the maximum.

§ The step attribute is allowed and has a default value of 1, which allows only integers to be specified.

§ If the value of the input is set programmatically and that value violates the specified step property (i.e., step is 20 and a value of 50 is set), the control should round the set value up to the closest value that matches the step and that does not violate the max attribute (in this example, the value would be set to 60).

In Kendo UI, the equivalent widget to the range type is the Slider control, which has identical behavior, and supports all of the necessary attributes and rules specified here. To add support for the range type, I’ll add another object literal to my typeUpgrades array, as shown in Example 3-14.

Example 3-14. Adding support for the range input type

{

type: 'range',

upgrade: function(inputs) {

inputs.kendoSlider({

showButtons: false,

tickPlacement: 'none'

});

}

}

For the Slider widget, I’ll need to pass in a couple of configuration settings so that the default behavior of the Kendo UI Slider matches that of browsers that do support this type. That means no buttons or ticks. Just a simple slider, as depicted in Figure 3-5. And the best news is that there is no step 2, other than refreshing your browser and viewing the slider in the sample form! With the refactor that we made in the preceding section, adding support for additional input types is a simple matter of adding a new entry to our lookup table. Now, adding features 3–n is quick and painless.

Sample form with range support

Figure 3-5. Sample form with range support

Building Utility Polyfills

When building your polyfill, you might, from time to time, encounter a situation where your polyfill needs a polyfill of its own. For instance, there are a bevy of JavaScript utility functions that, while useful, may not be supported in older browsers like IE6-8, which your polyfill will often need to target. Examples are useful utilities like String.trim and Array.forEach.

When developing your polyfill, you may encounter situations where some utility you need in the browser (such as a common JavaScript method) isn’t supported. To address this, you can choose to either leverage some other approach that is supported across browsers, or you can build a polyfill for this utility.

If you choose to take the latter approach, I recommend taking a look at the Mozilla Developer Network which, in addition to having the best docs on the Web for frontend developers, is also a great resource for quick utility polyfills. For many of their JavaScript docs, MDN provides great information about current browser support, in addition to a quick snippet that can be used to polyfill support for that API across all browsers. An example can be seen in Example 3-15.

Example 3-15. A simple Array.forEach polyfill

if (!Array.prototype.forEach) {

Array.prototype.forEach = function (fn, scope) {

'use strict';

var i, len;

for (i = 0, len = this.length; i < len; ++i) {

if (i in this) {

fn.call(scope, this[i], i, this);

}

}

};

}

This example is a simple polyfill for the Array.forEach method. First, I’ll check for the existence of the forEach method on the Array prototype. If the method exists, we do nothing. If not, we’ll add our polyfill, which is a simple for loop that iterates over each element of the array. For my HTML5 Forms polyfill, I’ve included this and other utility polyfills in a standalone source file that is included in my combined and minified production build.

Polyfilling Visual Features With CSS

With the input type refactor done, adding support for most of the remaining types (including datetime, date, time, and month) is pretty straightforward and not really worth covering in this book. There are a few quirks here and there with some of the date/time types, especially when it comes to the proper way to format date attribute values, but as long as you ensure you’re properly handling date and time strings as covered in the spec, you should be fine. Your author failed to do so when he first started building his HTML5 Forms polyfill, so do take my word for it. Not coincidentally, it was this experience that lead yours truly to make “Read the Spec” the first principle of responsible polyfill development, as covered in Chapter 2.

Rather than covering the rest of the HTML5 input types explicitly, let’s turn our focus to a different part of the HTML5 Forms spec, and take a look at a scenario where adding polyfill support requires JavaScript and CSS to get the job done. While there are a few areas of the HTML5 spec that require us to delve into CSS, the placeholder attribute is probably the best example of this type of feature. According to the Placeholder section of the spec, this attribute “represents a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value.” In contrast to the <label> element, the placeholder attribute is intended to contain hint text that is overlaid on or displayed inside input controls, and that disappears when a user enters a value.

Since the spec is pretty straightforward about this attribute, it enjoys pretty broad browser support. However, IE8 and previous don’t support this attribute, and most of us still support these browsers in our sites and apps, so it makes sense to polyfill this feature in our library.

To do so, I’m going to start by adding a new CSS file in my project, under the src/css/ directory, and I’ll call it kendo.forms.css. Then, I’ll add the CSS in Example 3-16.

Example 3-16. CSS for polyfilling placeholder support

label.placeholder {

color: gray;

display: block;

font-size: small;

padding-top: 3px;

position: relative;

text-indent: 5px;

}

input.placeholder {

background-color: transparent;

left: 0;

position: absolute;

top: 0;

z-index: 1;

}

input.relPlaceholder {

position: relative;

}

input.placeholder:focus, input.placeholder:first-line {

background-color: white;

}

span.hidden {

opacity: 0;

}

My strategy for polyfilling the placeholder will be to create a <label> that contains the same text as the placeholder attribute. The label will be overlaid on top of the input when the element is empty and does not have focus, and will be hidden when it does. The CSS is adding a few classes and pseudo elements that I’ll need in order to manipulate elements that my polyfill will be creating at runtime. The label.placeholder selector applies to an element that I’ll create to hold placeholder text, while the input.placeholder selector applies to the original input for which I’m polyfilling attribute support. The remaining selectors cover positioning and visibility for elements and content.

With our CSS in place, I’ll add the JavaScript needed for placeholder support. First, I’ll need to add a feature test for this attribute in order to make sure that I don’t do any unnecessary work (which will include some DOM interaction) if the browser already supports it. Since I know that this won’t be the only attribute my polyfill will need to test for—it will also need to support new attributes like required, pattern, and more—I’ll go ahead and create a generic test function, just as I did for the input types:

function isAttributeSupported(attr) {

return attr in document.createElement('input') &&

attr in document.createElement('textarea');

}

In the case of HTML5 attributes, testing for support is a simple matter of creating a new in-memory input (and textarea), and checking for the presence of an attribute via JavaScript’s in property operator. If the attribute is available on both input types, our test will return true; otherwise, it returns false. Now we can leverage our test and, if not supported, add in some logic to activate placeholder support (as shown in Example 3-17).

Example 3-17. Polyfilling placeholder support with CSS and JavaScript

if(!isAttributeSupported('placeholder')) {

form.find('[placeholder]').each(function(index, val) {

var el = $(val);

// Strip CR and LF from attribute vales, as specified in

// www.w3.org/TR/html5/forms.html#the-placeholder-attribute

var placeholderText = el.attr('placeholder').replace(/(\\r\\n|\\n|\\r)/gm,'');

// When the field loses focus, clear out the placeholder if

// the input contains a value.

el.on('blur', function() {

var $el = $(this);

var labelNode = this.previousSibling;

if (this.value) {

labelNode.nodeValue = '';

$el.addClass('relPlaceholder');

} else if (labelNode.nodeValue !== placeholderText) {

labelNode.nodeValue = placeholderText;

$el.removeClass('relPlaceholder');

}

});

el.wrap('<label class="placeholder">' + placeholderText + '</label>');

el.addClass('placeholder');

});

}

Let’s walk through this sample step-by-step and take a look at what’s going on. First, I’m grabbing all of the inputs with a placeholder attribute from my form. The rest of this block contains the callback for each placeholder-containing element. I start by caching the jQuery object for the element, and then grab the placeholder value. The RegEx on that line serves to strip out any newlines that might sneak into the placeholder attribute. This requirement is explicitly covered in the spec and since we’re polyfilling to the spec, it’s a no-brainer to add this support.

Once I have a sanitized attribute value, I’ll bind my element to a blur event, wrap my element in a new <label> that contains the placeholder text, and then add the placeholder class to that label and the original element, which applies the CSS I defined in Example 3-16. The CSS rules give my label some contrast so that it’s obvious to the user that this is not input text (again, as per the spec) while also adding a rule to slide the label over to sit on top of my input.

The final piece of the puzzle is my blur event, which clears out the dummy placeholder label if the user has entered text in the input. Without this event, my placeholder text would show up over any text the user enters after navigating off the element.

Of course, this is all fine in theory, but as with every other feature we’ve had so far, it doesn’t mean a thing until we test this new feature in a nonsupporting browser. However, since placeholder support is so darn good, it’s not as simple as testing in one of the new browsers installed on your machine. Often, testing polyfill support means getting your hands on IE6, 7, or 8, and we’ll discuss how to do that in the next section.

At this point, you might be wondering why we’re switching gears to testing when we’ve not yet built our entire polyfill. We could go through the exercise of building the entire HTML5 Forms polyfill, but it’s a mostly repetitive task now that we’ve covered the basics of input type support. There are other complexities to be solved, like forms validation, but in the interest of time and space in this short book, I decided to spend some time focusing on unit testing, performance, and refactoring over the next few chapters. These aspects of polyfill development are just as important as how you go about building the features themselves. That said, if you want to dig deeper into the guts of the HTML5 Forms polyfill we’ve started in this chapter, you’re welcome to do so in the online repository.

Testing Your Work Across Browsers

Thus far, we’ve been testing out our polyfill by viewing the sample HTML form in modern browsers like Chrome, Firefox, Opera, Safari, and IE9+. If you’re following along, you’ve probably even been testing using just a single browser, which tends to be my own manual testing workflow as well. And while this strategy is fine when you’re getting up and running and just trying to get things to work, eventually you’re going to need to test in more than one browser. In fact, you’re going to need to test in all of them, and often. In Chapter 4, I’ll discuss some strategies for automating your cross-browser testing, but let’s first look at a few ways that you can get started.

Installing the Evergreen Browsers

First, I recommend that you install every single browser that you can get your hands on for your OS. This might seem obvious, but it can’t be overstated. When building cross-browser polyfills, you’re venturing into the weeds so that other developers don’t have to, so you’d better have access to every browser you can.

And I don’t just mean the consumer release of every browser, but also the betas, dev channel, nightly releases, and platform previews of all of these. You need to know what your polyfill needs to support not only today, but tomorrow as well. Sometimes browser updates will modify their support for a feature in ways that will actually break your polyfill (spec API changes and vendor prefixes are two examples), and you’ll want to be covered.

Modern, self-updating browsers are commonly referred to as evergreen, because they’re always considered new and up-to-date. Nearly every major browser vendor now supports a self-updating model, and Table 3-2 lists all of these browsers and where to find them, as well as prerelease versions of these.

Table 3-2. Evergreen and prerelease desktop browsers

Browser

Update Cadence

Download URL

Chrome

~6 Weeks

https://www.google.com/intl/en/chrome/browser/

Chrome Beta

~6 Weeks

https://www.google.com/intl/en/chrome/browser/beta.html

Chrome Canary

Nightly

https://www.google.com/intl/en/chrome/browser/canary.html

Firefox

~6 Weeks

https://www.mozilla.org/en-US/firefox/new/?icn=tabz

Firefox Beta

~6 Weeks

http://www.mozilla.org/en-US/firefox/beta/

Firefox Nightly

Nightly

http://nightly.mozilla.org/

Internet Explorer

Varies

http://windows.microsoft.com/en-us/internet-explorer/download-ie

IE Platform Preview

Varies

http://ie.microsoft.com/testdrive/

Opera

Varies

http://www.opera.com/

Opera Next

Varies

http://www.opera.com/computer/next

Safari

Varies

http://www.apple.com/safari/

Safari Beta

Varies

https://developer.apple.com/technologies/safari/

NOTE

While many of the names in this table are pretty straightforward, Chrome Canary isn’t very self-explanatory. Canary is Google’s “nightly” browser, which is updated once per day and represents the most cutting-edge work being done to Google Chrome.

Testing in OldIE

In addition to testing out your polyfill in the latest version of all of the browsers listed—not to mention mobile browsers if you’re supporting those—I highly recommend hands-on testing with Internet Explorer 6, 7, and 8. If you’re a Windows user, you might be tempted to use the Browser Mode and Document Mode features in IE’s F12 Developer Tools to simulate IE7 and 8. I humbly ask that you resist that temptation but for the simplest of tests. These modes do a decent job of simulating the behaviors of oldIE, sure, but they aren’t foolproof. For example, Figure 3-6 andFigure 3-7 illustrate the differences I see when running my HTML5 Forms polyfill test suite (which I’ll introduce in the next chapter) in IE11, with simulation, and in IE8. It’s the same code and same test suite for both, and even though Document Mode is providing me an IE8 experience in theory, you’ll find that this is not always the case in practice.

Testing IE8 via IE11’s Browser Mode simulator

Figure 3-6. Testing IE8 via IE11’s Browser Mode simulator

Thankfully, testing oldIE doesn’t require that you buy old Windows Vista and XP licenses and install these browsers on the old hardware sitting in your closet. On the contrary, Microsoft hosts a fantastic site called modern.ie that’s purpose-built around the idea of providing developers with all the tools they need to support and test the various Internet Explorer browsers. In addition to providing tools like a page-scanning service and documentation on standards support for newer versions of the browser, the site provides free virtual machines for testing all versions of IE, including 6, 7, and 8. Whether you use VirtualBox, VMWare, Parallels, Virtual PC, or Hyper-V, there are free VMs available for you to download, fire up, and use to test out your hard work. To grab a VM, just head over to modern.ie and click the “Test Across Browsers” menu option.

Testing IE8 via an actual IE8 installation

Figure 3-7. Testing IE8 via an actual IE8 installation

Cross-Browser Testing and Verification with Online Services

In addition to testing with your own browsers, and testing oldIE with VMs, there are a growing number of online services available that you can use to easily test your work across browsers without installing another browser or running a VM. One popular service is BrowserStack, which allows you to test public and internal URLs across a variety of OSs and browsers from within a browser window. You can also automate BrowserStack tests via Selenium for automated testing. The only catch with BrowserStack is that the service is not free, so it’s not likely to be an option for most open source cross-browser polyfills.

Another great option for cross-browser testing is Testling, a CI server that tests your code across browsers each time you push to your remote repo. Testling requires some form of automated unit test suite, but I consider this to be a plus because automated testing across the 18 versions Testling supports is far more ideal than manual tests, in my opinion. We’ll discuss setting up unit and cross-browser tests in the next chapter.

In this chapter, we covered the basics of getting your polyfill project set up, and we also added basic features, did a simple refactor to improve polyfill maintenance, and even added support for the placeholder forms attribute in oldIE. We’ve come a long way already, and I hope you’ve learned a thing or two about putting those principles of responsible polyfill development into practice.

As great as our progress has been so far, though, you might find yourself bothered by the fact that a) our polyfill isn’t terribly easy to test and b) we don’t really have a strategy in place for linting our code, performing minification, or doing anything else that a good project should do before releasing production-quality code. In Chapter 4, we’re going to cover all of these and more.