<!DOCTYPE html>
<html>
<head>
<script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="setImmediate.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>JavaScript Array Processing Test</h1>
<p>Both chunk methods perform the same operations, but the timedChunk method takes "breaks" from processing to allow the browser to repaint.</p>
<ul style="list-style: none; ">
<li>CSS spinner: <div class="loader animated"></div></li>
<li>GIF spinner: <div class="k-loading-image"></div></li>
</ul>
<h2>chunk() Function</h2>
<p>This method will cause the GIF spinner to freeze, but the CSS spinner will continue.</p>
<p>
<button id="start-chunk-btn">Start chunk()</button>
<button id="reset-chunk-btn">Reset</button>
</p>
<div style="width: 500px; border: 1px solid black">
<div id="orig-progress" style="height: 16px; color: white; width: 100%;">
<div id="orig-label"></div>
</div>
</div>
<h2>timedChunk() Function</h2>
<p>This method will allow both spinners to keep spinning.</p>
<p>
<button id="start-timed-btn">Start timedChunk()</button>
<button id="reset-timed-btn">Reset</button>
</p>
<div style="width: 500px; border: 1px solid black">
<div id="new-progress" style="height: 16px; color: white; width: 100%;">
<div id="new-label"></div>
</div>
</div>
</body>
</html>
// Code goes here
$(function() {
function chunk(array, process, context, callback) {
var items = array.concat(); //clone the array
var start = +new Date();
var execute = function() {
process.call(context, items.shift(), start);
if (items.length <= 0) {
callback(array);
} else {
execute();
}
};
execute();
}
function timedChunk(items, process, context, callback) {
var todo = items.concat(); //create a clone of the original
var outerStart = +new Date();
var execute = function() {
var start = +new Date();
do {
process.call(context, todo.shift(), outerStart);
} while (todo.length > 0 && (+new Date() - start < 25));
if (todo.length > 0) {
setImmediate(execute);
} else {
callback(items);
}
};
execute();
}
var count = 10000;
var items = new Array(count);
for (var i = 0; i < count; i++) {
items[i] = i;
}
$('body').append('<style>.unit { width: 1px; }</style>');
function processOrig(item, start) {
var progress = $('#orig-progress');
$('#orig-label').text(+new Date() - start);
var units = progress.children('.unit').length;
var itemsPerUnit = Math.floor(count / progress.parent().width());
if (item % itemsPerUnit === 0) {
progress.append('<div class="unit"></div>');
}
}
function processNew(item, start) {
var progress = $('#new-progress');
$('#new-label').text(+new Date() - start);
var units = progress.children('.unit').length;
var itemsPerUnit = Math.floor(count / progress.parent().width());
if (item % itemsPerUnit === 0) {
progress.append('<div class="unit"></div>');
}
}
function startProcessing(button) {
resetTest();
var startTimedButton = $('#start-timed-btn');
var startButton = $('#start-chunk-btn');
var resetButton = $('#reset-chunk-btn');
startTimedButton[0].disabled = true;
startButton[0].disabled = true;
resetButton[0].disabled = true;
var origStart = +new Date();
chunk(items, processOrig, null, function() {
$('#orig-label').text(new Date() - origStart);
resetButton[0].disabled = false;
startTimedButton[0].disabled = false;
startButton[0].disabled = false;
});
}
function startTimedProcessing(button) {
resetTimedTest();
var startChunkButton = $('#start-chunk-btn');
var startButton = $('#start-timed-btn');
var resetButton = $('#reset-timed-btn');
startChunkButton[0].disabled = true;
startButton[0].disabled = true;
resetButton[0].disabled = true;
var newStart = +new Date();
timedChunk(items, processNew, null, function() {
$('#new-label').text(new Date() - newStart);
resetButton[0].disabled = false;
startChunkButton[0].disabled = false;
startButton[0].disabled = false;
});
}
function resetTest() {
var origprogress = $('#orig-progress .unit').remove();
$('#orig-label').text('');
$('#start-chunk-btn')[0].disabled = false;
}
function resetTimedTest() {
var newprogress = $('#new-progress .unit').remove();
$('#new-label').text('');
$('#start-timed-btn')[0].disabled = false;
}
$('body').on('click', '#start-chunk-btn', startProcessing);
$('body').on('click', '#start-timed-btn', startTimedProcessing);
$('body').on('click', '#reset-chunk-btn', resetTest);
$('body').on('click', '#reset-timed-btn', resetTimedTest);
});
/* Styles go here */
body, html {
font-family: arial, sans-serif;
font-size: 13px;
}
#new-progress,
#orig-progress {
position: relative;
}
#new-label,
#orig-label {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100px;
color: white;
}
.k-loading-image {
background-image: url('http://cdn.kendostatic.com/2014.2.903/styles/Bootstrap/loading-image.gif');
background-repeat: no-repeat;
background-position: center center;
width: 60px;
height: 50px;
}
li {
margin-bottom: 20px;
}
@-webkit-keyframes rotate {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes rotate {
from {
-moz-transform: rotate(0deg);
}
to {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes rotate {
from {
-o-transform: rotate(0deg);
}
to {
-o-transform: rotate(360deg);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.unit {
height: 16px;
background: navy;
color: white;
display: inline-block;
}
.loader {
font-size: 10px;
position: relative;
text-indent: -9999em;
background: none;
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
border-top: 8px solid rgba(0, 0, 0, 0.2);
border-right: 8px solid rgba(0, 0, 0, 0.2);
border-bottom: 8px solid rgba(0, 0, 0, 0.2);
border-left: 8px solid #333333;
}
.loader, .loader:after {
border-radius: 50%;
width: 40px;
height: 40px;
}
.loader.animated {
-moz-animation: rotate 1.1s infinite linear;
-webkit-animation: rotate 1.1s infinite linear;
animation: rotate 1.1s infinite linear;
}
(function (global, undefined) {
"use strict";
var tasks = (function () {
function Task(handler, args) {
this.handler = handler;
this.args = args;
}
Task.prototype.run = function () {
// See steps in section 5 of the spec.
if (typeof this.handler === "function") {
// Choice of `thisArg` is not in the setImmediate spec; `undefined` is in the setTimeout spec though:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
this.handler.apply(undefined, this.args);
} else {
var scriptSource = "" + this.handler;
/*jshint evil: true */
eval(scriptSource);
}
};
var nextHandle = 1; // Spec says greater than zero
var tasksByHandle = {};
var currentlyRunningATask = false;
return {
addFromSetImmediateArguments: function (args) {
var handler = args[0];
var argsToHandle = Array.prototype.slice.call(args, 1);
var task = new Task(handler, argsToHandle);
var thisHandle = nextHandle++;
tasksByHandle[thisHandle] = task;
return thisHandle;
},
runIfPresent: function (handle) {
// From the spec: "Wait until any invocations of this algorithm started before this one have completed."
// So if we're currently running a task, we'll need to delay this invocation.
if (!currentlyRunningATask) {
var task = tasksByHandle[handle];
if (task) {
currentlyRunningATask = true;
try {
task.run();
} finally {
delete tasksByHandle[handle];
currentlyRunningATask = false;
}
}
} else {
// Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
// "too much recursion" error.
global.setTimeout(function () {
tasks.runIfPresent(handle);
}, 0);
}
},
remove: function (handle) {
delete tasksByHandle[handle];
}
};
}());
function canUseNextTick() {
// Don't get fooled by e.g. browserify environments.
return typeof process === "object" &&
Object.prototype.toString.call(process) === "[object process]";
}
function canUseMessageChannel() {
return !!global.MessageChannel;
}
function canUsePostMessage() {
// The test against `importScripts` prevents this implementation from being installed inside a web worker,
// where `global.postMessage` means something completely different and can't be used for this purpose.
if (!global.postMessage || global.importScripts) {
return false;
}
var postMessageIsAsynchronous = true;
var oldOnMessage = global.onmessage;
global.onmessage = function () {
postMessageIsAsynchronous = false;
};
global.postMessage("", "*");
global.onmessage = oldOnMessage;
return postMessageIsAsynchronous;
}
function canUseReadyStateChange() {
return "document" in global && "onreadystatechange" in global.document.createElement("script");
}
function installNextTickImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
process.nextTick(function () {
tasks.runIfPresent(handle);
});
return handle;
};
}
function installMessageChannelImplementation(attachTo) {
var channel = new global.MessageChannel();
channel.port1.onmessage = function (event) {
var handle = event.data;
tasks.runIfPresent(handle);
};
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
channel.port2.postMessage(handle);
return handle;
};
}
function installPostMessageImplementation(attachTo) {
// Installs an event handler on `global` for the `message` event: see
// * https://developer.mozilla.org/en/DOM/window.postMessage
// * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
var MESSAGE_PREFIX = "com.bn.NobleJS.setImmediate" + Math.random();
function isStringAndStartsWith(string, putativeStart) {
return typeof string === "string" && string.substring(0, putativeStart.length) === putativeStart;
}
function onGlobalMessage(event) {
// This will catch all incoming messages (even from other windows!), so we need to try reasonably hard to
// avoid letting anyone else trick us into firing off. We test the origin is still this window, and that a
// (randomly generated) unpredictable identifying prefix is present.
if (event.source === global && isStringAndStartsWith(event.data, MESSAGE_PREFIX)) {
var handle = event.data.substring(MESSAGE_PREFIX.length);
tasks.runIfPresent(handle);
}
}
if (global.addEventListener) {
global.addEventListener("message", onGlobalMessage, false);
} else {
global.attachEvent("onmessage", onGlobalMessage);
}
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
// Make `global` post a message to itself with the handle and identifying prefix, thus asynchronously
// invoking our onGlobalMessage listener above.
global.postMessage(MESSAGE_PREFIX + handle, "*");
return handle;
};
}
function installReadyStateChangeImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement("script");
scriptEl.onreadystatechange = function () {
tasks.runIfPresent(handle);
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
return handle;
};
}
function installSetTimeoutImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
global.setTimeout(function () {
tasks.runIfPresent(handle);
}, 0);
return handle;
};
}
var setImmediate = global.setImmediate;
if (!setImmediate) {
// If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
var attachTo = typeof Object.getPrototypeOf === "function" && "setTimeout" in Object.getPrototypeOf(global) ?
Object.getPrototypeOf(global)
: global;
if (canUseNextTick()) {
// For Node.js before 0.9
installNextTickImplementation(attachTo);
} else if (canUsePostMessage()) {
// For non-IE10 modern browsers
installPostMessageImplementation(attachTo);
} else if (canUseMessageChannel()) {
// For web workers, where supported
installMessageChannelImplementation(attachTo);
} else if (canUseReadyStateChange()) {
// For IE 6–8
installReadyStateChangeImplementation(attachTo);
} else {
// For older browsers
installSetTimeoutImplementation(attachTo);
}
attachTo.clearImmediate = tasks.remove;
} else if (typeof setImmediate === 'function') {
global.setImmediate = function() {
return setImmediate.apply(null, arguments);
};
var clearImmediate = global.clearImmediate;
global.clearImmediate = function(id) {
clearImmediate(id);
};
}
}(typeof global === "object" && global ? global : this));