CBDL/CBDL.js

730 lines
21 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.
===============================================================================
*/
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.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;
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);
};
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.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);
};
// 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' ?
function(target, event_type, callback, bubble) {
var bubble = (typeof bubble === 'undefined' ? true : bubble);
target.attachEvent('on'+event_type, callback);
}
:
function(target, event_type, callback, bubble) {
var bubble = (typeof bubble === 'undefined' ? true : bubble);
target.addEventListener(event_type, callback, bubble);
}
);
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 = '';
}
}