├── index.js
├── component.json
├── tests
├── eventsource.html
├── offline-events.html
└── range.html
├── sessionStorage.js
├── bower.json
├── README.md
├── MIT-LICENCE.txt
├── offline-events.js
├── input-target.js
├── dataset.js
├── classList.js
├── Storage.js
├── range.js
├── EventSource.js
└── device-motion-polyfill.js
/index.js:
--------------------------------------------------------------------------------
1 | require( "./classList.js" );
2 | require( "./dataset.js" );
3 | require( "./EventSource.js" );
4 | require( "./input-target.js" );
5 | require( "./offline-events.js" );
6 | require( "./range.js" );
7 | require( "./sessionStorage.js" );
8 | require( "./Storage.js" );
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "polyfills",
3 | "scripts": [
4 | "classList.js",
5 | "dataset.js",
6 | "device-motion-polyfill.js",
7 | "EventSource.js",
8 | "index.js",
9 | "input-target.js",
10 | "offline-events.js",
11 | "range.js",
12 | "sessionStorage.js",
13 | "Storage.js"
14 | ],
15 | "main": "index.js"
16 | }
--------------------------------------------------------------------------------
/tests/eventsource.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | event source test
6 |
7 |
8 |
9 |
21 |
22 |
--------------------------------------------------------------------------------
/sessionStorage.js:
--------------------------------------------------------------------------------
1 | if (!sessionStorage && JSON) {
2 | sessionStorage = (function () {
3 | var data = window.name ? JSON.parse(window.name) : {};
4 |
5 | return {
6 | clear: function () {
7 | data = {};
8 | window.name = '';
9 | },
10 | getItem: function (key) {
11 | return data[key] === undefined ? null : data[key];
12 | },
13 | removeItem: function (key) {
14 | delete data[key];
15 | window.name = JSON.stringify(data);
16 | },
17 | setItem: function (key, value) {
18 | data[key] = value;
19 | window.name = JSON.stringify(data);
20 | }
21 | };
22 | })();
23 | }
24 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "html5-polyfills",
3 | "description": "Collection of polyfills by Remy Sharp",
4 | "homepage": "https://github.com/remy/polyfills",
5 | "keywords": [
6 | "polyfill",
7 | "client",
8 | "browser"
9 | ],
10 | "author": {
11 | "name": "Remy Sharp",
12 | "email": "remy@leftlogic.com"
13 | },
14 | "version": "0.0.20130621",
15 | "readme": "This is my own collection of code snippets that support different native features of browsers using JavaScript (and if required, Flash in some cases).",
16 | "readmeFilename": "README.md",
17 | "_id": "html5-polyfills@0.0.20130621",
18 | "repository": {
19 | "type": "git",
20 | "url": "git://github.com/remy/polyfills.git"
21 | }
22 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Polyfills
2 |
3 | > A way of getting the browser to behave and support the latest specifications.
4 |
5 | This is my own collection of code snippets that support different native features of browsers using JavaScript (and if required, Flash in some cases).
6 |
7 | ## Component Installation
8 |
9 | You can install polyfills using component:
10 |
11 | ```
12 | component install remy/polyfills
13 | ```
14 |
15 | If you'd like to include all polyfills (except device motion), you can just:
16 |
17 | ```javascript
18 | require( 'polyfills' );
19 | ```
20 |
21 | If you'd only like a specific polyfill, you can require indivual ones like this:
22 |
23 | ```javascript
24 | require( 'polyfills/classList' );
25 |
26 | // now we can use classList in lots of browsers!
27 | element.classList.add( 'foo' );
28 | ```
29 |
30 | # License
31 |
32 | License: http://rem.mit-license.org
33 |
--------------------------------------------------------------------------------
/MIT-LICENCE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 Remy Sharp, http://remysharp.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/offline-events.js:
--------------------------------------------------------------------------------
1 | // Working demo: http://jsbin.com/ozusa6/2/
2 | (function () {
3 |
4 | function triggerEvent(type) {
5 | var event = document.createEvent('HTMLEvents');
6 | event.initEvent(type, true, true);
7 | event.eventName = type;
8 | (document.body || window).dispatchEvent(event);
9 | }
10 |
11 | function testConnection() {
12 | // make sync-ajax request
13 | var xhr = new XMLHttpRequest();
14 | // phone home
15 | xhr.open('HEAD', '/', false); // async=false
16 | try {
17 | xhr.send();
18 | onLine = true;
19 | } catch (e) {
20 | // throws NETWORK_ERR when disconnected
21 | onLine = false;
22 | }
23 |
24 | return onLine;
25 | }
26 |
27 | var onLine = true,
28 | lastOnLineStatus = true;
29 |
30 | // note: this doesn't allow us to define a getter in Safari
31 | navigator.__defineGetter__("onLine", testConnection);
32 | testConnection();
33 |
34 | if (onLine === false) {
35 | lastOnLineStatus = false;
36 | // trigger offline event
37 | triggerEvent('offline');
38 | }
39 |
40 | setInterval(function () {
41 | testConnection();
42 | if (onLine !== lastOnLineStatus) {
43 | triggerEvent(onLine ? 'online' : 'offline');
44 | lastOnLineStatus = onLine;
45 | }
46 | }, 5000); // 5 seconds, made up - can't find docs to suggest interval time
47 | })();
48 |
--------------------------------------------------------------------------------
/tests/offline-events.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Online and offline event tests
5 |
12 |
13 |
14 | Directions: disconnect the internet connection from your machine and watch the status switch from online to offline (and back again if you want to see the online event fire).
15 | Note that this polyfill phones home so it will also fire the offline event if the server this script is hosted on, goes down (feature or bug? who knows!).
16 | ONLINE
17 |
18 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/range.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | range polyfill...
5 |
6 |
7 |
8 |
9 | 1
10 | 2
11 | 3
12 | 4
13 | 5
14 |
15 |
16 |
17 |
18 | 3
19 | 4
20 | 5
21 |
22 |
23 |
24 | 3
25 | 4
26 | 5
27 |
28 |
29 | Last two examples are as the previous, but add data-type-range-step="5":
30 |
31 | 3
32 | 4
33 | 5
34 |
35 |
36 | This one has the last value set to 51, instead of 50:
37 |
38 | 3
39 | 4
40 | 5
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/input-target.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | // setup for detection
4 | var form = document.createElement('form'),
5 | input = document.createElement('input'),
6 | body = document.body,
7 | id = 'f' + +new Date,
8 | inputTargetSupported = false;
9 |
10 | // insert into DOM, work out if it's supported
11 | form.setAttribute('id', id);
12 | input.setAttribute('form', id);
13 | body.appendChild(form);
14 | body.appendChild(input);
15 |
16 | inputTargetSupported = input.form !== null;
17 | body.removeChild(form);
18 | body.removeChild(input);
19 |
20 | // if not, hook click handlers to all existing submit elements
21 | function click(event) {
22 | event = event || window.event;
23 |
24 | // http://www.quirksmode.org/js/events_properties.html#target
25 | var target = event.target || event.srcElement;
26 | if (target.nodeType === 3) target = target.parentNode;
27 |
28 | if (target.nodeName === 'INPUT' && target.type === 'submit' && target.form === null) {
29 | form = document.getElementById(target.getAttribute('form'));
30 | var clone = target.cloneNode(true);
31 | clone.style.display = 'none';
32 | form.appendChild(clone);
33 | clone.click(); // doing this because form.submit won't fire handles the way I want
34 | form.removeChild(clone);
35 | }
36 | }
37 |
38 | if (!inputTargetSupported) {
39 | body.addEventListener ? body.addEventListener('click', click, false) : body.attachEvent('onclick', click);
40 | }
41 |
42 | })();
43 |
--------------------------------------------------------------------------------
/dataset.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var forEach = [].forEach,
3 | regex = /^data-(.+)/,
4 | dashChar = /\-([a-z])/ig,
5 | el = document.createElement('div'),
6 | mutationSupported = false,
7 | match
8 | ;
9 |
10 | function detectMutation() {
11 | mutationSupported = true;
12 | this.removeEventListener('DOMAttrModified', detectMutation, false);
13 | }
14 |
15 | function toCamelCase(s) {
16 | return s.replace(dashChar, function (m,l) { return l.toUpperCase(); });
17 | }
18 |
19 | function updateDataset() {
20 | var dataset = {};
21 | forEach.call(this.attributes, function(attr) {
22 | if (match = attr.name.match(regex))
23 | dataset[toCamelCase(match[1])] = attr.value;
24 | });
25 | return dataset;
26 | }
27 |
28 | // only add support if the browser doesn't support data-* natively
29 | if (el.dataset != undefined) return;
30 |
31 | el.addEventListener('DOMAttrModified', detectMutation, false);
32 | el.setAttribute('foo', 'bar');
33 |
34 | function defineElementGetter (obj, prop, getter) {
35 | if (Object.defineProperty) {
36 | Object.defineProperty(obj, prop,{
37 | get : getter
38 | });
39 | } else {
40 | obj.__defineGetter__(prop, getter);
41 | }
42 | }
43 |
44 | defineElementGetter(Element.prototype, 'dataset', mutationSupported
45 | ? function () {
46 | if (!this._datasetCache) {
47 | this._datasetCache = updateDataset.call(this);
48 | }
49 | return this._datasetCache;
50 | }
51 | : updateDataset
52 | );
53 |
54 | document.addEventListener('DOMAttrModified', function (event) {
55 | delete event.target._datasetCache;
56 | }, false);
57 | })();
58 |
--------------------------------------------------------------------------------
/classList.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | if (typeof window.Element === "undefined" || "classList" in document.documentElement) return;
4 |
5 | var prototype = Array.prototype,
6 | push = prototype.push,
7 | splice = prototype.splice,
8 | join = prototype.join;
9 |
10 | function DOMTokenList(el) {
11 | this.el = el;
12 | // The className needs to be trimmed and split on whitespace
13 | // to retrieve a list of classes.
14 | var classes = el.className.replace(/^\s+|\s+$/g,'').split(/\s+/);
15 | for (var i = 0; i < classes.length; i++) {
16 | push.call(this, classes[i]);
17 | }
18 | };
19 |
20 | DOMTokenList.prototype = {
21 | add: function(token) {
22 | if(this.contains(token)) return;
23 | push.call(this, token);
24 | this.el.className = this.toString();
25 | },
26 | contains: function(token) {
27 | return this.el.className.indexOf(token) != -1;
28 | },
29 | item: function(index) {
30 | return this[index] || null;
31 | },
32 | remove: function(token) {
33 | if (!this.contains(token)) return;
34 | for (var i = 0; i < this.length; i++) {
35 | if (this[i] == token) break;
36 | }
37 | splice.call(this, i, 1);
38 | this.el.className = this.toString();
39 | },
40 | toString: function() {
41 | return join.call(this, ' ');
42 | },
43 | toggle: function(token) {
44 | if (!this.contains(token)) {
45 | this.add(token);
46 | } else {
47 | this.remove(token);
48 | }
49 |
50 | return this.contains(token);
51 | }
52 | };
53 |
54 | window.DOMTokenList = DOMTokenList;
55 |
56 | function defineElementGetter (obj, prop, getter) {
57 | if (Object.defineProperty) {
58 | Object.defineProperty(obj, prop,{
59 | get : getter
60 | });
61 | } else {
62 | obj.__defineGetter__(prop, getter);
63 | }
64 | }
65 |
66 | defineElementGetter(Element.prototype, 'classList', function () {
67 | return new DOMTokenList(this);
68 | });
69 |
70 | })();
71 |
--------------------------------------------------------------------------------
/Storage.js:
--------------------------------------------------------------------------------
1 | if (typeof window.localStorage == 'undefined' || typeof window.sessionStorage == 'undefined') (function () {
2 |
3 | var Storage = function (type) {
4 | function createCookie(name, value, days) {
5 | var date, expires;
6 |
7 | if (days) {
8 | date = new Date();
9 | date.setTime(date.getTime()+(days*24*60*60*1000));
10 | expires = "; expires="+date.toGMTString();
11 | } else {
12 | expires = "";
13 | }
14 | document.cookie = name+"="+value+expires+"; path=/";
15 | }
16 |
17 | function readCookie(name) {
18 | var nameEQ = name + "=",
19 | ca = document.cookie.split(';'),
20 | i, c;
21 |
22 | for (i=0; i < ca.length; i++) {
23 | c = ca[i];
24 | while (c.charAt(0)==' ') {
25 | c = c.substring(1,c.length);
26 | }
27 |
28 | if (c.indexOf(nameEQ) == 0) {
29 | return c.substring(nameEQ.length,c.length);
30 | }
31 | }
32 | return null;
33 | }
34 |
35 | function setData(data) {
36 | data = JSON.stringify(data);
37 | if (type == 'session') {
38 | window.name = data;
39 | } else {
40 | createCookie('localStorage', data, 365);
41 | }
42 | }
43 |
44 | function clearData() {
45 | if (type == 'session') {
46 | window.name = '';
47 | } else {
48 | createCookie('localStorage', '', 365);
49 | }
50 | }
51 |
52 | function getData() {
53 | var data = type == 'session' ? window.name : readCookie('localStorage');
54 | return data ? JSON.parse(data) : {};
55 | }
56 |
57 |
58 | // initialise if there's already data
59 | var data = getData();
60 |
61 | return {
62 | length: 0,
63 | clear: function () {
64 | data = {};
65 | this.length = 0;
66 | clearData();
67 | },
68 | getItem: function (key) {
69 | return data[key] === undefined ? null : data[key];
70 | },
71 | key: function (i) {
72 | // not perfect, but works
73 | var ctr = 0;
74 | for (var k in data) {
75 | if (ctr == i) return k;
76 | else ctr++;
77 | }
78 | return null;
79 | },
80 | removeItem: function (key) {
81 | delete data[key];
82 | this.length--;
83 | setData(data);
84 | },
85 | setItem: function (key, value) {
86 | data[key] = value+''; // forces the value to a string
87 | this.length++;
88 | setData(data);
89 | }
90 | };
91 | };
92 |
93 | if (typeof window.localStorage == 'undefined') window.localStorage = new Storage('local');
94 | if (typeof window.sessionStorage == 'undefined') window.sessionStorage = new Storage('session');
95 |
96 | })();
97 |
--------------------------------------------------------------------------------
/range.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This was from a proposal that James Edwards / Brothercake had during
3 | * 2011 Highland Fling - to use select elements as a starting point for
4 | * the range element - so it's a pretty darn good fit, I put this together
5 | * during his Q&A. In the end I needed to lift the detection code from
6 | * Modernizr - which credit really goes to @miketaylr.
7 | * My code starts from "if (bool) {"
8 | *
9 | * This code is looking for and will progressively
10 | * enhance to a input[type=range] copying much of the attributes across.
11 | */
12 | !function () {
13 | var rangeTest = document.createElement('input'),
14 | smile = ':)';
15 | rangeTest.setAttribute('type', 'range');
16 |
17 | var bool = rangeTest.type !== 'text';
18 | if (bool) {
19 | rangeTest.style.cssText = 'position:absolute;visibility:hidden;';
20 | rangeTest.value = smile;
21 | if (rangeTest.style.WebkitAppearance !== undefined ) {
22 |
23 | document.body.appendChild(rangeTest);
24 | defaultView = document.defaultView;
25 |
26 | // Safari 2-4 allows the smiley as a value, despite making a slider
27 | bool = defaultView.getComputedStyle &&
28 | defaultView.getComputedStyle(rangeTest, null).WebkitAppearance !== 'textfield' &&
29 | // Mobile android web browser has false positive, so must
30 | // check the height to see if the widget is actually there.
31 | (rangeTest.offsetHeight !== 0);
32 |
33 | document.body.removeChild(rangeTest);
34 | }
35 | } else {
36 | bool = rangeTest.value == smile;
37 | }
38 |
39 | // if the input[range] is natively supported, then upgrade the
40 | // into a range element.
41 | if (bool) {
42 | function firstChild(el, nodeName) {
43 | nodeName = nodeName.toUpperCase();
44 | if (el.firstChild.nodeName === nodeName) {
45 | return el.firstChild;
46 | } else {
47 | return el.firstChild.nextSibling;
48 | }
49 | }
50 |
51 | function lastChild(el, nodeName) {
52 | nodeName = nodeName.toUpperCase();
53 | if (el.lastChild.nodeName === nodeName) {
54 | return el.lastChild;
55 | } else {
56 | return el.lastChild.previousSibling;
57 | }
58 | }
59 |
60 | var selects = document.getElementsByTagName('select'),
61 | i = 0;
62 |
63 | for (; i < selects.length; i++) {
64 | if (selects[i].getAttribute('data-type') == 'range') (function (select) {
65 | var range = document.createElement('input'),
66 | parent = select.parentNode;
67 |
68 | range.setAttribute('type', 'range');
69 | // works with the select element removed from the DOM
70 | select = parent.replaceChild(range, select);
71 | range.autofocus = select.autofocus;
72 | range.disabled = select.disabled;
73 | range.autocomplete = select.autocomplete; // eh? how would this even work?
74 | range.className = select.className;
75 | range.id = select.id;
76 | range.style = select.style;
77 | range.tabindex = select.tabindex;
78 | range.title = select.title;
79 | range.min = firstChild(select, 'option').value;
80 | range.max = lastChild(select, 'option').value;
81 | range.value = select.value;
82 | range.name = select.name;
83 | // Add step support
84 | if ( select.getAttribute('data-type-range-step') ) {
85 | range.step = select.getAttribute('data-type-range-step');
86 | }
87 | // yeah, this is filth, but it's because getElementsByTagName is
88 | // a live DOM collection, so when we removed the select element
89 | // the selects object reduced in length. Freaky, eh?
90 | i--;
91 | })(selects[i]);
92 | }
93 | }
94 | }();
95 |
--------------------------------------------------------------------------------
/EventSource.js:
--------------------------------------------------------------------------------
1 | ;(function (global) {
2 |
3 | if ("EventSource" in global) return;
4 |
5 | var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
6 |
7 | var EventSource = function (url) {
8 | var eventsource = this,
9 | interval = 500, // polling interval
10 | lastEventId = null,
11 | cache = '';
12 |
13 | if (!url || typeof url != 'string') {
14 | throw new SyntaxError('Not enough arguments');
15 | }
16 |
17 | this.URL = url;
18 | this.readyState = this.CONNECTING;
19 | this._pollTimer = null;
20 | this._xhr = null;
21 |
22 | function pollAgain(interval) {
23 | eventsource._pollTimer = setTimeout(function () {
24 | poll.call(eventsource);
25 | }, interval);
26 | }
27 |
28 | function poll() {
29 | try { // force hiding of the error message... insane?
30 | if (eventsource.readyState == eventsource.CLOSED) return;
31 |
32 | // NOTE: IE7 and upwards support
33 | var xhr = new XMLHttpRequest();
34 | xhr.open('GET', eventsource.URL, true);
35 | xhr.setRequestHeader('Accept', 'text/event-stream');
36 | xhr.setRequestHeader('Cache-Control', 'no-cache');
37 | // we must make use of this on the server side if we're working with Android - because they don't trigger
38 | // readychange until the server connection is closed
39 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
40 |
41 | if (lastEventId != null) xhr.setRequestHeader('Last-Event-ID', lastEventId);
42 | cache = '';
43 |
44 | xhr.timeout = 50000;
45 | xhr.onreadystatechange = function () {
46 | if (this.readyState == 3 || (this.readyState == 4 && this.status == 200)) {
47 | // on success
48 | if (eventsource.readyState == eventsource.CONNECTING) {
49 | eventsource.readyState = eventsource.OPEN;
50 | eventsource.dispatchEvent('open', { type: 'open' });
51 | }
52 |
53 | var responseText = '';
54 | try {
55 | responseText = this.responseText || '';
56 | } catch (e) {}
57 |
58 | // process this.responseText
59 | var parts = responseText.substr(cache.length).split("\n"),
60 | eventType = 'message',
61 | data = [],
62 | i = 0,
63 | line = '';
64 |
65 | cache = responseText;
66 |
67 | // TODO handle 'event' (for buffer name), retry
68 | for (; i < parts.length; i++) {
69 | line = parts[i].replace(reTrim, '');
70 | if (line.indexOf('event') == 0) {
71 | eventType = line.replace(/event:?\s*/, '');
72 | } else if (line.indexOf('retry') == 0) {
73 | var retry = parseInt(line.replace(/retry:?\s*/, ''));
74 | if(!isNaN(retry)) { interval = retry; }
75 | } else if (line.indexOf('data') == 0) {
76 | data.push(line.replace(/data:?\s*/, ''));
77 | } else if (line.indexOf('id:') == 0) {
78 | lastEventId = line.replace(/id:?\s*/, '');
79 | } else if (line.indexOf('id') == 0) { // this resets the id
80 | lastEventId = null;
81 | } else if (line == '') {
82 | if (data.length) {
83 | var event = new MessageEvent(data.join('\n'), eventsource.url, lastEventId);
84 | eventsource.dispatchEvent(eventType, event);
85 | data = [];
86 | eventType = 'message';
87 | }
88 | }
89 | }
90 |
91 | if (this.readyState == 4) pollAgain(interval);
92 | // don't need to poll again, because we're long-loading
93 | } else if (eventsource.readyState !== eventsource.CLOSED) {
94 | if (this.readyState == 4) { // and some other status
95 | // dispatch error
96 | eventsource.readyState = eventsource.CONNECTING;
97 | eventsource.dispatchEvent('error', { type: 'error' });
98 | pollAgain(interval);
99 | } else if (this.readyState == 0) { // likely aborted
100 | pollAgain(interval);
101 | } else {
102 | }
103 | }
104 | };
105 |
106 | xhr.send();
107 |
108 | setTimeout(function () {
109 | if (true || xhr.readyState == 3) xhr.abort();
110 | }, xhr.timeout);
111 |
112 | eventsource._xhr = xhr;
113 |
114 | } catch (e) { // in an attempt to silence the errors
115 | eventsource.dispatchEvent('error', { type: 'error', data: e.message }); // ???
116 | }
117 | };
118 |
119 | poll(); // init now
120 | };
121 |
122 | EventSource.prototype = {
123 | close: function () {
124 | // closes the connection - disabling the polling
125 | this.readyState = this.CLOSED;
126 | clearInterval(this._pollTimer);
127 | this._xhr.abort();
128 | },
129 | CONNECTING: 0,
130 | OPEN: 1,
131 | CLOSED: 2,
132 | dispatchEvent: function (type, event) {
133 | var handlers = this['_' + type + 'Handlers'];
134 | if (handlers) {
135 | for (var i = 0; i < handlers.length; i++) {
136 | handlers[i].call(this, event);
137 | }
138 | }
139 |
140 | if (this['on' + type]) {
141 | this['on' + type].call(this, event);
142 | }
143 | },
144 | addEventListener: function (type, handler) {
145 | if (!this['_' + type + 'Handlers']) {
146 | this['_' + type + 'Handlers'] = [];
147 | }
148 |
149 | this['_' + type + 'Handlers'].push(handler);
150 | },
151 | removeEventListener: function (type, handler) {
152 | var handlers = this['_' + type + 'Handlers'];
153 | if (!handlers) {
154 | return;
155 | }
156 | for (var i = handlers.length - 1; i >= 0; --i) {
157 | if (handlers[i] === handler) {
158 | handlers.splice(i, 1);
159 | break;
160 | }
161 | }
162 | },
163 | onerror: null,
164 | onmessage: null,
165 | onopen: null,
166 | readyState: 0,
167 | URL: ''
168 | };
169 |
170 | var MessageEvent = function (data, origin, lastEventId) {
171 | this.data = data;
172 | this.origin = origin;
173 | this.lastEventId = lastEventId || '';
174 | };
175 |
176 | MessageEvent.prototype = {
177 | data: null,
178 | type: 'message',
179 | lastEventId: '',
180 | origin: ''
181 | };
182 |
183 | if ('module' in global) module.exports = EventSource;
184 | global.EventSource = EventSource;
185 |
186 | })(this);
187 |
--------------------------------------------------------------------------------
/device-motion-polyfill.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DeviceMotion and DeviceOrientation polyfill
3 | * by Remy Sharp / leftlogic.com
4 | * MIT http://rem.mit-license.org
5 | *
6 | * Usage: used for testing motion events, include
7 | * script in head of test document and allow popup
8 | * to open to control device orientation.
9 | */
10 | (!document.DeviceOrientationEvent || !document.DeviceMotionEvent) && (function () {
11 |
12 | // thankfully we don't have to do anything, because the event only fires on the window object
13 | var polyfill = {
14 | motion: !document.DeviceMotionEvent,
15 | orientation: !document.DeviceOrientationEvent
16 | };
17 |
18 | if (polyfill.orientation) window.DeviceOrientationEvent = function () {};
19 |
20 | if (polyfill.motion) window.DeviceMotionEvent = function () {};
21 |
22 | // images - yes I do like to be self contained don't I?! :)
23 | var imageSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFsAAACpCAYAAABEbEGLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxODkwMDQ2NTNDNEYxMUUxQTYyOTk2M0QwMjU4OThGOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxODkwMDQ2NjNDNEYxMUUxQTYyOTk2M0QwMjU4OThGOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4OTAwNDYzM0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4OTAwNDY0M0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+jKamsAAAAtNJREFUeNrs2t1t01AYgGG3dACPkA3qEcIlV5QJSCdAnaAwAWKCsgFs0HQChwmaS+6SThCOKwuigtP4J+ln9LzSRyWaRMmTE8fHcJLt3yRNkeY8U9VDmkWa+ZAPOktTptmYf84qzU29GDtXQG49111XM7xuUy3QfF/oa2DHAbeijwReABp8vjVh+zI8zFw4fBxv7q3qF1jdp1s7Qx2ut9UfZ0Gh+26BJ313dANXRDuvXg38xuf1NjrKoeTxMBKlq/rCzlCt01zWP0N0GujjtjzQ4y4iYS+DPJd8ZI/bCTtKHw7wmLNIJwBngbCn9QZgOcDZSF4/XqgzrUjY26ds0//xZPs0E2zYgg1bsGHDFuwRt2sHOcfTqSJruPi1C/s1t07dZg2XGxxGHLNhCzZswYYNW7BhCzZs2IINW7BhCzZs2IINW7BhwxZs2IINW7BhwxZs2IING7ZgwxZs2IING7ZgwxZs2LAFG7Zgw4Yt2LAFG7Zgw4Yt2LAFGzZswYYt2LAFGzZswYYt2LBhCzZswYYt2LBhCzZswYYNW7BhCzZs2IINW7BhCzZs2IINW7BhwxZs2IIdvbMdv7vG06lJF+yP3BxGYAs2bGcj8VukmadZb/1dkWaaJh/Li6hO8TaB57YGbSofwWvYjAH7psWiqd6QVWTsyMfsr2kuW9x+3vL2viDrlmmuOtzve/0mwW7RlydfhG36BLv9Cu3zqVjCbgf2kve3qRl5OezjtY6KPXnh+x/sMPIj4POa9oSOhr3YfnLRdlv3PV7YTfSdcBnwCX7uAF0E3apfbD/JWdAnOWsJvRrLp7TM4l6Meu4S6kXgi1C/V/XJk5VRRj1tqq953GV/X89+X/+MuhN+1/TLqIeTMU759BP5quEUZZqp7+WCN2l+7nNjK3zAFb3vt3sJr9X0/l9kM+g7Z1WfMT27az1puQ2uVvu5Q/JjD9mff/Hfq18CDADFyFpnRW9JPwAAAABJRU5ErkJggg==';
24 |
25 | var imageBackSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFsAAACpCAIAAADLDtbcAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFCRTMwQTNDNjMxMUUxQTYyOTk2M0QwMjU4OThGOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFCRTMwQjNDNjMxMUUxQTYyOTk2M0QwMjU4OThGOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4OTAwNDZCM0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4OTAwNDZDM0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+zWf2VgAABt9JREFUeNrsnU1P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';
26 |
27 | var id = (+new Date).toString(32),
28 | body = document.documentElement, // yep - cheatin' Yud'up! :)
29 | height = 320;
30 |
31 | // if the url hash doesn't contain tiltremote (our key) then fireup the popup, else we are the popup
32 | if (window.location.hash.indexOf('tiltremote') === -1) {
33 | initServer();
34 | } else {
35 | initRemote();
36 | }
37 |
38 | function initServer() {
39 | // old way didn't work. Shame, but I like the new way too :)
40 | // var remote = window.open('data:text/html,', 'Tilt', 'width=300,height=' + height);
41 |
42 | var remote = window.open(window.location.toString() + '#tiltremote', 'Tilt', 'width=300,height=' + height);
43 | if (!remote) {
44 | alert('The remote control for the orientation event uses a popup')
45 | }
46 |
47 | // TODO add remote-tilt.com support
48 | }
49 |
50 | function initRemote() {
51 | var TO_RADIANS = Math.PI / 180;
52 | orientation = {
53 | alpha: 180,
54 | beta: 0,
55 | gamma: 0
56 | },
57 | accelerationIncludingGravity = { x: 0, y: 0, z: -9.81 },
58 | guid = (+new Date).toString(32);
59 |
60 | function update(fromInput) {
61 | var preview = document.getElementById('preview');
62 | preview.style.webkitTransform = 'rotateY('+ gamma.value + 'deg) rotate3d(1,0,0, '+ (beta.value*-1) + 'deg)';
63 | preview.parentNode.style.webkitTransform = 'rotate(' + (180-orientation.alpha) + 'deg)';
64 |
65 | if (!fromInput) {
66 | for (var key in orientation) {
67 | var el = document.getElementById(key);
68 | oninput.call(window, { target: el });
69 | }
70 | }
71 |
72 | fireEvent();
73 | }
74 |
75 | function fireDeviceOrienationEvent() {
76 | var event = document.createEvent('HTMLEvents');
77 | event.initEvent('deviceorientation', true, true);
78 | event.eventName = 'deviceorientation';
79 | event.alpha = orientation.alpha;
80 | event.beta = orientation.beta;
81 | event.gamma = orientation.gamma;
82 |
83 | window.opener.dispatchEvent(event);
84 | }
85 |
86 | function fireDeviceMotionEvent() {
87 | var event = document.createEvent('HTMLEvents');
88 | event.initEvent('devicemotion', true, true);
89 | event.eventName = 'devicemotion';
90 |
91 | event.accelerationIncludingGravity = {};
92 | event.accelerationIncludingGravity.x = accelerationIncludingGravity.x;
93 | event.accelerationIncludingGravity.y = accelerationIncludingGravity.y;
94 | event.accelerationIncludingGravity.z = accelerationIncludingGravity.z;
95 |
96 | window.opener.dispatchEvent(event);
97 | }
98 |
99 | function fireEvent() {
100 | if (polyfill.orientation) fireDeviceOrienationEvent();
101 | if (polyfill.motion) fireDeviceMotionEvent();
102 | }
103 |
104 | // hides the old body - but doesn't prevent the JavaScript from running.
105 | // just a clean up thing - lame, but tidier
106 | setTimeout(function () {
107 | var bodies = document.getElementsByTagName('body'),
108 | body = bodies[1];
109 | if (bodies.length == 2) {
110 | if (body.id == guid) body = bodies[0];
111 | }
112 | document.documentElement.removeChild(body);
113 | });
114 |
115 | body.innerHTML = ['Motion Emulator ',
116 | '',
130 | '',
131 | '',
132 | '', // end of controls
140 | '',
141 | '
',
142 | '
',
143 | '
',
144 | '
',
145 | '
',
146 | 'south ',
147 | ''
148 | ].join('');
149 |
150 | function oninput(event) {
151 | var target = event.target;
152 | if (target.nodeName == 'INPUT') {
153 |
154 | var value = target.value * 1;
155 |
156 | if (target.id == 'beta') {
157 | // parseFloat + toFixed avoids the massive 0.00000000 and infinitely small numbers
158 | accelerationIncludingGravity.z = parseFloat( (Math.sin( (TO_RADIANS * (value - 90))) * 9.81).toFixed(10) );
159 | accelerationIncludingGravity.y = parseFloat((Math.sin( (TO_RADIANS * (value - 180))) * 9.81).toFixed(10));
160 | value = parseFloat((Math.sin(value * TO_RADIANS) * 90).toFixed(10));
161 | } else if (target.id == 'gamma') {
162 | accelerationIncludingGravity.x = parseFloat( (Math.sin( (TO_RADIANS * (value - 180))) * -9.81).toFixed(10) );
163 | }
164 |
165 | document.getElementById('o' + target.id.substring(0, 1)).value = parseFloat(value.toFixed(2));
166 |
167 | orientation[target.id] = value;
168 |
169 | if (this !== window) update(true); // i.e. if not called manually
170 | }
171 | }
172 |
173 | body.addEventListener('input', oninput, false);
174 |
175 | var down = false,
176 | last = { x: null, y: null },
177 | pov = document.getElementById('pov'),
178 | alpha = document.getElementById('alpha'),
179 | beta = document.getElementById('beta'),
180 | gamma = document.getElementById('gamma');
181 |
182 | pov.addEventListener('mousedown', function (event) {
183 | down = true;
184 | last.x = event.pageX;
185 | last.y = event.pageY;
186 | event.preventDefault();
187 | }, false);
188 |
189 | body.addEventListener('mousemove', function (event) {
190 | if (down) {
191 | var dx = (last.x - event.pageX)// * 0.1,
192 | dy = (last.y - event.pageY); // * 0.1;
193 | last.x = event.pageX;
194 | last.y = event.pageY;
195 | gamma.value -= dx;
196 | beta.value -= dy;
197 | update();
198 | }
199 | }, false);
200 |
201 | body.addEventListener('mouseup', function (event) {
202 | down = false;
203 | }, false);
204 |
205 | body.addEventListener('click', function (event) {
206 | var target = event.target;
207 | if (target.nodeName == 'BUTTON') {
208 | if (target.id == 'flat') {
209 | alpha.value = 180;
210 | beta.value = 0;
211 | gamma.value = 0;
212 |
213 | }
214 | update();
215 | } else if (target.id == 'wobble') {
216 | if (target.checked) startShake();
217 | else stopShake();
218 | }
219 | }, false);
220 |
221 | function startShake() {
222 | shake = setInterval(function () {
223 | alpha.value = parseFloat(alpha.value) + (Math.random() * (Math.random() < 0.5 ? 1 : -1) * 0.05);
224 | beta.value = parseFloat(beta.value) + (Math.random() * (Math.random() < 0.5 ? 1 : -1) * 0.05);
225 | gamma.value = parseFloat(gamma.value) + (Math.random() * (Math.random() < 0.5 ? 1 : -1) * 0.05);
226 | update();
227 | }, 100);
228 | }
229 |
230 | function stopShake() {
231 | clearInterval(shake);
232 | }
233 |
234 | update();
235 | }
236 |
237 | })();
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
--------------------------------------------------------------------------------