CBDL/CBDL.js

809 lines
24 KiB
JavaScript

/* **** CBDL - Cross-browser DirectMedia Layer
*
* look at: http://en.wikipedia.org/wiki/SFML
*/
var CBDL = CBDL || {};
CBDL.version = 0.1;
// TODO: completely redo requires & library includes. At the moment, it is broken and disallows multiple Apps from starting if they both require libraries.
/*
===============================================================================
CBDL.App
Object that should be the base for all CBDL-based applications. Contains the
bare-minimum logic, members, and vars that any CBDL app should hold.
Usage:
some_app = new CBDL.App();
some_app.Init();
===============================================================================
*/
CBDL.App = CBDL.App || function() {};
CBDL.App.prototype.Main = function() {};
CBDL.App.prototype.Go = function() {
if (this.requires) {
this._includes = new CBDL.Includes(this);
for (var require in this.requires) {
this._includes.add(this.requires[require]);
}
}
if (this._includes) {
if (this._includes.state != CBDL.states.LOADED) {
this.includeCallback = function(return_value) {
if (return_value < 0) { // fail
console.log("CBDL error: includes failed to load!");
return return_value;
}
this.Main();
};
} else if (this._includes.state == CBDL.states.LOADED) {
this.Main();
}
} else {
this.Main();
}
};
CBDL.App.prototype.include = function(some_library) {
if (!this._includes) {
this._includes = new CBDL.Includes(this);
}
this._includes.add(some_library);
};
/* called when all includes succeed or fail */
CBDL.App.prototype.includeCallback = function(return_value) {
if (return_value < 0) { // fail
console.log("CBDL error: includes failed to load!");
return return_value;
} else {
this.Main();
}
};
/*
===============================================================================
CBDL.Includes
Object that manages includes for an App. Attempts to load all includes
specified by the App, providing they are provided prior to the App's onExecute
member function being called.
TODO: fix on mobile, yoo
===============================================================================
*/
CBDL.Includes = function(app) {
_Includes = this;
this.app = app;
this.state = 0x00;
this.current = 0;
this.includes = [];
this.add = function(some_library) {
if (this.state != CBDL.states.LOADED) {
this.includes.push(some_library);
if (this.state != CBDL.states.LOADING) {
this.load();
}
}
};
this.load = function() {
this.state = CBDL.states.LOADING;
if (this.current < this.includes.length) {
this.netLoad();
} else {
this.state = CBDL.states.LOADED;
this.app.includeCallback();
}
};
this.netLoad = function() {
var exists = false;
// Compare some_library string to every script.src so as to disallow
// multiple loading of the same library.
// TODO: implement getElementsByTagName for <=IE8
var script_list = document.head.getElementsByTagName('script');
for(script in script_list) {
if (script_list[script].src) {
if (script_list[script].src.substring(script_list[script].src.length-_Includes.includes[_Includes.current].length) == _Includes.includes[_Includes.current]) {
exists = true;
}
}
}
if (exists == false) {
var element = document.createElement("script");
element.type = "text/javascript";
element.src = _Includes.includes[_Includes.current];
element.onload = function() {
_Includes.state = 0x00;
_Includes.current++;
_Includes.load(); // load next file
}
document.getElementsByTagName('head')[0].appendChild(element);
} else {
_Includes.state = 0x00;
_Includes.current++;
_Includes.load(); // load next file
}
// if (!/*@cc_on!@*/false) {
/* var request = new XMLHttpRequest();
} else {
var request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.onreadystatechange = function() {
if (this.readyState == 4) { // done loading
console.log(this.status);
if (this.status == 200) {
var element = document.createElement("script");
element.type = "text/javascript";
element.text = this.responseText;
document.getElementsByTagName('head')[0].appendChild(element);
_Includes.state = 0x00;
_Includes.current++;
_Includes.load(); // load next file
} else {
_Includes.state = CBDL.states.FAILED;
}
}
}
request.open('GET', _Includes.includes[_Includes.current], true);
request.send(null);
*/
};
}
/*
================
CBDL.inherit(baseClass, newClass)
Function that inherits the properties from baseClass into newClass
================
*/
CBDL.inherit = function(base_class, new_class) {
base_class.call(new_class);
new_class.constructor = new_class;
}
CBDL.extend = function(base_class, new_class) {
new_class.prototype = new base_class;
new_class.prototype.constructor = base_class; // fix constructor
}
/*
===============================================================================
CBDL.Loop(scope, callback, interval)
Object that acts as a simple interface to creating and managing loops within
the script. Attempts to call callback() after X interval has passed. Interval
is modified internally to adjust for the time callback() takes to finish. If
the time is beyond the defined interval, then callback() is called with a 0ms
delay.
If the interval is not passed, or is passed as 0, then the Loop will rely
on the callback() to return an interval value.
Usage:
new_loop = new CBDL.Loop(some_func, 50);
new_loop.start(50); // calls some_func() after 50ms and repeats every 50ms.
Or:
some_func = function() { return(1000); };
new_loop = new CBDL.Loop(some_func);
new_loop.start(); // calls some_func() every 1000ms.
===============================================================================
*/
CBDL.Loop = function(scope, callback, interval) {
this.scope = scope;
this.callback = callback;
this.interval = (interval ? interval : 0);
this.time_start = 0;
this.time_end = 0;
this.time_delay = this.interval;
this.running = false;
this.start = function(delay) {
this.running = true;
if (delay) {
setTimeout(
(function(_Loop) {
return function() {
_Loop.loop.call(_Loop);
}
})(this),
delay);
//setTimeout(_Loop.loop, _Loop.time_delay);
} else {
this.loop();
}
};
this.stop = function() {
this.running = false;
};
this.loop = function() {
if (this.running) {
this.time_start = new Date().getTime();
if (this.interval == 0) { // acquire time_delay from callback return
this.time_delay = this.callback.call(this.scope, this.time_start - this.time_end);
this.time_end = new Date().getTime();
} else {
this.callback.call(this.scope, this.time_start-this.time_end); // return delta to callback
this.time_end = new Date().getTime();
this.time_delay = this.time_end-this.time_start;
this.time_delay = (this.time_delay > this.interval ? 0 : this.interval - this.time_delay);
}
setTimeout(
(function(_Loop) {
return function() {
_Loop.loop.call(_Loop);
}
})(this),
this.time_delay);
//setTimeout(this.callLoop.call, this);
}
};
this.callLoop = function() {
this.loop();
}
}
// TODO: Add CallbackLoop - an alternative object that loops only on some event
CBDL.EventLoop = function(scope, callback, events) {
this.scope = scope;
this.time_start = 0;
this.time_end = 0;
this.running = false;
this.start = function() {
};
this.stop = function() {
};
}
/*
================
CBDL.useNamespace(object)
Sets the window parent to own all of the defined object's members. Functions
in a fashion similar to setting the namespace in C++.
Usage:
CBDL.dump(); // prints CBDL version
CBDL.useNamespace(CBDL);
dump(); // prints CBDL version
================
*/
CBDL.useNamespace = function(object) {
var parent = window;
for (var member in object) {
parent[member] = object[member];
}
}
/*
================
CBDL.globalize(name, variable)
Adds the passed variable/function/object to a new var in the "global"
namespace via the passed name.
Usage:
some_function() {
var foo = 'bar';
}
console.log(foo); // undefined
some_function() {
var foo = 'bar';
CBDL.globalize("foo", foo); // window.foo = 'bar', effectively
}
console.log(foo); // prints 'bar'
================
*/
CBDL.globalize = function(name, variable) {
var parent = window;
parent[name] = parent[name] || variable;
}
/*
===============================================================================
CBDL.Include(filename)
Internal object created by calls to CBDL.include(filename). Simply stores the
filename and the current state via the 'state' member (bitmask).
===============================================================================
*/
CBDL.Include = function(filename) {
this.filename = filename;
this.state = 0x00;
}
CBDL.current_include = 0;
CBDL.includes = [];
CBDL.states = {OPEN: 0x00, LOADING:0x01, LOADED:0x02, FAILED:0x03};
/*
================
CBDL.include(filename)
Method which should be invoked by the program implementing CBDL. It provides
a nice method for including new source files/libraries located within the same
directory as the invoking script.
Usage:
CBDL.include("CBDL_video.js"); // loads CBDL_video.js, making all objects/vars,
etc. available to the calling script.
================
*/
CBDL.include = function(filename) {
if (typeof filename == "string") {
CBDL.includes.push(new CBDL.Include(filename));
if (CBDL.includes[CBDL.current_include].state === CBDL.states.OPEN) {
CBDL.loadInclude(CBDL.includes[CBDL.current_include]);
//CBDL.nextInclude();
}
} else {
for (var file in filename) {
CBDL.include(filename[file]);
}
}
}
/*
================
CBDL.nextInclude()
Internal method that is called when an Include finishes loading, so as to
load the next Include within the includes list.
Usage:
CBDL.nextInclude();
================
*/
CBDL.nextInclude = function() {
if (CBDL.includes[CBDL.current_include+1] instanceof CBDL.Include) {
CBDL.current_include++;
CBDL.loadInclude(CBDL.includes[CBDL.current_include]);
} else {
// CBDL.onReady();
// CBDL.onReady = function(){};
}
}
/*
================
CBDL.loadInclude(Include)
Internal method that attempts to use XMLHttpRequest or otherwise to
dynamically load the specified Include. Upon success, a new <script> is
added to the page's <head> and the state of the Include is changed to LOADED.
Usage:
new_include = new CBDL.Include("CBDL_net.js");
CBDL.loadInclude(new_include);
console.log("state is: " + new_include.state);
================
*/
CBDL.loadInclude = function(include) {
var element = document.createElement("script");
element.type = "text/javascript";
element.src = include.filename;
element.onload = function() {
include.state = CBDL.states.LOADED;
CBDL.nextInclude();
}
document.getElementsByTagName('head')[0].appendChild(element);
// if (!/*@cc_on!@*/false) {
/* var request = new XMLHttpRequest();
} else {
var request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.onreadystatechange = function() {
if (this.readyState == 4) { // done loading
if (this.status == 200) {
include.element = document.createElement("script");
include.element.type = "text/javascript";
include.element.text = this.responseText;
document.getElementsByTagName('head')[0].appendChild(include.element);
include.state = CBDL.states.LOADED;
} else {
include.state = CBDL.states.FAILED;
}
CBDL.nextInclude();
} else if (this.readyState == 2) { // loading
include.state = CBDL.states.LOADING;
}
}
request.open('GET', include.filename, true);
request.send(null);
*/
}
CBDL.EventTrigger = function(context, event_type, target, bubble) {
CBDL.addEvent(context, event_type, target, bubble);
};
/*
===============================================================================
CBDL.Event(event_masks)
Object that can be instantized to monitor a variety of event types, either
through the passed event_masks bitmask, or by calling the Event Object's
addEvent method.
Remake:
Given that the JavaScript Event model uses string values for identifying
event types(event.type), it makes little sense to use a bitmask for managing
multiple event types. This is somewhat sad, as bitmasks are certainly more
efficient than strings (and doing string comparisons).
TODO: Do a performance test of straight Event string comparison vs. an
associative array comparison. e.g.:
if (event.type == 'mousemove') {
console.log('yea');
}
// bit comparison
if (events[event.type] == MOUSEMOVE) {
}
===============================================================================
*/
CBDL.Event = CBDL.Event || function(event_types, context) {
context = (typeof context !== 'undefined' ? context : window);
this.event_pool = new CBDL.Event.EventPool;
this.callbacks = [];
for (event_type in event_types) {
CBDL.addEvent(context, event_types[event_type], (function(scope) {
return function(event) {
// IE is so broken. After pushing an event to
// an array, and even after checking if the
// event is still a proper event(check its
// .type property) after you PUSH it,
// once you attempt to retrieve it, it somehow
// reverts to a basic MSEventObj object. WTF
// AKA:
// event_list.push(event);
// console.log(event_list[0].type);
// ^-- returns "keydown"
// THEN, elsewhere:
// console.log(event_list[0].type);
// ^-- ERROR, NO SUCH MEMBER
// This problem is bypassed if you first create
// a new Object, copy the event's properties
// to it, then push that new Object to the
// array. It can retrieve the data fine
// afterwards. This must mean that IE destroys
// the Event object, even if you add it to an
// array. :(
// TODO: make an IE-only CBDL.Event.
var _event_ = new Object();
for (property in event) {
_event_[property] = event[property];
}
scope.handleEvent(_event_);
}
})(this));
}
};
CBDL.Event.prototype.addEvent = function(event_types, context) {
context = (typeof context !== 'undefined' ? context : window);
for (event_type in event_types) {
CBDL.addEvent(context, event_types[event_type], (function(scope) {
return function(event) {
// IE is so broken. After pushing an event to
// an array, and even after checking if the
// event is still a proper event(check its
// .type property) after you PUSH it,
// once you attempt to retrieve it, it somehow
// reverts to a basic MSEventObj object. WTF
// AKA:
// event_list.push(event);
// console.log(event_list[0].type);
// ^-- returns "keydown"
// THEN, elsewhere:
// console.log(event_list[0].type);
// ^-- ERROR, NO SUCH MEMBER
// This problem is bypassed if you first create
// a new Object, copy the event's properties
// to it, then push that new Object to the
// array. It can retrieve the data fine
// afterwards. This must mean that IE destroys
// the Event object, even if you add it to an
// array. :(
// TODO: make an IE-only CBDL.Event.
var _event_ = new Object();
for (property in event) {
_event_[property] = event[property];
}
scope.handleEvent(_event_);
}
})(this));
}
};
CBDL.Event.prototype.handleEvent = function(event) {
this.event_pool.push(event);
// TODO: add a standard JS "event" event here for an Event-based loop -- or just use a callback function for the same purpose
if (this.callbacks.length > 0) {
for (callback in this.callbacks) {
this.callbacks[callback].callback.call(this.callbacks[callback].scope, event);
}
this.event_pool.pop(event);
}
};
CBDL.Event.prototype.pollEvent = function() {
if (this.event_pool.isEmpty()) {
return false;
} else {
return this.event_pool.getNext();
}
};
// TODO: move this to CBDL.Graphics - it doesn't belong here.
CBDL.Event.prototype.getMouse = function(event, display) {
return ( {x: (event.clientX-display.element.offsetLeft), y: (event.clientY-display.element.offsetTop)} );
};
CBDL.Event.prototype.addCallback = function(scope, callback) {
this.callbacks.push({scope: scope, callback: callback});
return 0;
};
CBDL.Event.prototype.remCallback = function(callback) {
for (callbacks in this.callbacks) {
if (callbacks.callback == callback) {
callbacks = null;
}
}
};
CBDL.Event.EventPool = CBDL.Event.EventPool || function() {};
CBDL.Event.EventPool.prototype.events = [];
CBDL.Event.EventPool.prototype.isEmpty = function() {
if (this.events.length <= 0) {
return true;
}
return false;
};
CBDL.Event.EventPool.prototype.getNext = function() {
if (this.events.length > 0) {
return this.events.shift();
}
return false;
};
CBDL.Event.EventPool.prototype.push = function(event) {
this.events.push(event);
};
CBDL.Event.EventPool.prototype.pop = function(event) {
this.events.pop(event);
};
// TODO: create a CBDL.Loader object that allows for fancier file loading with
// progress, current states, etc.
/*
========================================
CBDL.loadFile(file_name, callback_function)
This function creates a hidden 'iframe' tag, sets its 'src' property to
file_name, and adds a 'load' event which calls callback_function with the
innerHTML of the iframe's document.body. If the iframe also has a 'pre' tag
within document.body, as occurs default within FF, the innerHTML property of
the first 'pre' tag is provided to the callback.
========================================
*/
CBDL.loadFile = function(file, callback) {
var iframe = document.createElement('iframe');
iframe.content = "application/json;charset=UTF-8";
iframe.id = file;
iframe.style.display = 'none';
document.body.appendChild(iframe);
CBDL.addEvent(iframe, 'load', (function(iframe, callback) {
return function() {
var doc = (iframe.contentDocument || iframe.contentWindow.document || window[iframe.id].document);
var pre = doc.body.getElementsByTagName("pre");
if (typeof pre[0] === 'undefined') {
callback(doc.body.innerHTML);
} else {
callback(pre[0].innerHTML);
}
// remove self w/ "delay" to prevent resource cancelled msg in Safari
setTimeout((function(iframe) {
return function() {
document.body.removeChild(iframe);
}
})(iframe), 0);
}
})(iframe, callback));
iframe.src = file;
};
/*
========================================
CBDL.requestFile(file_name, callback_function)
This function creates an XMLHttpRequest/Microsoft.XMLHTTP request for the
provided 'file_name', calling 'callback_function' with the request's
responseText property upon a 200 status load. CBDL.requestFile and
CBDL.loadFile can be used interchangeably, however requestFile requires the
App to be loaded from a web server, whilst loadFile can work on an App without
a webserver, so long as iframes are supported.
========================================
*/
CBDL.requestFile = function(file, callback) {
if (!/*@cc_on!@*/false) {
var request = new XMLHttpRequest();
} else {
var request = new ActiveXObject("Microsoft.XMLHTTP");
}
CBDL.addEvent(request, 'readystatechange', (function(scope, callback) {
return function() {
if (scope.readyState == 4) { // done loading
if (scope.status == 200) {
callback(scope.responseText);
}
}
}
})(request, callback));
request.open('GET', file, true);
request.send(null);
};
CBDL.Window = function(){};
CBDL.Window.setTitle = function(string) { document.title = string; }
CBDL.Console = function(name) {
this.name = name;
this.logs = [];
this.is_logging = true;
this.is_printing = true;
};
CBDL.Console.prototype.stopLogging = function() {
this.is_logging = false;
};
CBDL.Console.prototype.startLogging = function() {
this.is_logging = true;
};
CBDL.Console.prototype.stopPrinting = function() {
this.is_printing = false;
};
CBDL.Console.prototype.startPrinting = function() {
this.is_printing = true;
};
CBDL.Console.prototype.log = function(string) {
var time = new Date().toJSON();
if (this.is_logging) {
var new_log = { time: time, string: string };
this.logs.push(new_log);
}
if(this.is_printing) {
this.print(this.name+":"+time + ": " + string);
}
};
CBDL.Console.prototype.print = (typeof console.log !== 'undefined' ?
function(string) {
console.log(string);
}
:
function(string) {
}
);
// CROSS-BROWSER GLUE
CBDL.addEvent = (typeof window.attachEvent !== 'undefined' ?
// TODO: don't use tagName, find out the base Element type or array of types and use those
function(target, event_type, callback, bubble) {
var bubble = (typeof bubble === 'undefined' ? true : bubble);
if (target.attachEvent == undefined) { // not an Object with built-in Event handling, add a bogus element for events
target.__event = (target.__event ? target.__event : document.createElement("div"));
target.__event.parent = target;
target.__event.attachEvent('on'+event_type, callback);
} else {
target.attachEvent('on'+event_type, callback);
}
}
:
function(target, event_type, callback, bubble) {
var bubble = (typeof bubble === 'undefined' ? true : bubble);
if (target.addEventListener == undefined) { // not an Object with built-in Event handling, add a bogus element for events
target.__event = (target.__event ? target.__event : document.createElement("div"));
target.__event.parent = target;
target.__event.addEventListener(event_type, callback, bubble);
} else {
target.addEventListener(event_type, callback, bubble);
}
}
);
CBDL.fireEvent = function(target, event_type, obj) {
// TODO: we really want to return target as the event target here, rather than target.__event. This could be solved in a few ways -- 1. copy over all necessary methods/properties to the target Event when a custom event is added via CBDL.addEvent, thereby removing the need for target.__event. 2. Figure out a way to properly dispatchEvent from target.__event with target being passed in the end.
var call_target = (target.__event ? target.__event : target);
if (document.createEventObject) { // IE
var evt = document.createEventObject();
// NOTE: we do a shallow copy of obj to evt - should this be done differently? :S
for (attr in obj) {
evt[attr] = obj[attr];
}
return call_target.fireEvent('on'+event_type, evt);
} else { // Godly Browsers that are not Horrendous
var evt = document.createEvent("HTMLEvents");
for (attr in obj) {
evt[attr] = obj[attr];
}
evt.initEvent(event_type, true, true);
return !call_target.dispatchEvent(evt);
//return !(call_target.dispatchEvent.call(target, evt));
//return !(call_target.dispatchEvent(evt)).call(target, evt);
/*return !(function(evt) {
this.dispatchEvent(evt);
}).call(target);*/
}
};
CBDL.test_div = document.createElement('div');
if ('WebkitTransform' in CBDL.test_div.style) {
CBDL.setElementTransform = function(element, transform) {
element.style.webkitTransform = transform;
};
CBDL.clearElementTransform = function(element) {
element.style.webkitTransform = '';
}
} else if ('MozTransform' in CBDL.test_div.style) {
CBDL.setElementTransform = function(element, transform) {
element.style.MozTransform = transform;
};
CBDL.clearElementTransform = function(element) {
element.style.MozTransform = '';
}
} else if ('msTransform' in CBDL.test_div.style) {
CBDL.setElementTransform = function(element, transform) {
element.style.msTransform = transform;
};
CBDL.clearElementTransform = function(element) {
element.style.msTransform = '';
}
} else if ('OTransform' in CBDL.test_div.style) {
CBDL.setElementTransform = function(element, transform) {
element.style.OTransform = transform;
};
CBDL.clearElementTransform = function(element) {
element.style.OTransform = '';
}
} else if ('transform' in CBDL.test_div.style) {
CBDL.setElementTransform = function(element, transform) {
element.style.transform = transform;
};
CBDL.clearElementTransform = function(element) {
element.style.transform = '';
}
}