Important note: Webshim is not compatible with upcoming jQuery 3.x and I do not plan to work on a new major version. I will still fix critical bugs in the future though.
Webshim is a polyfill library that enables you to reliably use HTML5 features across browsers, even if native support is lacking.
The project is hosted on GitHub, and is available for use under the MIT software license.
You can report bugs and discuss features on the GitHub issues page.
Webshim depends on jQuery.
There are many appealing HTML5 features such as form validation, geolocation, mediaelements and UI widgets which ease the burden of writing rich web applications.
Webshim is a polyfill solution. It adds these features in a way that is transparent to the end user and developer. It enables coding against the browser standards rather than learning and maintaining abstractions. It results in less code that is simpler and easier to maintain.
Webshim is also more than a polyfill, it has become a UI component and widget library. Webshim enables a developer to also enhance HTML5 capable browsers with more highly customizable, extensible and flexible UI components and widgets. Its capability based loading system enables webshim to only load those files, which are needed for the specific browser/device and only when it is needed (deferred loading), to reduce especially the initial network payload.
Take the 'js-webshim' folder and add it to your project. (You will find a minified and a dev folder there. Put the whole folder, not only the polyfiller.js file into your project!) Here is an example showing how to include dependencies and load webshims:
<script src="js/jquery.js"></script>
<script src="js-webshim/minified/polyfiller.js"></script>
<script>
//webshim.setOptions('basePath', '/js-webshim/minified/shims/');
//request the features you need:
webshim.polyfill('es5 mediaelement forms');
$(function(){
// use all implemented API-features on DOM-ready
});
</script>
The polyfill method accepts an optional whitespace-separated feature list. Call it as soon as possible (before DOM ready.)
webshim.polyfill( "canvas geolocation" );
position
: sticky
)
setOptions
should always be called before the
polyfill
method.
The available options for
webshim.setOptions
:
webshim.setOptions('basePath', '/yourFolderTo/shims/');
webshim.ready
callback.
loadStyles
is set false
webshims won't load any default styles. In this case a site has to provide all styles for the used widgets. A developer also can copy all used webshim styles into his own stylesheet and minimize requests/only use needed styles for used widgets. See also Customizing styles and UI
debug
to true enables debug informations to be printed in the developer console. Note the polyfiller.js in the dev folder has more debug informations and pretty printed code.
true
configuration values with the keyword 'auto
' are treated as true, if this is false
the keyword 'auto
' is treated as false
.
window.Audio && (!window.matchMedia || matchMedia('(min-device-width: 721px)').matches)
)
// enhanceAuto will be false for devices smaller than 720px (i.e. Smartphones or for devices smaller than 1024px and with touchevents (i.e.: Tablets)
webshim.setOptions('enhanceAuto', !(matchMedia('(max-device-width: 720px)').matches || matchMedia('(max-device-width: 1024px)').matches && Modernizr.touchevents) );
webshim.setOptions({
'forms-ext': {
replaceUI: 'auto'
},
'mediaelement', {
replaceUI: 'auto'
}
});
//webshims will implement those features in all browsers/devices
// but will only enhance capable browsers on desktop with custom styleable mediaelement controls and form widgets
webshim.polyfill('forms forms-ext mediaelement');
setOptions
can also take a single options parameter:
webshim.setOptions({
extendNative: true
});
Feature specific options are nested options with the feature name as their key.
webshim.setOptions({
// configure generally option
extendNative: true,
// configure canvas-shim
canvas: {
type: 'flash' // use flashcanvas instead of excanvas as polyfill
},
// configure forms-shim
forms: {
lazyCustomMessages: true // implement customValidationMessages
}
});
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
webshim.ready
and pass the feature name(s) and a callback function:
webshim.ready('geolocation es5', function(){
// work with geolocation and es5
});
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':
webshim.ready('DOM canvas', function(){
// work with canvas in the document
});
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) methods:
// 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 can be accesed using $.prop and executed in the context of the DOM element.
// "this" refers to a DOM element, not a jQuery object
var fn = $.prop(this, 'checkValidity');
fn.apply(this);
To make this more easier Webshims lib also generates a jQuery plugin which wraps the featured method, so you can use jQuery too:
$(this).checkValidity();
$(this).callProp('checkValidity'); // $(this).callProp('play');
The jQuerified method can also take some arguments, if specified by the spec.
$('video').addTextTrack('subtitles', 'just a test', 'en');
$('video').callProp('addTextTrack', ['subtitles', 'just a test', 'en']);
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.
In case a developer wants to make method calling "feel 100% native", the general option
extendNative
can be set to
true
.
webshim.setOptions('extendNative', true);
webshim.polyfill('forms');
// ...
this.checkValidity() // or use $(this).callProp('checkValidity')
$('video').get(0).addTextTrack('subtitles', 'just a test', 'en');
binding to events: Always use jQuery's
on
method to register your event listeners.
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/copy new HTML5 content dynamically, use
.htmlPolyfill()
,
.appendPolyfill()
,
.prependPolyfill()
,
.afterPolyfill()
,
.beforePolyfill()
,
.replaceWithPolyfill()
and
.clonePolyfill()
:
// 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.
// get the UI component element of input[type="date"]
$('input[type="date"]').getShadowElement();
Another problem can be, that some styles of your website may conflict with webshims styles. This normally happens if a tagname selector without a specific class or attribute is used as last selector part:
/* do not use */
fieldset > div {
/* some styles */
}
/* instead write something like this: */
.form-row {
/* some styles */
}
Some styles like float or display: none/block are not reflected by the shadowdom, it is a good approach to do this on a wrapper element or to also add those styles to the generated UI components yourself:
<!-- instead of -->
<div class="form-field">
<label for="date" style="display: none;">Birthdate</label>
<input type="date" id="date" style="display: none;" />
</div>
<!-- do this: -->
<div class="form-field" style="display: none;">
<label for="date">Birthdate</label>
<input type="date" id="date" />
</div>
<!-- or instead of: -->
<div class="mediaplayer">
<video src="file.mp4" controls style="margin: 0 10px 5px 0; float: left">
</video>
</div>
<!-- do this: -->
<div class="mediaplayer" style="margin: 0 10px 5px 0; float: left">
<video src="file.mp4" controls></video>
</div>
This could be also fixed by adding those styles to the polyfilled component:
/* do not use */
video {
margin: 0 10px 5px 0;
float: left;
}
/* do something like this: */
video,
.polyfill-video {
margin: 0 10px 5px 0;
float: left;
}
/* or better */
.video-wrapper {
margin: 0 10px 5px 0;
float: left;
}
re-rendering shadowelements: On dynamic websites shadowelements dimensions and/or positions sometimes need to be updated, this can be achieved by triggering the
updateshadowdom
.
$(document).trigger('updateshadowdom');
The
updateshadowdom
event is automatically and lazily triggered on window.resize/emchange events.
IE8- isn't capable of rendering HTML5 elements. Webshims lib in combination with Modernizr or HTML5shiv automatically fixes this issue and adds WAI-ARIA landmarks to enable accessibility of HTML5.
Additionally it implements the following manipulation methods to allow dynamic creation of HTML5 elements:
.updatePolyfill()
,
.htmlPolyfill()
,
.appendPolyfill()
,
.prependPolyfill()
,
.afterPolyfill()
,
.beforePolyfill()
.replaceWithPolyfill()
,
.appendPolyfillTo()
,
.prependPolyfillTo()
,
.insertPolyfillAfter()
,
.insertPolyfillBefore()
,
.replacePolyfillAll()
var html = '<section><form action="#">' +
'<input type="text" placeholder="name" name="test" required/>' +
'<input type="date" required="required" name="test2" />' +
'<input type="submit" />' +
'</form></section>';
$('#Shiv-dynamic-html5-test').htmlPolyfill(html);
updatePolyfill
method with Backbone/jQMobile/Knockout
If a plugin, script or framework is used, which uses a "normal" JS/jQuery manipulation method instead of the corresponding webshims enhanced manipulation method to generate new HTML5 content (i.e.:
.html()
instead of
.htmlPolyfill()
), the method
.updatePolyfill()
can be used to polyfill the dynamic content:
$('#my-dynamic-container').load('ajax/test.html', function(){
$(this).updatePolyfill();
});
//set waitReady to false
webshim.setOptions('waitReady', false);
// call webshim.polyfill() before domready
webshim.polyfill();
// bind to the pageinit event after domready...
$(document).on('pageinit', function(e){
// ...and call updatePolyfill on the changed element container
$(e.target).updatePolyfill();
});
For jQuery Mobile see also webshims and jQ mobile
The
es5
feature uses the ES5 shim by Kris Kowal (all object methods, which can't be used cross-browser are removed.).
The script implements the following methods:
Note: All methods added to the prototype object are visible inside of a for in loop, while native implementations aren't enumerable. (use hasOwnProperty)
webshim.ready('es5', function(){
[1, 2, 3].forEach(function(){
// do stuff
});
});
Webshims Lib adds the following methods:
webshim.objectCreate
,
webshim.defineProperty
,
webshim.defineProperties
,
webshim.getOwnPropertyDescriptor
and corresponding support flags:
webshim.support.objectAccessor
(true in all modern Browsers including IE9, getters and setters can be used on all objects) and
webshim.support.advancedObjectProperties
(true in FF4, IE9, Chrome 10..., the full ES5 specification of defineProperty is implemented (including writeable, enumerable and configurable).
webshim.objectCreate
works very similar to Object.create
If
propertiesObject
is defined, the method goes through this object and sets writeable, enumerable and configurable to true, if the corresponding property is undefined.
After this, it will pass
proto
and
propertiesObject
to Object.create (if defined) or will use Crockfords
begetObject
-Method on the
proto
-object and then calls
webshim.defineProperties
with the returned object and the
propertiesObject
.
If you pass the options parameter and the Object already has a Options-property. The options-property on the object will be deeply extended otherwise an new options-property will be created.
If the object has a method called
_create
, it will call this method with options as first argument.
After this, the created object will be returned.
var carProto = {
options: {
foo: 'bar',
baz: 'boom'
},
wheels: 4,
drive: function(){
this.isDriving = true;
}
};
var myCar = Object.create(carProto, {
_create: {
value: function(){
this.drive();
}
},
jumps: {
value: function(){
//implements jumping
}
},
{baz: 'jo'}
);
// myCar will look like this:
{
// own property:
options: {
foo: 'bar',
baz: 'jo'
},
// prototype:
wheels: 4,
// prototype
drive: function(){
this.isDriving = true;
},
// own property:
_create: function(){
this.drive();
},
// own property:
jumps: function(){
//implements jumping
},
// own property:
isDriving: true
}
webshim.defineProperties
works similar to Object.defineProperties
It will go through the props properties and will set writeable, enumerable and configurable to true, if they are undefined.
After this either Object.defineProperties will be invoked or legacy code is used.
The
forms
(constraint validation without number, date etc.) and
forms-ext
(number, date, range etc.) features of the webshim lib are implementing support for the constraint validation API, some input widgets and the placeholder-attribute.
The
forms
feature also implements an easy and cross browser way to control the validation UI (error message and styling the validation bubble).
forms
:
forms-ext
:
forms
OptionsA typical configuration would look like this:
webshim.setOptions("forms", {
lazyCustomMessages: true,
replaceValidationUI: true,
customDatalist: "auto",
list: {
"filter": "^"
}
});
webshim.polyfill('forms');
customMessages
, but the data is lazily loaded after window.onload or as soon as the user starts to interact with a form for better performance. The customValidationMessage property is therefore not available on DOM-Ready.
webshim.setOptions("forms", {
iVal: {
"sel": ".ws-validate",
"handleBubble": "hide",
"recheckDelay": 400,
"fieldWrapper": ":not(span):not(label):not(em):not(strong):not(p)",
"events": "focusout change",
"errorClass": "user-error",
"errorWrapperClass": "ws-invalid",
"successWrapperClass": "ws-success",
"errorBoxClass": "ws-errorbox",
"errorMessageClass": "ws-errormessage",
"fx": "slide",
"submitCheck": false
}
});
webshim.setOptions("forms", {
list: {
"filter": "*",
"multiple": false,
"focus": false,
"highlight": false,
"valueCompletion": false,
"inlineValue": false,
"noHtmlEscape": false,
"popover": {
"constrainWidth": true
}
}
});
forms-ext
Options
'datetime-local range date time number month color'
. If one of the listed input types is not supported, Webshim will load the forms-ext API and UI package and implement all types.
webshim.setOptions("forms-ext", {
"widgets": {
"startView": 0,
"minView": 0,
"inlinePicker": false,
"size": 1,
"splitInput": false,
"yearSelect": false,
"monthSelect": false,
"daySelect": false,
"noChangeDismiss": false,
"openOnFocus": false,
"buttonOnly": false,
"classes": "",
"popover": {
//popover options
},
"calculateWidth": true,
"animate": true,
"toFixed": 0,
"onlyFixFloat": false
}
});
A typical configuration would look like this:
//configure forms features
webshim.setOptions("forms", {
lazyCustomMessages: true,
replaceValidationUI: true,
customDatalist: "auto",
list: {
"filter": "^"
}
});
//configure forms-ext features
webshim.setOptions("forms-ext", {
replaceUI: "auto",
types: "date range number",
date: {
startView: 2,
openOnFocus: true,
classes: "show-week"
},
number: {
calculateWidth: false
},
range: {
classes: "show-activevaluetooltip"
}
});
//load forms and forms-ext features
webshim.polyfill('forms forms-ext');
firstinvalid
is a simple, bubbling event, which is triggered on the first invalid form element. Preventing the default of firstinvalid will automatically prevent the default behavior of all current invalid elements.
lastinvalid
is an extended, bubbling, (but) uncancelable event, which is triggered on the last invalid form element. The property
invalidlist
is a jQuery-collection of all current invalid elements.
console.log(webshim.validityAlert)
to make changes)
the canvas feature implements one jQuery method
.getContext()
.
var ctx = $('#my-canvas').getContext('2d');
ctx.clearRect(10, 10, 100, 100);
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect(10, 10, 55, 50);
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect(30, 30, 55, 50);
Webshim Lib can implement excanvas or FlashCanvas/FlashCanvas Pro:
// always run configuration before calling webshim.polyfill();
window.FlashCanvasOptions = {
disableContextMenu: true
};
webshim.setOptions('canvas', {
type: 'flashpro' // excanvas | flash | flashpro
});
//start polyfilling forms
webshim.polyfill('forms');
Webshim will implement a bridge between the native/polyfilled drawImage canvas API and the polyfilled mediaelement API. To get this work in IE8 the 'flashpro' option type for the canvas
feature has to be used.
canvas
feature
flash
or
flashpro
or
excanvas
The
mediaelement
feature implements the audio, video and source elements including their API and enables playing mp4, mp3, flv, fla etc. media files and playing rtmp streams in incapable browsers.
mediaelement
featuremediaplayer
get custom styleable controls. If set to the string 'auto', only desktop browsers (except IE8) will get those extra styleable controls.
<video poster="poster.jpg" controls="">
<!-- Mac OS / iOS HLS Streaming -->
<source src="http://server.com/path/file.m3u8" type="application/x-mpegURL" />
<!-- rtmp streaming: using the data-server attribute -->
<source data-server="rtmp://server.com/path/" src="file.mp4" type="video/rtmp" />
<!-- rtmp streaming: using an identifier (i.e.: mp4:) -->
<source src="rtmp://server.com/path/identifier:file.mp4" type="video/rtmp" />
</video>
Video quality can be suggested by using the vq
parameter. Possible values are: small, medium, large, hd720, hd1080, highres
<video poster="poster.jpg" controls="" src="http://www.youtube.com/watch?v=siOHh0uzcuY&vq=large">
</video>
<style>
/* add basic styles */
.mediaplayer {
position: relative;
height: 0;
width: 100%;
padding-bottom: 56.25%; /* 16/9 */
}
.mediaplayer video,
.mediaplayer .polyfill-video {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
</style>
<script>
webshim.setOptions('mediaelement', {replaceUI: 'auto'});
webshim.polyfill('mediaelement');
</script>
<div class="mediaplayer">
<video poster="poster.jpg" src="http://www.youtube.com/watch?v=siOHh0uzcuY&vq=large">
</video>
</div>
Webshim will implement a bridge between the native/polyfilled canvas.drawImage
API and the polyfilled mediaelement API (flash).
<script>
webshim.setOptions({
canvas: {
type: 'flashpro'
},
mediaelement: {
replaceUI: 'auto'
}
});
webshim.polyfill('mediaelement canvas');
</script>
<script>
$(function(){
var context, $video;
function initCanvas() {
var $canvas = document.getElementsByTagName("canvas")[0];
$video = $("video");
context = $canvas.getContext("2d");
$video.on("timeupdate", paintFrame);
}
function paintFrame(e) {
context.drawImage($video[0], 0, 0);
//timeupdate is dispatched every 250ms, let's paint the frame more often,
//but only if a real timeupdate was dispatched
if(e){
setTimeout(paintFrame, 65);
setTimeout(paintFrame, 130);
setTimeout(paintFrame, 195);
}
}
initCanvas();
});
</script>
<div class="mediaplayer ratio-16-9">
<video controls preload="none" poster="sintel-trailer.jpg">
<source src="sintel-trailer.mp4" type="video/mp4" />
</video>
</div>
<canvas style="border: 1px solid black;" height="280" width="500"></canvas>
Here you can find more information about customizing controls and extending the mediaplayer's behavior.
The
track
feature implements the track element width a WebVTT parser and the corresponding DOM- and JS-APIs.
VTTCue
constructor
$([ $('video').prop('textTracks') ]).on('addtrack', function(e){console.log('track was added');});
)
$.prop(track, 'activeCues');
).
track
feature
The
geolocation
-feature implements the navigator.geolocation API. The following methods are available:
The shim uses the geolocation information provided by http://freegeoip.net and/or googles API-Loader
webshim.setOptions('geolocation', {
confirmText: '{location} wants to know your position. It is Ok to press Ok.'
});
navigator.geolocation.getCurrentPosition(function(pos){
alert("Thx, you are @ latitude: "+ pos.coords.latitude +"/longitude: " + pos.coords.longitude);
});
The HTML5 details element is an interactive element. If the open attribute is set the details are shown, if not the details are hidden.
Here are the details of this element:
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore mag
Dynamically creating a details element (always use htmlPolyfill, afterPolyfill, beforePolyfill etc.):
$('details:first').afterPolyfill('<details open="open"><p>Here is some text</p></details>');
The openness of a details element can be also scripted. Simply change the open IDL-attribute:
// set the open attribute of the first details element ...
// ... to its opposite value
$('details:first').prop('open', !$('details').prop('open') );
details
feature
webshim.setOptions('details', {animate: true});
The filereader
feature enables accessing and reading a file from an input[type="file"]. Additionally it can be processed over AJAX
to a server. Due to its obtrusive nature an input has to have an additional class with the name ws-filereader
:
An example of how to read a file as a base64 string from a form control:
<!-- add ws-filereader class to all shimable type="file" inputs -->
<input type="file" class="ws-filereader" id="user-photo" multiple="" accept="image/*" />
//load the filereader
webshim.polyfill('filereader');
//on DOM ready filereader is shimmed
$(function(){
$('#user-photo').on('change', function (evt) {
var reader, file;
reader = new FileReader();
reader.onload = function (evt) {
var fileData = evt.target.result;
// fileData is the base64 encoded image
};
//use $.prop to access files property
file = $(this).prop('files')[0];
reader.readAsDataURL(file);
});
});
To get the file transferred to a server the FormData
constructor in conjunction with AJAX can be used:
$('form[data-action].ajax-form').on('submit', function(){
//FormData is similar to $.serializeArray, but can handle type="file" in AJAX
var data = new FormData(this);
$.ajax({
url: $form.data('action'),
success: function(data){
//success
},
error: function(){
//error
},
data: data,
processData: false,
contentType: false,
type: 'POST'
});
e.preventDefault();
});
For the code above to fully work it is important, that everything is either in the same origin or a crossdomain.xml is on the server. A featured example including php code can be found in the filereader directory.
In case webshim is not served from the same domain (for exmaple using a CDN) a crossdomain.xml like the following should be reachable on your server root:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM
"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" secure="false" />
</cross-domain-policy>
In case you don't have full access to your server's root to do so, you can host Moxie.min.swf (Flash) on your server and tell webshim to load them from there:
webshim.setOptions('filereader', {
swfpath: '/assests/Moxie.min.swf'
});
The filereader
feature is also lazy loaded by webhsim's form
feature as soon as an element matching the input.ws-filereader
selector is used. Additionally the forms features enables custom styleable [type=file] elements if it finds a wrapper with the class ws-custom-file
:
<script>
//filereader is lazyLoaded no need to call feature
webshim.polyfill('forms');
</script>
<div class="ws-custom-file">
<!-- add ws-filereader class to all shimable type="file" inputs -->
<input type="file" class="ws-filereader" id="user-photo" multiple="" accept="image/*" />
<!-- button and .ws-file-value for custom filepicker UI -->
<button type="button">Browse</button>
<span class="ws-file-value">...</span>
</div>
The filereader
feature is based on code from https://github.com/moxiecode/moxie.
The matchMedia
feature is a polyfill of
matchMedia. It includes also a polyfill for matchMedia('(max-width: 480px').addListener
.
This feature is based on code from https://github.com/paulirish/matchMedia.js.
The usermedia
feature is a polyfill of
navigator.getUserMedia. It includes also a polyfill for URL
and the srcObj
property.
A demo with code example of getUserMedia polyfill.
The url
feature is a polyfill of
WHATWG URL Spec. It also includes the searchParams
object.
The URL
object has the following properties:
href
origin
protocol
username
password
host
hostname
port
pathname
search
searchParams
append(name, value)
delete(name)
get(name)
getAll(name)
has(name)
set(name, value)
hash
Usage
var link = new URL('http://afarkas.github.io/webshim/demos/index.html?param1=value1');
link.protocol; // returns 'http:'
link.searchParams.get('param1'); // returns 'value1'
link.searchParams.append('param2', 'value2');
link.href // returns 'http://afarkas.github.io/webshim/demos/index.html?param1=value1¶m2=value2'
This feature is based on code from https://github.com/inexorabletash/polyfill/blob/master/url.js.
The promise
feature is a polyfill of
ES6
Promises. For instructions on using promises and details of the API, read
JavaScript
Promises at HTML5Rocks.
This feature is based on code from https://github.com/paulmillr/es6-shim.
A strict polyfill of the
picture
element draft specification from the Responsive Images Community Group, including
the srcset
and sizes
attributes.
webshim.polyfill('picture'):
This feature is based on code from https://github.com/aFarkas/respimage/. Complete documentation is available there.
The sticky
feature is a polyfill for the CSS position
value sticky
(position sticky demo).
Simply add the class ws-sticky
to all elements, which should become sticky and define either a top
or bottom
value:
<section>
<header class="ws-sticky">
<!-- ... -->
</header>
<!-- ... -->
<footer class="ws-sticky">
<!-- ... -->
</footer>
</section>
Sticky and responsive webdesign. The sticky position can be also used in conjunction with mediaqueries. Simply add a data-stickymedia
attribute with your mediaquery to your element:
<section>
<header class="ws-sticky" data-stickymedia="(max-width: 480px)">
<!-- ... -->
</header>
<!-- ... -->
</section>
Often you don't want to add the class 'ws-sticky' directly into your HTML, in this case you can use the JS-API. Simply trigger the event 'wssticky' on all elements, which should become sticky:
$('table.long-table > thead')
.addClass('ws-sticky')
.trigger('wssticky')
;
Example with mediaqueries:
$('table.long-table > thead')
.addClass('ws-sticky')
.data('stickymedia', '(max-width: 480px)')
.trigger('wssticky')
;
The stickyness of an element, can also be removed or updated with the JS API:
//use destroysticky to remove sticky
$('dl.list-view > dt')
.removeClass('ws-sticky')
.trigger('destroysticky')
;
//use updatesticky to remove sticky
$('dl.list-view > dt').trigger('updatesticky');
Webshim sticky implementation supports sticky in conjunction with either top or bottom properties and on most elements including table headers (thead) and table footer (tfoot). It also implements "local" sticky areas, where the parent element of the 'sticky element' has set the overflow value to 'auto/scroll'.
Webshim has evolved form a general polyfill library to a sophisticated UI component / UI widget library which makes it easy to create scalable, responsive and highly adaptable websites and webapps.
While webshim polyfills older browsers and automatically fixes a lot of bugs, it enables the developer to also enhance even modern browsers with highly customizable widgets (datepicker, form validation, slider, autosuggest, custom stylable controls for video/audio).
Webshim is opinionated, that a developer should always solve a problem the HTML5 way.
For example most datepickers have a direct configuration option to disable certain days to be picked from the control. This is not possible with current implementations of input[type="date"]
, but it is possible to use the HTML 5 form validation API to constrain the user input to certain days. Therefore webshim offers a way to constrain the user input and those constraints are used to decide whether certain days should be disabled in the pickercontrol.
This way a developer can switch between polyfilled/enhanced and native widgets. While the appearance might be different the base functionality will work in all browsers. But if a certain feature is barley possible with HTML5 or a developer needs full control in all browsers/devices, he can always switch to a enhance all strategy.
Webshim might in total load a bunch of files to implement or enhance features. This might look crazy at first glance. But due to the fact that webshim uses conditionally and deferred loading techniques, it turns out, that these techniques help to build large and fast responding websites and webapps. Webshim often initially loads only few (often only one file) and small files depending on the capabilities of the device and the requested features and then delays loading a bunch of other files either as soon as they are needed or after onload also depending on the capabilities of the device.
Often a developer might choose not to "enhance" a widget on smartphones and use built-in UI widgets, to create fast responding websites. In case a developer switches to Webshim's custom UI widgets, he gets fully responsive, font-size scalable and touch-optimized widgets.
Tip: Enlarge touch target size of UI widgets by using the font-size scalability:
/* enlarge mediacontrols by factor 1.5 from 16px to 24px */
.touchevents .mediaplayer {
font-size: 24px;
}
/* enlarge rangeslider, datepicker by factor ~1.5 from 13px to 19px */
.touchevents .ws-popover,
.touchevents .ws-range {
font-size: 19px;
}
@media (pointer:coarse), (-moz-touch-enabled), (-webkit-touch-enabled), (touch-enabled) {
.mediaplayer {
font-size: 24px;
}
.ws-popover,
.ws-range {
font-size: 19px;
}
}
All UI components of webshim are fully accessible and conform to WCAG 2.0 (Level A and AA) and are therefore also Section 508-compliant. Webshim not only adds some WAI-ARIA roles, but also conforms to the ARIA Best Practices and tests all widgets with real screenreaders.
While most scripts only use one JS file to include the whole behavior, Webshims is using the polyfiller.js file, a polyfiller
named AMD module, as an entry point and loads conditionally other resources. This means webshims needs to know, where those resources are.
The code inside of the polyfiller.js automatically detects the path to the script it is run from and assumes, that the shims
folder is in the same directory. This means, you have to make sure, that either the shims folder is placed parallel to the code of the polyfiller.js or to configure the path using the basePath
option.
//configure path manually
webshim.setOptions('basePath', '/yourFolderTo/shims/');
//polyfill
webshim.polyfill('forms mediaelement');
The following example, shows a very simple setup where the shims folder is parallel to the normal polyfiller.js file and the optimized js file (i.e.: app.min.js):
+-- projectfolder
|-- index.html
+-- scripts
+-- vendor
|-- jquery.js
+-- shims
|-- main.js
|-- require.js
|-- app.min.js (optimized code, which also includes the polyfiller.js code)
|-- polyfiller.js
A better way is to output the concatenated and compressed code into a different directory for deployment. For a webshims project this means beside an optimization task (concat and minify) often another simple task to copy the shims folder parallel to the optimized script package:
|-- projectfolder
|-- index.html
+-- bower_components
|-- jquery
+-- js-webshim
+-- dev
|-- polyfiller.js
|-- shims
|-- minified
+-- scripts
+-- vendor
|-- main.js (requires polyfiller.js from the **dev** folder)
|-- require.js
+-- optimized-scripts (folder with optimized scripts)
|-- app.min.js (optimized code, which also includes the polyfiller.js code)
|-- shims (copied shims folder from the **minified** folder)
Webshim offers a lot of different config options to change the behavior of widgets and controls. The pre-defined styles loaded by webshim are just an offer. A developer is appealed to enhance/change those styles. Even all animation are done with CSS and can be changed.
Webshim pre-defined selectors are sometime overqualified to minimize conflicts with existing page styles.
There are two different main strategies to customize webshim widget's styles. In case you only want to make small changes, you can simply override the styles with your own modification. But in case you either want to have full control or need to make more changes, it is wise to set loadStyles
to false
and write you own styles.
In case you are setting loadStyles
to false
you can either start from scratch or grab/copy the default styles from webshim included in the dev folder.
webshim.setOptions('loadStyles', false);
Some widget's also use inline styles to calculate the position or the dimension of the widget. This can be turned off depending on the widget settings. (Pro Tip: This can also improve performance, especially in old IE8 or some mobile browsers.):
webshim.setOptions({
loadStyles: false,
'forms-ext': {
widgets: {
calculateWidth: false
}
},
track: {
positionDisplay: false
}
});
In case you have made some nice changes and want to share those with the community, feel free to add those to the themes-directory and do start a pr.
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:
webshim.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:
webshim.addPolyfill('magicMethod', {
test: function(){
return ('magicMethod' in window);
},
d: ['es5'] // 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
webshim.register
:
webshim.register('magicMethod', function($, webshims, window, document, undefined, options){
// now you can use es5-feature feature
window.magicMethod = function(){
// your polyfill implementation
};
});
In case of a DOM extension, webshims lib comes with some useful extension-methods.
webshim.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
webshim.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:
webshim.defineNodeNamesProperty
(nodeNames, property, descriptor)
webshim.defineNodeNamesBooleanProperty
(nodeNames, property, descriptor)
// wait till the DOM-Extension feature is loaded
webshim.register('mediaelement', function($, webshims, window, document, undefined, options){
// webshims refers to jQuery.webshims in this function
// Implements a new IDL property on audio, video called currentTime
webshim.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
webshim.defineNodenamesProperty('audio video', 'HAVE_CURRENT_DATA', {
prop: {
value: 2
}
});
// Implements a new method called play on audio, video
webshim.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
webshim.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
});
});