Skip to content

Instantly share code, notes, and snippets.

@hongymagic
Created February 23, 2012 03:13
Show Gist options
  • Save hongymagic/1889742 to your computer and use it in GitHub Desktop.
Save hongymagic/1889742 to your computer and use it in GitHub Desktop.
Pre-fetcher, Pre-loader, Pre-fixer, Pre-mium stuff.
/* vim: set noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 autoindent smartindent: */
/* jslint devel: false, browser: true, node: true, eqeq: true, white: true, passfail: true, plusplus: false, regexp: true, maxerr: 50, indent: 4 */
//
// Pre.js 1.0.0
//
// (c) 20011-2012 David Hong, @hongymagic.
// Underscore is freely distributable under the MIT license.
// Portions of Pre.js are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and DocumentCloud's Underscore.js.
// For all details and documentation:
// http://hongymagic.github.com/pre.js
//
// ## Global namespace, Pre
var Pre = (function () {
'use strict';
var
Pre,
prototype,
// Regular expressions to help determine file type. Pre.js supports the
// following file types:
//
// 1. Images: jpg, jpeg, gif, bmp, gif, png
// 2. Audio: wav, weba, ogg, mp3, m4a, aiff
//
// #### TODO
//
// 1. JSON
// 2. HTML
// 3. SVG
types = {
"image":
{
regex: /^.*\.(jpe?g|png|bmp|gif)$/i,
get: function (resource) {
var image = new Image();
image.src = resource;
return image;
}
},
"audio":
{
regex: /^.*\.(wav|weba|ogg|mp3|m4a|aiff)$/i,
get: function (resource) {
var audio = new Audio();
audio.preload = "auto";
audio.src = resource;
return audio;
}
}
},
// Returns the type of resource based on file types defined above.
type = function (resource) {
var rtype, matches;
for (rtype in types) {
if (types.hasOwnProperty(rtype)) {
matches = types[rtype].regex.exec(resource);
if (matches && matches.length > 1) {
return rtype;
}
}
}
throw new Error('Unable to determine extension type: ' + resource);
},
// Cache inline functions for faster! performance! Woo yeah!
ArrayProto = Array.prototype,
ObjectProto = Object.prototype,
FuncProto = Function.prototype,
isArray = Array.isArray,
reduce = ArrayProto.reduce,
each = ArrayProto.each,
map = ArrayProto.map,
slice = ArrayProto.slice,
bind = FuncProto.bind,
toString = ObjectProto.toString,
// ### Custom inline functions
// Returns a flattened array, **usage**:
//
// flatten([1, 2, 3], [4, 5, 6]);
// => [1, 2, 3, 4, 5, 6];
flatten = function (array, shallow) {
return array.reduce(function (memo, value) {
if (isArray(value)) {
return memo.concat(shallow ? value : flatten(value));
}
memo[memo.length] = value;
return memo;
}, []);
},
isFunction = function (func) {
return toString.call(func) == '[object Function]';
},
// Create an asset data for internal use
createAsset = function (resource, asset) {
return {
url: resource,
resource: resource,
asset: asset
};
},
// Create a wrapped Callback object for internal use
createCallback = function (callback, context) {
return {
context: context,
callback: callback
};
},
// Store downloaded assets locally, main advantage is for audio assets
// we can cheat and play it over and over again without recreating the
// <audio> (or Audio) object by setting its `currentTime` property to 0.
loaded = Object.create(null),
done = Object.create(null),
// Load an asset and store internally
load = function (resource, callback, context) {
var
rtype = type(resource),
object,
asset;
if (!isArray(loaded[rtype])) {
loaded[rtype] = [];
}
object = types[rtype].get(resource);
if (isFunction(callback)) {
object.addEventListener('load', bind.call(callback, context || object), false);
}
asset = createAsset(resource, object);
loaded[rtype].push(asset);
return asset;
},
// Used by Pre instances to store and resolve events. It is a simple events
// handler
events = Object.create(null);
// ## The Pre class
// Define `Pre` class and store a reference to its `prototype` for further
// extension. Use it like a class object, **usage**:
//
// var game = new Pre;
// var game = Object.create(Pre.prototype);
prototype = (Pre = function (options) {
this.options = options;
}).prototype;
// ## Extending the Pre.prototype
// Start your prefetches, **usage**:
//
// Pre.fetch('image.jpg');
// Pre.fetch('landscape.jpg', 'sun.png', 'clouds.png');
// Pre.fetch(['landscape.jpg', 'sun.png', 'clouds.png']);
//
// TODO:
//
// - store each asset in its own category
// - start loading immediately!
prototype.fetch = function () {
var
resources = slice.call(arguments),
asset,
pre = this;
if (resources.length > 0) {
resources = flatten(resources, []);
}
resources.forEach(function (resource) {
asset = load(resource, function (event) {
this.resolve('load', arguments);
}, this);
this.resolve('fetch', resource, asset);
}, this);
return this;
};
// **Alias**: `pre.load`
prototype.load = prototype.fetch;
// Create a deferred style event binding
//
// var game = new Pre;
//
// game.when('fetch', function (resource) {
// console.log('Currently fetching: ' + resource);
// }, Pre);
//
// Context is optional.
//
// You can also force resolve:
//
// game.resolve('fetch');
prototype.when = function (event, callback, context) {
if (!isFunction(callback)) {
throw new Error('Cannot register a callback: typeof callback = ' + toString.call(callback));
}
if (!events[event]) {
events[event] = [];
}
events[event].push(createCallback(callback, context));
return this;
};
// **Alias**: `pre.on`
prototype.on = prototype.when;
// Resolve an event, unlike Promises/A, an event can be triggered
// multiple times.
prototype.resolve = function (event /*, resource, asset */) {
if (!events[event]) {
return;
}
var
callbacks = events[event],
args = slice.call(arguments, 1);
callbacks.forEach(function (node) {
node.callback.apply(node.context || args[0], args);
}, this);
return this;
};
// #### DEBUG ONLY
//
// Get the internal store
prototype.all = function () {
return loaded;
};
return Pre;
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment