UNPKG

processing-js

Version:

For the project website, visit http://processingjs.org

339 lines (306 loc) • 11.4 kB
/** * 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; };