processing-js
Version:
For the project website, visit http://processingjs.org
339 lines (306 loc) • 11.4 kB
JavaScript
/**
* Finalise the Processing.js object.
*/
module.exports = function finalizeProcessing(Processing, options) {
// unpack options
var window = options.window,
document = options.document,
XMLHttpRequest = window.XMLHttpRequest,
noop = options.noop,
isDOMPresent = options.isDOMPresent,
version = options.version,
undef;
// versioning
Processing.version = (version ? version : "@DEV-VERSION@");
// Share lib space
Processing.lib = {};
/**
* External libraries can be added to the global Processing
* objects with the `registerLibrary` function.
*/
Processing.registerLibrary = function(name, library) {
Processing.lib[name] = library;
if(library.hasOwnProperty("init")) {
library.init(defaultScope);
}
};
/**
* This is the object that acts as our version of PApplet.
* This can be called as Processing.Sketch() or as
* Processing.Sketch(function) in which case the function
* must be an already-compiled-to-JS sketch function.
*/
Processing.Sketch = function(attachFunction) {
this.attachFunction = attachFunction;
this.options = {
pauseOnBlur: false,
globalKeyEvents: false
};
/* Optional Sketch event hooks:
* onLoad - parsing/preloading is done, before sketch starts
* onSetup - setup() has been called, before first draw()
* onPause - noLoop() has been called, pausing draw loop
* onLoop - loop() has been called, resuming draw loop
* onFrameStart - draw() loop about to begin
* onFrameEnd - draw() loop finished
* onExit - exit() done being called
*/
this.onLoad = noop;
this.onSetup = noop;
this.onPause = noop;
this.onLoop = noop;
this.onFrameStart = noop;
this.onFrameEnd = noop;
this.onExit = noop;
this.params = {};
this.imageCache = {
pending: 0,
images: {},
// Opera requires special administration for preloading
operaCache: {},
// Specify an optional img arg if the image is already loaded in the DOM,
// otherwise href will get loaded.
add: function(href, img) {
// Prevent muliple loads for an image, in case it gets
// preloaded more than once, or is added via JS and then preloaded.
if (this.images[href]) {
return;
}
if (!isDOMPresent) {
this.images[href] = null;
}
// No image in the DOM, kick-off a background load
if (!img) {
img = new Image();
img.onload = (function(owner) {
return function() {
owner.pending--;
};
}(this));
this.pending++;
img.src = href;
}
this.images[href] = img;
// Opera will not load images until they are inserted into the DOM.
if (window.opera) {
var div = document.createElement("div");
div.appendChild(img);
// we can't use "display: none", since that makes it invisible, and thus not load
div.style.position = "absolute";
div.style.opacity = 0;
div.style.width = "1px";
div.style.height= "1px";
if (!this.operaCache[href]) {
document.body.appendChild(div);
this.operaCache[href] = div;
}
}
}
};
this.sourceCode = undefined;
this.attach = function(processing) {
// either attachFunction or sourceCode must be present on attach
if(typeof this.attachFunction === "function") {
this.attachFunction(processing);
} else if(this.sourceCode) {
var func = ((new Function("return (" + this.sourceCode + ");"))());
func(processing);
this.attachFunction = func;
} else {
throw "Unable to attach sketch to the processing instance";
}
};
this.toString = function() {
var i;
var code = "((function(Sketch) {\n";
code += "var sketch = new Sketch(\n" + this.sourceCode + ");\n";
for(i in this.options) {
if(this.options.hasOwnProperty(i)) {
var value = this.options[i];
code += "sketch.options." + i + " = " +
(typeof value === 'string' ? '\"' + value + '\"' : "" + value) + ";\n";
}
}
for(i in this.imageCache) {
if(this.options.hasOwnProperty(i)) {
code += "sketch.imageCache.add(\"" + i + "\");\n";
}
}
// TODO serialize fonts
code += "return sketch;\n})(Processing.Sketch))";
return code;
};
};
/**
* aggregate all source code into a single file, then rewrite that
* source and bind to canvas via new Processing(canvas, sourcestring).
* @param {CANVAS} canvas The html canvas element to bind to
* @param {String[]} source The array of files that must be loaded
* @param {Function} onComplete A callback, called with the sketch as the argument.
*/
var loadSketchFromSources = Processing.loadSketchFromSources = function(canvas, sources, onComplete) {
var code = [], errors = [], sourcesCount = sources.length, loaded = 0;
function ajaxAsync(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var error;
if (xhr.status !== 200 && xhr.status !== 0) {
error = "Invalid XHR status " + xhr.status;
} else if (xhr.responseText === "") {
// Give a hint when loading fails due to same-origin issues on file:/// urls
if ( ("withCredentials" in new XMLHttpRequest()) &&
(new XMLHttpRequest()).withCredentials === false &&
window.location.protocol === "file:" ) {
error = "XMLHttpRequest failure, possibly due to a same-origin policy violation. You can try loading this page in another browser, or load it from http://localhost using a local webserver. See the Processing.js README for a more detailed explanation of this problem and solutions.";
} else {
error = "File is empty.";
}
}
callback(xhr.responseText, error);
}
};
xhr.open("GET", url, true);
if (xhr.overrideMimeType) {
xhr.overrideMimeType("application/json");
}
xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); // no cache
xhr.send(null);
}
function loadBlock(index, filename) {
function callback(block, error) {
code[index] = block;
++loaded;
if (error) {
errors.push(filename + " ==> " + error);
}
if (loaded === sourcesCount) {
if (errors.length === 0) {
// This used to throw, but it was constantly getting in the way of debugging where things go wrong!
var sketch = new Processing(canvas, code.join("\n"));
if (onComplete) {
onComplete(sketch);
}
} else {
throw "Processing.js: Unable to load pjs sketch files: " + errors.join("\n");
}
}
}
if (filename.charAt(0) === '#') {
// trying to get script from the element
var scriptElement = document.getElementById(filename.substring(1));
if (scriptElement) {
callback(scriptElement.text || scriptElement.textContent);
} else {
callback("", "Unable to load pjs sketch: element with id \'" + filename.substring(1) + "\' was not found");
}
return;
}
ajaxAsync(filename, callback);
}
for (var i = 0; i < sourcesCount; ++i) {
loadBlock(i, sources[i]);
}
};
/**
* Automatic initialization function.
*/
var init = function() {
document.removeEventListener('DOMContentLoaded', init, false);
var i;
// before running through init, clear the instances list, to prevent
// sketch duplication when page content is dynamically swapped without
// swapping out processing.js
while (Processing.instances.length > 0) {
for (i = Processing.instances.length - 1; i >= 0; i--) {
if (Processing.instances[i]) {
Processing.instances[i].exit();
}
}
}
var canvas = document.getElementsByTagName('canvas'),
filenames;
for (i = 0, l = canvas.length; i < l; i++) {
// datasrc and data-src are deprecated.
var processingSources = canvas[i].getAttribute('data-processing-sources');
if (processingSources === null) {
// Temporary fallback for datasrc and data-src
processingSources = canvas[i].getAttribute('data-src');
if (processingSources === null) {
processingSources = canvas[i].getAttribute('datasrc');
}
}
if (processingSources) {
filenames = processingSources.split(/\s+/g);
for (var j = 0; j < filenames.length;) {
if (filenames[j]) {
j++;
} else {
filenames.splice(j, 1);
}
}
loadSketchFromSources(canvas[i], filenames);
}
}
// also process all <script>-indicated sketches, if there are any
var s, last, source, instance,
nodelist = document.getElementsByTagName('script'),
scripts=[];
// snapshot the DOM, as the nodelist is only a DOM view, and is
// updated instantly when a script element is added or removed.
for (s = nodelist.length - 1; s >= 0; s--) {
scripts.push(nodelist[s]);
}
// iterate over all script elements to see if they contain Processing code
for (s = 0, last = scripts.length; s < last; s++) {
var script = scripts[s];
if (!script.getAttribute) {
continue;
}
var type = script.getAttribute("type");
if (type && (type.toLowerCase() === "text/processing" || type.toLowerCase() === "application/processing")) {
var target = script.getAttribute("data-processing-target");
canvas = undef;
if (target) {
canvas = document.getElementById(target);
} else {
var nextSibling = script.nextSibling;
while (nextSibling && nextSibling.nodeType !== 1) {
nextSibling = nextSibling.nextSibling;
}
if (nextSibling && nextSibling.nodeName.toLowerCase() === "canvas") {
canvas = nextSibling;
}
}
if (canvas) {
if (script.getAttribute("src")) {
filenames = script.getAttribute("src").split(/\s+/);
loadSketchFromSources(canvas, filenames);
continue;
}
source = script.textContent || script.text;
instance = new Processing(canvas, source);
}
}
}
};
/**
* automatic loading of all sketches on the page
*/
document.addEventListener('DOMContentLoaded', init, false);
/**
* Make Processing run through init after already having
* been set up for a page. This function exists mostly for pages
* that swap content in/out without reloading a page.
*/
Processing.reload = init;
/**
* Disable the automatic loading of all sketches on the page.
* This will work as long as it's issued before DOMContentLoaded.
*/
Processing.disableInit = function() {
document.removeEventListener('DOMContentLoaded', init, false);
};
// done.
return Processing;
};