| Server IP : 182.53.201.61 / Your IP : 216.73.217.175 Web Server : Apache/2.2.15 (Fedora) System : Linux km10.dyndns.org 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 User : apache ( 48) PHP Version : 5.3.3 Disable Function : NONE MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /var/www/html/qcardm/js/ |
Upload File : |
/**
* PixelLab Resource Loader
* Loads resources while providing progress updates.
*/
function PxLoader(settings) {
// merge settings with defaults
settings = settings || {};
// how frequently we poll resources for progress
if (settings.statusInterval == null) {
settings.statusInterval = 5000; // every 5 seconds by default
}
// delay before logging since last progress change
if (settings.loggingDelay == null) {
settings.loggingDelay = 20 * 1000; // log stragglers after 20 secs
}
// stop waiting if no progress has been made in the moving time window
if (settings.noProgressTimeout == null) {
settings.noProgressTimeout = Infinity; // do not stop waiting by default
}
var entries = [],
// holds resources to be loaded with their status
progressListeners = [],
timeStarted, progressChanged = +new Date;
/**
* The status of a resource
* @enum {number}
*/
var ResourceState = {
QUEUED: 0,
WAITING: 1,
LOADED: 2,
ERROR: 3,
TIMEOUT: 4
};
// places non-array values into an array.
var ensureArray = function(val) {
if (val == null) {
return [];
}
if (Array.isArray(val)) {
return val;
}
return [val];
};
// add an entry to the list of resources to be loaded
this.add = function(resource) {
// ensure tags are in an object
resource.tags = new PxLoaderTags(resource.tags);
// ensure priority is set
if (resource.priority == null) {
resource.priority = Infinity;
}
entries.push({
resource: resource,
status: ResourceState.QUEUED
});
};
this.addProgressListener = function(callback, tags) {
progressListeners.push({
callback: callback,
tags: new PxLoaderTags(tags)
});
};
this.addCompletionListener = function(callback, tags) {
progressListeners.push({
tags: new PxLoaderTags(tags),
callback: function(e) {
if (e.completedCount === e.totalCount) {
callback();
}
}
});
};
// creates a comparison function for resources
var getResourceSort = function(orderedTags) {
// helper to get the top tag's order for a resource
orderedTags = ensureArray(orderedTags);
var getTagOrder = function(entry) {
var resource = entry.resource,
bestIndex = Infinity;
for (var i = 0; i < resource.tags.length; i++) {
for (var j = 0; j < Math.min(orderedTags.length, bestIndex); j++) {
if (resource.tags[i] == orderedTags[j] && j < bestIndex) {
bestIndex = j;
if (bestIndex === 0) break;
}
if (bestIndex === 0) break;
}
}
return bestIndex;
};
return function(a, b) {
// check tag order first
var aOrder = getTagOrder(a),
bOrder = getTagOrder(b);
if (aOrder < bOrder) return -1;
if (aOrder > bOrder) return 1;
// now check priority
if (a.priority < b.priority) return -1;
if (a.priority > b.priority) return 1;
return 0;
}
};
this.start = function(orderedTags) {
timeStarted = +new Date;
// first order the resources
var compareResources = getResourceSort(orderedTags);
entries.sort(compareResources);
// trigger requests for each resource
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
entry.status = ResourceState.WAITING;
entry.resource.start(this);
}
// do an initial status check soon since items may be loaded from the cache
setTimeout(statusCheck, 100);
};
var statusCheck = function() {
var checkAgain = false,
noProgressTime = (+new Date) - progressChanged,
timedOut = (noProgressTime >= settings.noProgressTimeout),
shouldLog = (noProgressTime >= settings.loggingDelay);
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
if (entry.status !== ResourceState.WAITING) {
continue;
}
// see if the resource has loaded
if (entry.resource.checkStatus) {
entry.resource.checkStatus();
}
// if still waiting, mark as timed out or make sure we check again
if (entry.status === ResourceState.WAITING) {
if (timedOut) {
entry.resource.onTimeout();
} else {
checkAgain = true;
}
}
}
// log any resources that are still pending
if (shouldLog && checkAgain) {
log();
}
if (checkAgain) {
setTimeout(statusCheck, settings.statusInterval);
}
};
this.isBusy = function() {
for (var i = 0, len = entries.length; i < len; i++) {
if (entries[i].status === ResourceState.QUEUED || entries[i].status === ResourceState.WAITING) {
return true;
}
}
return false;
};
var onProgress = function(resource, statusType) {
// find the entry for the resource
var entry = null;
for (var i = 0, len = entries.length; i < len; i++) {
if (entries[i].resource === resource) {
entry = entries[i];
break;
}
}
// we have already updated the status of the resource
if (entry == null || entry.status !== ResourceState.WAITING) {
return;
}
entry.status = statusType;
progressChanged = +new Date;
var numResourceTags = resource.tags.length;
// fire callbacks for interested listeners
for (var i = 0, numListeners = progressListeners.length; i < numListeners; i++) {
var listener = progressListeners[i],
shouldCall;
if (listener.tags.length === 0) {
// no tags specified so always tell the listener
shouldCall = true;
} else {
// listener only wants to hear about certain tags
shouldCall = resource.tags.contains(listener.tags);
}
if (shouldCall) {
sendProgress(entry, listener);
}
}
};
this.onLoad = function(resource) {
onProgress(resource, ResourceState.LOADED);
};
this.onError = function(resource) {
onProgress(resource, ResourceState.ERROR);
};
this.onTimeout = function(resource) {
onProgress(resource, ResourceState.TIMEOUT);
};
// sends a progress report to a listener
var sendProgress = function(updatedEntry, listener) {
// find stats for all the resources the caller is interested in
var completed = 0,
total = 0;
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i],
includeResource = false;
if (listener.tags.length === 0) {
// no tags specified so always tell the listener
includeResource = true;
} else {
includeResource = entry.resource.tags.contains(listener.tags);
}
if (includeResource) {
total++;
if (entry.status === ResourceState.LOADED || entry.status === ResourceState.ERROR || entry.status === ResourceState.TIMEOUT) {
completed++;
}
}
}
listener.callback({
// info about the resource that changed
resource: updatedEntry.resource,
// should we expose StatusType instead?
loaded: (updatedEntry.status === ResourceState.LOADED),
error: (updatedEntry.status === ResourceState.ERROR),
timeout: (updatedEntry.status === ResourceState.TIMEOUT),
// updated stats for all resources
completedCount: completed,
totalCount: total
});
};
// prints the status of each resource to the console
var log = this.log = function(showAll) {
if (!window.console) {
return;
}
var elapsedSeconds = Math.round((+new Date - timeStarted) / 1000);
window.console.log('PxLoader elapsed: ' + elapsedSeconds + ' sec');
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
if (!showAll && entry.status !== ResourceState.WAITING) {
continue;
}
var message = 'PxLoader: #' + i + ' ' + entry.resource.getName();
switch(entry.status) {
case ResourceState.QUEUED:
message += ' (Not Started)';
break;
case ResourceState.WAITING:
message += ' (Waiting)';
break;
case ResourceState.LOADED:
message += ' (Loaded)';
break;
case ResourceState.ERROR:
message += ' (Error)';
break;
case ResourceState.TIMEOUT:
message += ' (Timeout)';
break;
}
if (entry.resource.tags.length > 0) {
message += ' Tags: [' + entry.resource.tags.join(',') + ']';
}
window.console.log(message);
}
};
}
// Tag object to handle tag intersection; once created not meant to be changed
// Performance rationale: http://jsperf.com/lists-indexof-vs-in-operator/3
function PxLoaderTags(values) {
this.array = [];
this.object = {};
this.value = null; // single value
this.length = 0;
if (values !== null && values !== undefined) {
if (Array.isArray(values)) {
this.array = values;
} else if (typeof values === 'object') {
for (var key in values) {
this.array.push(key);
}
} else {
this.array.push(values);
this.value = values;
}
this.length = this.array.length;
// convert array values to object with truthy values, used by contains function below
for (var i = 0; i < this.length; i++) {
this.object[this.array[i]] = true;
}
}
// compare this object with another; return true if they share at least one value
this.contains = function(other) {
if (this.length === 0 || other.length === 0) {
return false;
} else if (this.length === 1 && this.value !== null) {
if (other.length === 1) {
return this.value === other.value;
} else {
return other.object.hasOwnProperty(this.value);
}
} else if (other.length < this.length) {
return other.contains(this); // better to loop through the smaller object
} else {
for (var key in this.object) {
if (other.object[key]) {
return true;
}
}
return false;
}
}
}
// shims to ensure we have newer Array utility methods
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) == '[object Array]';
};
}