Webshims Lib.

Polyfill only the incapable browsers

Webshims Lib is a modular capability-based polyfill-loading library, which focuses on accurate implementations of stable HTML5 features, so that developers can write modern, interoperable and robust code in all browsers. It is built on top of jQuery and Modernizr.

Main concepts

  • Capability based loading: the more features a browser supports natively, the more lightweight webshims will be
  • Modular feature implementation: Only load features that you need for your application.
  • Future-proof feature detection: We try to feature detect everything instead of browser sniffing to be more future proof
  • Almost pure native and accurate APIs Helps you to learn HTML5 (Markup-/DOM/JS-APIs) and to build useful abstractions and extensions on top of the standard. Documentation is the specification (e.g. HTML5 W3C Specification)
  • stable and reliable polyfills for current and future x-browser landscape: webshims lib's polyfills are cross-browser tested and work with incapable, buggy, partial and of course full capable HTML5 implementations.

What makes webshims lib different?

Did you ever try to dynamically set or get the placeholder or the value attribute of a text input with one of the other placeholder shims? With many of them it doesn't properly work. Webshims strives to provide a uniform and standards-compliant API in every environment.

The following text input uses the html5 placeholder attribute:

If you're viewing this page in a browser that doesn't support placeholder, Webshims has added this functionality via Javascript, and in a way that preserves your ability to work with the element's properties dynamically:

$("#placeholder").attr("placeholder", "");

Being able to change the placeholder of an input element dynamically isn't a huge deal, but it shows how webshims lib works. With few exceptions, you won't be able to tell the difference between a shim and a native implementation. In some cases, the shim will even be more standards compliant than some browsers' native implementations.

The placeholder polyfill also works with getting and setting the value property (many other placeholder polyfills don't support this properly):

  • $("#placeholder").prop('value')
  • $("#placeholder").val()
  • $("#placeholder").prop('value', 'new value')
  • $("#placeholder").val('new value')

tested Browser Support

  • IE6+
  • Firefox 3.6+
  • Safari 4.0+
  • Safari for iOS 5+
  • Chrome 16.0+
  • Opera 11.6+

Features / Demos

How to

Easy & simple Quick install

<!-- dependencies: jQuery + Modernizr with yepnope --> <script src="js/jquery-1.7.1.js"></script> <script src="js/modernizr-yepnope-custom.js"></script> <!-- reference the base script --> <script src="js-webshim/minified/polyfiller.js"></script> <script> //implement all unsupported features || call polyfill before DOM-Ready to implement everything as soon and as fast as possible $.webshims.polyfill(); //or load only specific features you need //$.webshims.polyfill('forms json-storage'); $(function(){ //use all implemented API-features on DOM-ready }); </script>

Note: Webshims do not need all Modernizr tests to work. Here you find all Modernizr detections, which are used by webshims.

Embedding and configuring the script

  • Take the 'js-webshim' folder and add it to your project. (You will find a minified and a dev folder there.)
  • Include the polyfiller.js into your HTML page. You can concatenate this file with your other js and rename. Don't forget to also include jQuery, Modernizr (including Modernizr.load).
  • call the polyfill method
<script src="js/jquery-1.8.2.js"></script> <script src="js/modernizr-yepnope-custom.js"></script> <script src="js-webshim/minified/polyfiller.js"></script> <script> //load and implement all unsupported features || call polyfill before DOM-Ready to implement everything as soon and as fast as possible $.webshims.polyfill(); </script>

The polyfill method can also take a whitespace-separated list of several features and should be called as soon as possible (before DOM ready).

//load and implement json-storage and geolocation features, if they aren't supported $.webshims.polyfill('json-storage geolocation');

List of features

  • json-storage
  • es5
  • geolocation
  • canvas
  • forms
  • forms-ext
  • mediaelement
  • track
  • details

If you have any trouble setting up Webshims lib, set $.webshims.debug to true and open your console:

<script> $.webshims.debug = true; $.webshims.polyfill(); </script>

If you dont want any log messsages, simply set $.webshims.debug explicitly to false.

Custom Modernizr build

Webshims lib does not need every Modernizr test and supports creating a custom Modernizr build. (Webshims lib uses requireJS as a script loader, if this is included, if not it uses Modernizr.load/yepnope.)

changing Options path to shims folder

Shims folder is computed automatically. If the name of polyfiller.js is changed and the polyfill-script is loaded async, this computation can be wrong and you have to set the folder manually. This generated path can be changed by changing the basePath configuration with the setOptions method.

$.webshims.setOptions("basePath", "/yourFolderTo/shims/"); //or: $.webshims.setOptions({ basePath: "/yourFolderTo/shims/" });

Fire when ready

Not every feature is ready immediately in all browsers; some shims might take time to load before you can use them. You can use one of jQuery's ready methods to delay working with elements until the DOM and any necessary shims are ready.

$(function(){ //work with DOM + all implemented features }); $(document).ready(function(){ //work with DOM + all features });

If you want to use a feature as soon as possible or you have set the waitReady option to false, you can use $.webshims.ready and pass the feature name(s) and a callback function:

$.webshims.ready('geolocation json-storage', function(){ //work with geolocation, JSON and localStorage });

Note that this callback function may be called before the DOM is ready. If you want to use a feature after DOM-Ready, simply pass the string 'DOM':

$.webshims.ready('DOM canvas', function(){ //work with canvas in the document });

Setting options

Webshims has some general and some feature specific options which can be configured with the $.webshims.setOptions method.

setOptions should always be called before the polyfill method.

The available general options are the following:

  • extendNative (default: true): Webshims lib will automatically extend DOM-Objects with polyfilled methods and will additionally generate a jQuery plugin with this method name. If extendNative is set to false, webshims lib won't touch any DOM-Objects and will only implement jQuery plugins. Setting this option to false, might be also good for DOM performance.
  • basePath (default: "automatially computed path to the shims folder"): If polyfiller.js was renamed and is loaded dynamically, the basePath should be changed manually.
  • waitReady (default: true): If this option is true, jQuery's ready-event will be delayed until all needed features are ready to use. This option should be set to false, if webshims lib polyfiller.js is loaded async (see also Loading webshims async in the loading documentation) or from bottom of the body element. This option can also be set to false, if a website is using only HTML5 markup APIs (pure HTML or $.attr) and do not use any DOM-/JS-APIs on DOM-ready. If this feature is set to false, which might be good against FOUCs, scripted access to polyfilled APIs have to be added inside of a $.webshims.ready callback.
  • disableShivMethods (default: true): Automatically disables the shivMethods option of html5shiv, if jQuery 1.7.x is used, which provides a suitable innerShiv method.
$.webshims.setOptions("extendNative", false);

setOptions can also take a single options parameter:

$.webshims.setOptions({ extendNative: false });

Feature specific options are nested options with the featurename as their key.

$.webshims.setOptions({ //configure generally option extendNative: false, //configure canvas-shim canvas: { type: "flash" // use flashcanvas instead of excanvas as polyfill }, //configure forms-shim forms: { customMessages: true // implement customValidationMessages } });

The polyfill-options can also be changed by using declarative markup, using a data-polyfill-cfg attribute with a valid JSON string on a script-element:

<script src="some.js" data-polyfill='{ "basePath": "pathToYour/shims-folder/", "canvas": { "type": "flash" } }'> </script>

Minor Abstractions to DOM attributes, DOM methods and DOM events

Due to the fact that we cannot extend accessors of elements in all browsers, we always use jQuery as an extension-wrapper.

  • Accessing DOM properties/attribute IDLs: if the feature allows accessing a specific attribute or property, always use jQuery's built-in $.prop (in case of IDL attributes/properties) or $.attr/$.removeAttr (in case of content attributes) method:

    // instead of accessing a property directly (e.g. this.validity), use $.fn.prop $(this).prop('validity'); // or: //$.prop(this, 'validity'); //setting a property $('video').prop('currentTime', 200); // or: //$.prop(videoElement, 'currentTime', 200);
  • calling a DOM method: While DOM properties have to be accessed through jQuery's $.prop/$.attr methods, DOM methods are added to DOM elements themselves, so you can call them directly:

    // "this" refers to a DOM element, not a jQuery object this.checkValidity();

    Webshims lib also generates a jQuery plugin which wraps the feature, so you can use jQuery too:

    $(this).checkValidity();

    If you pass a list of nodes and the method doesn't return anything, the method will be called on each element in this list, otherwise it will be called on the first element.

    A lot of developers deprecate extending the DOM directly, in those cases the general option extendNative can be set to false.

    $.webshims.setOptions("extendNative", false); $.webshims.polyfill('forms'); //... //this.checkValidity() does not work cross-browser anymore use: $(this).checkValidity(); or $(this).callProp('checkValidity');
  • binding to events: Always use jQuery's bind method to register your event handlers. addEventListener or inline event handler won't work in polyfilled browsers. Most events in the HTML5 specification are so-called simple events. Simple events do not bubble. Due to the fact that developers really like event delegation and jQuery cannot distinguish between the event phases (capturing/propagation), we use event capturing in the native implementation and $.fn.trigger in the shim.

    This means that some HTML5 events go down the DOM tree in capable browsers and go up the tree in polyfilled browsers. This can create differences if you are calling event.stopPropagation(); or return false; in your event handler. If you only want to prevent the default, don't use return false;, use event.preventDefault(); instead.

  • manipulation methods/dynamically adding HTML5 content: To insert new HTML5 content dynamically, use .htmlPolyfill(), .appendPolyfill(), .prependPolyfill(), .afterPolyfill(), .beforePolyfill() and .replaceWithPolyfill():

    //three different examples $('form').appendPolyfill('<fieldset><input type="date" /></fieldset>'); $('#canvas-box').htmlPolyfill('<canvas></canvas>'); $('datalist#list select').prependPolyfill('<option value="new suggestion item" />');

    See also dynamic HTML5 description.

  • no shadowdom: Webshims lib sometimes has to add additional DOM elements to mimic the UI of HTML5 elements. This can sometimes create differences, between polyfilled and non polyfilled browsers. Webshims lib gives you a method called 'getShadowElement' to access the shadowelement from the native element and a method called 'getNativeElement' to access the native element from the shadowelement. If the element has no shadowelement the element itself is returned.

    //animate the width of all date - inputs to a width of 300 $('input[type="date"]').getShadowElement().animate({width: 300});

    re-rendering shadowelements: On dynamic websites shadowelements dimensions and/or positions sometimes need to be updated, this can be achieved by triggering the updateshadowdom.

    $.event.trigger('updateshadowdom');

    Note: The updateshadowdom event is automatically (but 'lazily') triggered on window.resize/emchange events.

Loading, Performance, FOUCs

Making webshims lib more lightweight (call polyfill conditionally)

Webshims lib can be considered as very lightweight polyfill libary. Feature implementations are only loaded in browsers, which do not support those features without any blocking behavior.

The grouping of features in webshims lib however is a balancing act. For grouping features, we take the following aspects into account: filesize vs. HTTP-request (minimize requests vs. parallelise requests), relation between features (JSON has nothing to do with input[placeholder], but with localStorage) and the current featureset of modern browsers (current modern browsers shouldn't load too much).

In some cases those grouping decisions - made by webshims lib - do not really fit the needs of a project. An example: If a project only need the input[placeholder] and input[autofocus] features, the forms feature has to be used. But forms does not only implement those needed features, but also constraint validation and some other features like formtarget.

This would not hurt a browser, which hasn't implemented anything of this (Due to our minimize requests and non blocking approach, the additional filesize isn't really significant). But it hurts browsers, which have implemented those features, but haven't implemented the other features (For example: Safari 5 has no validation UI. Opera 11.5 has a buggy validation API).

To make webshims lib really lightweight in those cases, the polyfill method should be called conditionally:

//only polyfill forms, if placeholder or autofocus are not supported if(!Modernizr.input.placeholder || !Modernizr.input.autofocus){ $.webshims.polyfill('forms'); }

And in case of adding multiple conditionally features:

//always polyfill mediaelement -> mp4, flv, youtube in all browsers var polyfillFeatures = ['mediaelement']; //only polyfill forms, if placeholder or autofocus are not supported if(!Modernizr.input.placeholder || !Modernizr.input.autofocus){ polyfillFeatures.push('forms'); } //Start polyfilling $.webshims.polyfill(polyfillFeatures);

Customize Webshims lib's polyfiller.js

While webshims lib comes with a lot of files, the polyfiller.js file is the only file, which has to be embeded directly in the page. The following tool allows you to customize and minimize the polyfiller.js file. (Simply overwrite the polyfiller.js with your customized version. All other files from the download package should stay.):

webshims config

Modernizr build

Modernizr build link

Loading webshims async

If webshims lib is loaded async, there are some configurations which have to be changed manually:

  • waitReady: should be changed to false. Note: you can not rely on jQuery's DOM-Ready event anymore.
  • basePath: should be set manually

A typical configuration and usage for webshims lib loading async (using yepnope) would look like this:

<script src="modernizr-yepnope.js"></script> <script> Modernizr.load([ { load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.js', complete: function () { if ( !window.jQuery ) { Modernizr.load('/js/libs/jquery-1.6.2.min.js'); } } }, { // This will wait for the fallback to load and execute load: '/js/libs/combined-package-with-polyfiller.js', complete: function () { //The following code can be also inside of your combined-package-with-polyfiller.js file package if ( jQuery.webshims ) { //change webshims options jQuery.webshims.setOptions({ waitReady: false, basePath: "/js/libs/shims/" }); //start polyfilling jQuery.webshims.polyfill('forms canvas'); //wait for the normal ready event and... jQuery(function($){ //wait until all requested features are implemented $.wehshims.ready('canvas forms', function(){ //use polyfilled features var context = $('#myCanvas').getContext("2d"); }); }); } } } ]); </script> </head>

Webshims polyfiller main file also registers as a "polyfiller" named AMD module. A typical configuration for loading webshims and jQuery async, using requireJS, would look like this:

<script src="require.js"></script> <script src="modernizr.custom.js"></script> <script> //requireJS config //... require( ["jquery", "polyfiller"], function(e){ //change webshims options jQuery.webshims.setOptions({ waitReady: false, basePath: "/js/libs/shims/" }); //start polyfilling jQuery.webshims.polyfill('forms canvas'); //wait for the normal ready event and... jQuery(function($){ //wait until all requested features are implemented $.wehshims.ready('canvas forms', function(){ //use polyfilled features var context = $('#myCanvas').getContext("2d"); }); }); }); </script> </head>

Dealing with FOUCs

Webshims lib uses a script loader and loading scripts dynamically always have the potential to create so called Flash of unstyled contents or Flash of unbehaviored contents.

There are a lot ways to deal with FOUCs/FOUBCs. The most easiest way to remove FOUCs caused by webshims lib is:

  1. Put your JS into your head and call $.webshims.polyfill() immediately (before DOM-Ready)
  2. Put the following CSS in your Stylesheet: /* The class loading-polyfills is set after you have called polyfill and will be removed as soon as all polyfills are loaded and implemented */ html.loading-polyfills body { visibility: hidden; } /* If polyfilling takes longer as 600ms, an additional class called 'long-loading-polyfill' will be set It's good practice to show the user that something is going on in this case */ html.long-loading-polyfills { background-image: url(polyfill-loader.gif); background-position: center 240px; background-repeat: no-repeat; }

Why you should put your webshims inside of your documents head

There is a lot of misunderstanding about the yslow rule "Put your JavaScript at the bottom". Put your JavaScript at the bottom does not decrease total page load time. The effect is psychologic. The user sooner sees how the page is loading, because JS blocks the UI rendering.

This also means that a FOUC is not a side effect of "Put your JavaScript at the bottom". It is the intended effect of this technique.

Using a script loader (and webshims lib uses a script loader) has a very similiar effect, because dynamically added scripts won't block the UI anymore. Combining both techniques will increase FOUCs.

If you use a script loader and put your JS at top you have done everything right about performance. Some versions of yslow recognize that you have used this technique and won't subtract points for this rule ("Put your JavaScript at the bottom"). The nice thing about script loaders is, that loading JS-files dynamically will additionally decrease page load time, because all browsers can parallelize script loading.

How to write a new feature implementation?

A Pollyfill is always split into at least two parts. First the test and loader definition, and then the implementation.

Assume there is a new method magicMethod on the window object that we want to polyfill if the browser doesn't provide it. An implementation would look like this:

$.webshims.addPolyfill('magicMethod', { test: function(){ return ('magicMethod' in window); } }); // loads magicMethod.js from shim-folder if test returns false

Create a file called magicMethod with your polyfill code and place it in the shim folder:

window.magicMethod = function(){ //your polyfill implementation };

If your implementation has dependencies on other polyfills/modules, you can add a dependencies property to make sure they're loaded too:

$.webshims.addPolyfill('magicMethod', { test: function(){ return ('magicMethod' in window); }, d: ['es5', 'json-storage'] // d = dependencies }); // load magicMethod.js and its dependencies if test returns false

If your shim has dependencies, you have to register your implementation code with $.webshims.register:

jQuery.webshims.register('magicMethod', function($, webshims, window, document, undefined, options){ //now you can use es5-feature and json-storage feature window.magicMethod = function(){ //your polyfill implementation }; });

In case of a DOM extension, webshims lib comes with some useful extension-methods.

$.webshims.addPolyfill('mediaelement', { test: function(){ return ('canPlayType' in document.createElement('video')); }, d: ['dom-support'], methodNames: ['play'] //pause, load }); // load mediaelement.js and DOM extension features from shim folder // if test returns false and always create a jQuery plugin called play, // which tries to invoke native or polyfilled play // listen to 'play' events in the capturing phase // can use event delegation with jQuery's bind method $.webshims.capturingEvents(['play']);

Now put a mediaelement.js inside of your shim folder and start implementing some features. The dom-support feature of Webshims lib comes with some very useful implementation helpers. Here are two of them:

  • $.webshims.defineNodeNamesProperty (nodeNames, property, descriptor)
  • $.webshims.defineNodeNamesBooleanProperty (nodeNames, property, descriptor)
//wait till the DOM-Extension feature is loaded jQuery.webshims.register('mediaelement', function($, webshims, window, document, undefined, options){ //note: webshims refers to jQuery.webshims in this function //Implements a new IDL property on audio, video called currentTime webshims.defineNodeNamesProperty('audio, video', 'currentTime', { prop: { get: function(){ //your getter implementation ("this" refers to the element) }, set: function(value){ //your setter implementation ("this" refers to the element) } } }); //Implements a new constant property called HAVE_CURRENT_DATA on audio, video webshims.defineNodenamesProperty('audio, video', 'HAVE_CURRENT_DATA', { prop: { value: 2 } }); //Implements a new method called play on audio, video webshims.defineNodeNamesProperty('audio, video', 'play', { prop: { value: function(){ //your play method implementation //this refers to the element } } }); //Implements a new boolean IDL property called controls on audio, video //which is reflected by a content attribute webshims.defineNodeNamesBooleanProperty('audio, video', 'controls', { //set works more like an onSet: //you don't have to set the value anymore on the DOM node, only handle true/false values set: function(value){ if(value){ //show controls for elem } else { //hide controls for elem } }, initAttr: true }); });

Webshims polyfilling philosophy

Abstractions vs. Polyfills

A polyfill is a shim that mimics/replicates a future/standarized API using legacy techniques to older browsers. A "shim-abstraction" is a code that uses modern and legacy techniques to create a new unstandardized API.

An abstraction can have several advantages over a pure polyfill. One of those advantages is a more clean and more easy-to-use API. Webshims Lib has decided to create/use polyfills (with some extensions on top of the standard, not instead!). This decision has several reasons.

Make learning new web technologies easy: It's an easy question: Do you want to learn proprietary or standardized code.

Make interoperability easy: Although Webshims Lib technique to create DOM-Setters and DOM-Getters is an abstraction (always use $.prop/$.attr to get/set DOM-Attributes). This abstraction is compatible to HTML5-browsers and makes writing interoperable code easy. Which means, you can use and mix different code of different developers.

Make extensions easy: If you want to create an extension on top of a feature, you always have one code path, because everything (including the low-level API) should be polyfilled. If you want to extend an abstraction, it might be possible, that you have to extend several code paths (modern and legacy code).

Let the user/developer choose: Webshims lib's polyfills are loaded modular by demand, which means you can mix several polyfills. Consider the following:

A plugin-author needs a storage API and he chooses abstraction code A, because it is good, has an awsome API and is only 5kb. Another plugin author also needs an storage API and uses abstraction code B, it's also nice and also just 5kb. And now you need to use both scripts and you are adding 10kb of useless code, because 80% of all browser already support localStorage and JSON (I would say this is a big fail).

Create stable, reliable polyfills

One big problem in the "polyfilling" world are reliable and production ready polyfills. Webshims Lib takes cross-browser testing and studying the specifications very serious.

Improve the standard

While Webshims lib's polyfills only want to polyfill the gaps and in some cases do some extension work. Every developer is invited to abstract ugly APIs, to re-style ugly polyfilled UI components or/and to write extensions on top of the standard. If you are doing so, please be so kind and share your code with others.

Further Development

Feel free to fork, report issues, develop implementations/features, develop extensions on top of implemented features or simply use :-).