/*
* Common tools for JavaScript to support multiple browsers and languages.
*
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
var js = {};
/** =======================================================================
* Logging API.
*
* Allows for sending debug info to a console window. Potentially,
* one for each of the different modes, along with a rendering hook for it.
* This way, some pages could define a particular section on their page for
* reporting errors.
*/
js.log = {};
/**
* public static void inlineConsole()
*/
js.log.inlineConsole = function() {
document.writeln(
"<div id='js-console-outer' class='js-console-outer'>" +
js.messages.console_title +
" <button class='js-console-button' onClick=" +
"'document.getElementById(\"js-console\").innerHTML = \"\";'>" +
js.messages.console_button +
"</button>" +
"<div id='js-console' class='js-console'></div>");
js.log.setDisplay(document.getElementById('js-console'));
js.log.DEBUG.format = '"<br /><span class=\'js-console-debug\'>[debug] {?1}</span>"';
js.log.WARN.format = '"<br /><span class=\'js-console-warn\'>Warning: {?1}</span>"';
js.log.ERROR.format = '"<br /><span class=\'js-console-error\'>Error: {?1}</span>"';
js.log.FAILURE.format = '"<br /><span class=\'js-console-failure\'>Failure: {?1}</span>"';
js.log.EXCEPT.format = '"<br /><span class=\'js-console-except\'>Exception: {?1}\nException {?2}:\n{?3}</span>"';
};
/**
* public static void setDisplay(domNode)
*/
js.log.setDisplay = function(display) {
for (var p in js.log) {
var x = js.log[p];
if (x['parseArgsFunc']) {
x['display'] = display;
}
}
};
/**
* public class js.log.LevelType(String name, boolean enabled = false,
* DomNode display = null, EvalString format = '"{name}: {1}"',
* Function parseArgFunc = js.log.__parseArgs_default):
*
* Log level type that knows how to format & display the log messages.
*
* @param name
* @param enabled
* @param display
* @param format
* @param parseArgFunc
*
* @field String name:
* @field boolean enabled:
* @field DomNode display:
* @field EvalString format:
* @field Function parseArgsFunc:
*
*/
js.log.LevelType = function(name, enabled, display, format, parseArgsFunc) {
if (enabled == null) {
enabled = true;
}
if (format == null) {
format = '"' + name + ': {1}"';
}
if (parseArgsFunc == null) {
parseArgsFunc = js.log.__parseArgs_default;
}
this.name = name;
this.enabled = true;
this.display = display;
this.format = format;
this.parseArgsFunc = parseArgsFunc;
};
/**
* public void js.log.LevelType.log(...)
*
* Logs a message to the level type. The number of arguments must match the
* type's format expectations.
*/
js.log.LevelType.prototype.log = function() {
if (this.enabled) {
// eval the format so that post-setup references can be altered
var fmt = '';
try {
fmt = eval(this.format);
} catch (e) {
alert(js.msg(js.messages.except,
js.msg(js.messages.bad_eval, this.format), e.name, e.message));
}
var args = new Array(fmt);
this.parseArgsFunc(args, arguments);
var msg = js.msg2(args);
if (this.display) {
this.display.innerHTML += msg;
} else {
alert(msg);
}
}
};
js.log.__parseArgs_default = function(list, args) {
for (var i = 0; i < args.length; i++) {
list.push(args[i]);
}
};
js.log.parseArgs_except = function(list, args) {
if (args.length > 0) {
list.push(args[0]);
if (args.length > 1) {
var e = args[1];
if (e) {
list.push(e.name);
list.push(e.message);
}
}
}
};
/* List of log levels, and how they are displayed. These can be overridden
* by the implementation, or altered by the implementation.
*/
js.log.DEBUG = new js.log.LevelType('debug', false, null, null);
js.log.WARN = new js.log.LevelType('warn', false, null, 'js.messages.error');
js.log.ERROR = new js.log.LevelType('err', true, null, 'js.messages.error');
js.log.FAILURE = new js.log.LevelType('fail', true, null, 'js.messages.failure');
js.log.EXCEPT = new js.log.LevelType('except', true, null, 'js.messages.except',
js.log.parseArgs_except);
/**
* public void js.debug(String text)
*/
js.debug = function(text) {
js.log.DEBUG.log(text);
};
/**
* public void js.warn(String text)
*/
js.warn = function(text) {
js.log.WARN.log(text);
};
/**
* public void js.error(String text)
*/
js.error = function(text) {
js.log.ERROR.log(text);
};
/**
* public void js.failure(String text)
*/
js.failure = function(text) {
js.log.FAILURE.log(text);
};
/**
* public void js.except(String text, Exception ex)
*/
js.except = function(text, ex) {
js.log.EXCEPT.log(text, ex);
};
/** ========================================================================
* Message Formatting
*
* Allows for message objects and localization.
*/
/**
* String msg(String text, ...)
*
* Generates a computed message using {1}, {2}, ... style replacements.
* Used for generating language-specific messages. Argument indicies start
* at 1.
*/
js.msg = function() {
return js.msg2(arguments);
};
js.msg2 = function(args) {
if (args.length <= 0) {
return "";
}
var subs = args[0];
var a2 = new Array();
for (var i = 1; i < args.length; i++) {
a2[i-1] = args[i];
}
return js.__message.parse(subs, args, js.__message.parseIndex);
};
js.parseMsg = function(subs, context) {
if (context == null) {
context = window;
}
return js.__message.parse(subs, context, js.__message.parseContext);
};
js.__message = {};
js.__message.parse = function(subs, args, parseFunc) {
var ret = "";
var len = subs.length;
var start = 0;
while (start < len) {
var pos = subs.indexOf('{', start);
if (pos >= 0) {
var npos = subs.indexOf('}', pos+1);
if (npos >= 0) {
var replace = subs.substring(pos+1, npos);
var txt = '';
if (replace.length > 0 && replace.charAt(0) == '?') {
txt = js.escapeHtml(parseFunc(replace.substring(1), args));
} else {
txt = parseFunc(replace, args);
}
ret = ret + subs.substring(start, pos) + txt;
start = npos + 1;
} else {
ret = ret + subs.substring(start, pos + 1);
start = pos + 1;
}
} else {
// end of string
ret = ret + subs.substring(start, len);
start = len;
}
}
return ret;
};
js.__message.parseIndex = function(text, args) {
var index = parseInt(text);
if (index > 0 && index < args.length) {
return args[index];
} else {
return '{' + text + '}';
}
};
js.__message.parseContext = function(text, context) {
var seps = text.split('.');
var out = context;
var tc = 'context';
var txt = '';
for (var i = 0; i < seps.length; i++) {
if (out[seps[i]]) {
tc += '.' + seps[i];
out = out[seps[i]];
txt = '' + out;
} else {
txt = '{' + js.msg(js.messages.bad_eval_parse,
tc, seps[i]) + '}';
break;
}
}
return txt;
};
js.escapeHtml = function(txt) {
if (! txt) {
return 'null';
}
return txt.replace(/\&/g, '&').replace(/\</g, '<').
replace(/\>/g, '>').
replace(/\"/g, '"');
};
js.isFunction = function(a) {
return typeof a == 'function';
};
js.isArray = function(a) {
return typeof a == 'array';
};
js.isObject = function(a) {
return typeof a == 'object';
};
js.isString = function(a) {
return typeof a == 'string';
}
js.isNumber = function(a) {
return typeof a == 'number';
}
js.isInt = function(a) {
if (js.isNumber(a)) {
var f = Math.floor(a);
// epsilon checking for whether we should consider the number an int
if (Math.abs(a - f) < 0.00001) {
return true;
}
}
return false;
}
js.isFloat = function(a) {
return (js.isNumber(a) && ! js.isInt(a));
}
/*
alert('typeof 1.2 = ' + (typeof 1.2) + '\n' +
'typeof 1 = ' + (typeof 1) + '\n' +
'typeof "abc" = ' + (typeof "abc") + '\n' +
'isInt(1.0) = ' + (js.isInt(1.0)) + '\n' +
'isInt(1.2) = ' + (js.isInt(1.2)) + '\n' +
'isFloat(1.0) = ' + (js.isFloat(1.0)) + '\n' +
'isFloat(1.2) = ' + (js.isFloat(1.2)));
*/
/* ========================================================================
* Class / inheritence support
*/
/**
* public static Function js.Class(Object prototypedef, Function classXtor1, ...)
*
* @param prototypedef an Object whose member fields make up the base prototype
* values of the new class. This overrides any superclass prototype
* declarations. If this is a function, then it's assumed to be a
* classXtor parameter.
* @param classXtor1 a superclass, allowing for the "simulation" of multiple
* inheritence. The first listed class will be used as the base prototype.
* Note that for non-primary prototypes, the '__X__' style methods will not
* be inherited, and changes to the non-primary prototypes will not affect
* this constructed class.
* @return a new class constructor Function with the correct prototype chain
* setup.
*/
js.Class = function() {
var xtor = new Function('', 'this.__runInit(arguments);');
xtor.prototype.__runInit = js.Class.__runInit;
if (arguments.length >= 0) {
var setup = null;
var i = 0;
if (! js.isFunction(arguments[i])) {
setup = arguments[i++];
}
for (; i < arguments.length; i++) {
var proto = arguments[i];
if (js.isFunction(proto)) {
xtor.prototype = new proto();
break;
}
}
for (; i < arguments.length; i++) {
var proto = arguments[i];
if (js.isFunction(proto)) {
for (var x in proto.prototype) {
if (x.substring(0,2) != '__' && x.substring(x.length-2) != '__') {
xtor.prototype[x] = proto.prototype[x];
}
}
}
}
if (setup) {
for (var x in setup) {
xtor.prototype[x] = setup[x];
}
}
}
return xtor;
};
js.Class.__runInit = function(args) {
if (js.isFunction(this.__init__)) {
this.__init__.apply(this, args);
}
}
/* ========================================================================
* Browser compatibility functions. For internal use only.
*/
js.__compat = {};
js.__compat.ie4_getElementById = function(elname) {
return js.__compat.generic_getElementById('all', elname);
};
js.__compat.nn4_getElementById = function(elname) {
return js.__compat.generic_getElementById('layers', elname);
};
js.__compat.generic_getElementById = function(key, elname) {
var expr = /^\\w[\\w\\d]*$/;
if (!expr.test(elname)) {
return null;
} else {
var ret = document[key][elname];
if (ret) {
// if it returns an array, we only need the top element
if (ret[0]) {
return ret[0];
} else {
return ret;
}
}
}
// fall-through
return null;
};
js.__compat.Array_push = function(arg) {
this[this.length] = arg;
};
js.__compat.Array_pop = function() {
if (this.length <= 0) {
return null;
} else {
return this[this.length--];
}
};
js.__compat.Array_shift = function() {
if (this.length <= 0) {
return null;
} else {
var ret = this[0];
for (var i = 1; i < this.length; i++) {
this[i-1] = this[i];
}
this.length--;
}
};
js.__compat.Array_unshift = function(a) {
for (var i = this.length; i >= 1; i--) {
this[i] = this[i-1];
}
};
/**
* Initialization of the JavaScript environment, to ensure a viable platform
* for operation.
*/
js.__compat.check = function() {
// ie 4 DOM compatibility check
if (document.all && !document.getElementById) {
document.getElementById = js.__compat.ie4_getElementById;
}
else
// netscape 4 DOM compatibility check
if (document.layers && !document.getElementById) {
document.getElementById = js.__compat.nn4_getElementById;
}
// old API compatibility
// Array
if (! js.isFunction(Array.prototype['push'])) {
Array.prototype.push = js.__compat.Array_push;
}
if (! js.isFunction(Array.prototype['pop'])) {
Array.prototype.push = js.__compat.Array_pop;
}
if (! js.isFunction(Array.prototype['shift'])) {
Array.prototype.shift = js.__compat.Array_shift;
}
if (! js.isFunction(Array.prototype['unshift'])) {
Array.prototype.unshift = js.__compat.Array_unshift;
}
};
js.__compat.check();
// don't need the compat anymore, so remove it
delete js.__compat;
/* =========================================================================
* DEFAULT MESSAGE TEXT
*
* Defaults to English.
*/
js.messages = {};
js.messages.language_code = "en";
js.messages.language = "US English";
js.messages.error = "Error: {1}";
js.messages.failure = "Failure: {1}";
js.messages.except = "{1}\nException {2}:\n{3}";
js.messages.bad_eval = "error in eval ({1})";
js.messages.bad_eval_parse = "error in expression ({1}.{2})";
js.messages.console_title = 'Console';
js.messages.console_button = 'Clear Console';