diff options
Diffstat (limited to 'itjs')
-rw-r--r-- | itjs/boot.js | 136 | ||||
-rw-r--r-- | itjs/error.gif | bin | 0 -> 43 bytes | |||
-rw-r--r-- | itjs/it.js | 165 | ||||
-rw-r--r-- | itjs/loader.js | 207 | ||||
-rw-r--r-- | itjs/state.html | 29 | ||||
-rw-r--r-- | itjs/state.js | 114 | ||||
-rw-r--r-- | itjs/timer.js | 64 |
7 files changed, 715 insertions, 0 deletions
diff --git a/itjs/boot.js b/itjs/boot.js new file mode 100644 index 0000000..8ca539f --- /dev/null +++ b/itjs/boot.js @@ -0,0 +1,136 @@ +// $Id$ + +var it_boot_status = ""; +var it_panictimer = window.setTimeout("it_panic(it_boot_status)", 31337); +var it_catcherrstart = new Date().getTime(); + +function it_catcherr(msg, url, line) +{ + var stacktrace = ""; + + for (var c = it_catcherr.caller; c != null; c = c.caller) + { + var funcname = c.toString().match(/function (\w*)/)[1]; + stacktrace += (funcname ? funcname : "anon") + "/"; + + if (c.caller == c) // Break simple recursion + { + stacktrace += "*"; + break; + } + } + + it_boot_report(msg, url, line, stacktrace); + + return !window.env || !!window.env.is_live_server; // No env or live server -> suppress error +} + +window.onerror = it_catcherr; + +function it_boot_addparam(url, param) +{ + return url + (url.match(/\?/) ? "&" : "?") + param; +} + +function it_panic(msg) +{ + window.setTimeout("document.location.href = it_boot_addparam(document.location.href, 'static=" + msg + "')", 500); + return it_catcherr('panic ' + msg, '-', -1); +} + + +function it_boot_report(msg, file, line, error) +{ + window.clearTimeout(window.it_domtimer); + window.clearTimeout(window.it_panictimer); + + new Image().src = "/itjs/error.gif/" + escape(msg) + "|" + escape(file) + "|" + line + "|" + (new Date().getTime() - it_catcherrstart) + "|" + escape(error); +} + +function it_boot(file, isretry) +{ + window.it_domtimer = null; + var doc = document; + var dom = doc && (dom = document.getElementById('it_boot_dom')); // HTML has been rendered + var view = dom && doc.defaultView; // We can check if stylesheet is active + var style = view && view.getComputedStyle && view.getComputedStyle(dom, ''); + var css = style && style.getPropertyValue && (style.getPropertyValue("visibility") == "hidden"); // CSS active (inline style on tag) + var stylesheet = style && style.getPropertyValue && (style.getPropertyValue("display") == "none"); // External stylesheet loaded + + if (!(doc || !(it_boot_status = "doc")) || !(dom || !(it_boot_status = "dom")) || (style && (style.length > 0) && !(stylesheet || !(it_boot_status = "stylesheet")))) + { + window.it_domtimer = window.setTimeout("it_boot('" + file + "')" , 42); + + if (style && !css) + it_panic("css"); + + return; + } + + try + { + var loader = new XMLHttpRequest(); + } + catch (e) + { + var classnames = [ 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ]; + + for (var i in classnames) + try { loader = new ActiveXObject(classnames[i]); break; } catch (e) {} + } + + if (loader) + { + loader.open("GET", it_boot_addparam(file, "boot=1" + (isretry ? "&retry=1" : ""))); + loader.onreadystatechange = function() + { + var error = ""; + + if (loader.readyState == 4) + { + if (loader.status < 400) // Opera gives back 304 if from cache + { + try + { + var data = eval("(" + loader.responseText + ")"); + + if (data.code.length == data.len) + { + var code = "try {" + data.code + ";window.it_boot_init()} catch (e) { it_boot_report('Load error', '-', -1, e); }"; // Wrapped in try/catch as Konqueror does not support window.onerror + isretry = true; // No further retry after this point + + if (window.execScript) + window.execScript(code, "javascript"); // IE work-around to get script executed in global scope + else + window.setTimeout(code, 0); // Standard compliant version + } + else + error = "Length mismatch " + data.code.length + " != " + data.len; + } + catch (e) + { + for (i in e) + error += i + "=" + e[i] + ";"; + } + } + else + error = loader.statusText; + + if (error) + { + if (isretry) + it_boot_report('Load error on retry', file, -1, error); + else + it_boot(file, true); + } + } + } + loader.send(null); + } + else + { + var tag = document.createElement("script"); + tag.src = it_boot_addparam(file, 'init=1'); + dom.appendChild(tag); + } +} diff --git a/itjs/error.gif b/itjs/error.gif Binary files differnew file mode 100644 index 0000000..5bfd67a --- /dev/null +++ b/itjs/error.gif diff --git a/itjs/it.js b/itjs/it.js new file mode 100644 index 0000000..1a792f3 --- /dev/null +++ b/itjs/it.js @@ -0,0 +1,165 @@ +// $Id$ + +/** + * Clear contents of element 'jsdebug' + */ +function CED(txt) +{ + var element = document.getElementById('jsdebug'); + + if (element) + element.innerHTML = txt ? txt : ""; +} + +/** + * Add debugging output to element 'jsdebug' + */ +function ED() +{ + var element = document.getElementById('jsdebug'); + + if (element) + { + var text = ""; + + for (var i = 0; i < arguments.length; i++) + { + var variable = arguments[i]; + + if (typeof variable == "string") + variable = variable.replace(/&/g, '&').replace(/</g, '<'); + + text += (typeof variable) + " " + variable; + + if (typeof variable == "object") + { + text += ":"; + + for (field in variable) + { + text += field + "="; + + try { text += typeof variable[field] == 'function' ? 'function' : variable[field]; } + catch (e) { text += "*" + e + "*"; } + + text += "\n"; + } + text += "\n"; + } + + text += "\n"; + } + + element.innerHTML += '<pre style="background-color:#FEE; margin:0">' + text + '</pre>'; + } +} + +/** + * Quote HTML special chars + * @return Text string with & " < > htmlentities-encoded + */ +function Q(value) +{ + return value.toString().replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); +} + +/** + * String class: Replaces variables of the form {var} with values from given array + * @param values Associative array containing values to fill in (optional) + * @return Text string with variables replaced by their values + */ +String.prototype.T = function(values) +{ + var result = this; + + for (key in values) + result = result.replace(new RegExp("{" + key + "}", "g"), values[key]); + + return result; +} + +/** + * Insert an event handler on top of chain + * @param p.element Element to handle event for + * @param p.event Name of event:'focus', 'click', ... (without 'on') + * @param p.object Object that contains handler method + * @param p.method Method of p.object to call on p.event + */ +function it_event(p) +{ + var oldhandler = p.element["on" + p.event]; + + p.element["on" + p.event] = function(ev) + { + var pp = arguments.callee.p ? arguments.callee.p : p; + var oo = arguments.callee.oldhandler ? arguments.callee.oldhandler : oldhandler; + + var result = pp.object[pp.method](ev ? ev : window.event, pp); + + if (result && oo) + result = oo(ev); + + return result; + } + p.element["on" + p.event].p = p; + p.element["on" + p.event].oldhandler = oldhandler; +} + +/* Get object pixel position. Based on quirksmode.org's code */ +function it_get_obj_x(obj) +{ + var curleft = 0; + if (obj.offsetParent) + while (obj) + { + curleft += obj.offsetLeft; + obj = obj.offsetParent; + } + else if (obj.x) + curleft += obj.x; + return curleft; +} + +function it_get_obj_y(obj) +{ + var curtop = 0; + if (obj.offsetParent) + while (obj) + { + curtop += obj.offsetTop; + obj = obj.offsetParent; + } + else if (obj.y) + curtop += obj.y; + return curtop; +} + +/* Get an iframe's content document in a compatible way */ +function it_get_iframe_document(iframe) +{ + return iframe.contentWindow ? iframe.contentWindow.document : iframe.contentDocument; +} + +/** + * Clone src value and return dst. If dst is given then src attributes are + * copied into it, otherwise a new object is created. Also works with scalar + * values. Performs a deep copy of objects + * @param src Source object/value to clone + * @param dst Destination object to copy attributes from src to (optional) + * @return dst holding copy of src + */ +function it_clone(src, dst) +{ + if (typeof src == "object") + { + if (!dst) + dst = {}; + + for (var i in src) + dst[i] = it_clone(src[i]); + } + else + dst = src; + + return dst; +} diff --git a/itjs/loader.js b/itjs/loader.js new file mode 100644 index 0000000..19f5f9c --- /dev/null +++ b/itjs/loader.js @@ -0,0 +1,207 @@ +/** + * Create loader to request data from server + * + * @param handler Object providing clear()/render() function when data arrives + */ +function it_loader(handler) +{ + /* Clear cache etc. if completely new data */ + this.loader = null; + this.handler = handler; + this.name = "it_loader"; + this.callid = 0; + this.clear(); +} + +/* Methods */ +it_loader.prototype = +{ + +/* Clear cache and initialize handler */ +clear: function() +{ + /* Clear cache etc. if completely new data */ + this.entry = new Array(); + this.start = this.end = 0; + this.attr = { num: 0, loadtime: 0 }; + + if (this.handler.clear) + this.handler.clear(); +}, + +load: function(baseurl, pos, num, query_volatile, retry) +{ + /* Convert to int */ + pos -= 0; + num -= 0; + + if (isNaN(retry)) + retry = 0; + + if (baseurl != this.baseurl) + { + this.clear(); + this.baseurl = baseurl; + this.start = this.end = pos; + } + + this.pos = pos; + this.num = num; + this.query_volatile = query_volatile; + + if (this.loader) + this.stop(); + + while ((num > 0) && this.entry[pos]) + { + pos++; + num--; + } + + if (this.attr.eof) + num = Math.min(num, this.end - pos); + + if (num > 0) + { + if (retry) + baseurl += "&retry=" + retry; + + try + { + this.loader = new XMLHttpRequest(); + } + catch (e) + { + var classnames = [ 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ]; + + for (var i in classnames) + { + try + { + this.loader = new ActiveXObject(classnames[i]); + break; + } + catch (e) {} + } + } + + try + { + this.loader.open("GET", baseurl + "&pos=" + pos + "&num=" + num + (query_volatile ? query_volatile : "")); + var me = this; + this.loader.onreadystatechange = function() { me.readyStateChanged(); } + var workingxmlhttp = this.loader.onreadystatechange; + + if (!workingxmlhttp) /* Old Konqueror */ + this.loader = null; + } + catch (e) { } + + if (this.loader) + { + this.starttime = new Date().getTime(); + this.retry = retry; + this.loader.send(null); + } + else + { + + if (!this.iframe) + { + this.iframe = document.createElement("iframe"); + this.iframe.frameBorder = 0; + this.iframe.style.width = this.iframe.style.height = 1; + document.body.appendChild(this.iframe); + } + + this.loader = { starttime: new Date().getTime(), retry: retry }; + var loaderinstance = this.name; + window[loaderinstance] = this; + this.iframe.src = baseurl + "&pos=" + pos + "&num=" + num + '&itjs_call=parent.' + loaderinstance + '.dataReady&itjs_callid=' + ++this.callid; + } + } + else + this.handler.render(this); +}, + +readyStateChanged: function() +{ + var loader = this.loader; // Avoid race conditions + + if (loader && (loader.readyState == 4)) + { + var data = null; + + try + { + if (loader.responseText != "") + data = eval("("+ loader.responseText + ")"); + } + catch (e) + { + var retry = this.retry + 1; + + if (retry < 10) + it_timer({ object: this, method: "retryload", timeout: Math.pow(5, Math.min(retry, 5)), baseurl: this.baseurl, pos: this.pos, num: this.num, query_volatile: this.query_volatile, retry: retry }); + else + ED(e, loader.responseText); + } + + if (data) + this.dataReady(data, this.callid); + } +}, + +retryload: function(p) +{ + this.load(p.baseurl, p.pos, p.num, p.query_volatile, p.retry); +}, + +dataReady: function(data, callid) +{ + var fixkonqueror33gcbug = this.loader; + var loadtime = new Date().getTime() - this.starttime; + + this.loader = null; + + if ((typeof data == "object") && (this.callid == callid)) + { + this.attr = { loadtime: loadtime }; + + for (var key in data) + { + var value = data[key]; + var id = key - 0; + + if (!isNaN(id)) + { + this.start = Math.min(this.start, id); + this.end = Math.max(this.end, id + 1); + this.entry[id] = data[key]; + } + else + this.attr[key] = data[key]; + } + + if (this.attr.eof) + this.attr.num = this.end; /* Fix bogus # of result value */ + + this.handler.render(this); + + if (!this.attr.eof && (this.end < this.pos + this.num)) + this.load(this.baseurl, this.end, this.pos + this.num - this.end); + } +}, + +stop: function() +{ + try + { + this.loader.abort(); + } + catch (e) { } + + this.loader = null; +}/* NO COMMA */ + +} diff --git a/itjs/state.html b/itjs/state.html new file mode 100644 index 0000000..08ab639 --- /dev/null +++ b/itjs/state.html @@ -0,0 +1,29 @@ +<?php header("Cache-Control: max-age=3600"); +return <<<EOF +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html> +<head> +<title></title> +<script type="text/javascript"> +function state_onload() +{ + if (parent.it_state) + { + if (parent.it_state.ready) + parent.setTimeout(parent.it_state_restore_history, 1); + else + window.setTimeout(state_onload, 90); + } + window.onbeforeunload = state_onbeforeunload; +} +function state_onbeforeunload() +{ + if (parent.it_state) + parent.it_state.store_state(); +} +</script> +</head> +<body onload="state_onload()"><form action=""><input type="hidden" id="state" value=""></form></body> +</html> +EOF +?> diff --git a/itjs/state.js b/itjs/state.js new file mode 100644 index 0000000..04f8acf --- /dev/null +++ b/itjs/state.js @@ -0,0 +1,114 @@ +/** + * Generic state object (singleton), for supporting Ajax browser history + * + * $Id$ + * + */ +var it_state = +{ +it_iframe: null, +it_history_field: null, +it_store_handlers: [], +it_restore_handlers: [], + +/** + * Register a handler that gets called before creating a history entry + * @param p.object Object that contains handler method + * @param p.method Method of p.object to call + */ +register_store_handler: function(p) +{ + this.it_store_handlers[this.it_store_handlers.length] = p; +}, + + +/** + * Register a handler that gets called after restoring a history entry + * @param p.object Object that contains handler method + * @param p.method Method of p.object to call + * @param p.initial optional flag: if true, handler is called with initial empty state + */ +register_restore_handler: function(p) +{ + this.it_restore_handlers[this.it_restore_handlers.length] = p; + + // If we already have state data, call registered handler immediately. Yup this _is_ needed! + if (this.it_history_field && this.it_history_field.value) + p.object[p.method](); +}, + + +/** + * Create a new history entry, saving all values of it_state (asynchronously) + * Users must call this without parameter! + */ +new_history_entry: function(p) +{ + // ED('it_state::new_history_entry()'); + + if (!this.it_iframe && !(this.it_iframe = document.getElementById('it_state'))) + ED('it_state::new_history_entry(): it_state object not found!'); + + this.it_iframe.src = "/itjs/state.html?t="+now(); + this.it_history_field = null; +}, + + +/** + * Restore state from history, called from iframe's onload handler but in main window's context + */ +restore_history: function() +{ + if (!this.it_iframe && !(this.it_iframe = document.getElementById('it_state'))) + ED('it_state::restore_history(): it_state object not found!'); + + var idoc = it_get_iframe_document(this.it_iframe); + this.it_history_field = idoc ? idoc.getElementById('state') : {}; // Work-around IE5 not returning iframe document + + if (this.it_history_field.value) + { + var res = eval('({' + this.it_history_field.value + '})'); + // ED('it_state::restore_history(): restoring these settings:', res); + for (var key in res) + this[key] = res[key]; + } + + for (var i in this.it_restore_handlers) + { + if (this.it_history_field.value || (this.it_restore_handlers[i].initial && (!idoc || !it_get_iframe_document(this.it_iframe).location.href.match(/t=/)))) + this.it_restore_handlers[i].object[this.it_restore_handlers[i].method](); + } +}, + + +/** + * Call all store handlers and store state in it_history_field + */ +store_state: function() +{ + if (!this.it_iframe && !(this.it_iframe = document.getElementById('it_state'))) + ED('it_state::store_state(): it_state object not found!'); + + var idoc = it_get_iframe_document(this.it_iframe); + this.it_history_field = idoc ? idoc.getElementById('state') : {}; // Work-around IE5 not returning iframe document + + for (var i in this.it_store_handlers) + this.it_store_handlers[i].object[this.it_store_handlers[i].method](); + + var ser = []; + for (var key in this) + { + var value = this[key], type = typeof(value); + if (!key.match(/^it_/) && type.match(/boolean|number|string/)) + ser[ser.length] = key + ':' + ((type == 'string') ? "'" + value.replace(/([\\'])/g, '\\\1') + "'" : value); + } + + // ED('it_state::store_state()', ser); + this.it_history_field.value = ser.join(','); +}/* NO COMMA */ +} + +function it_state_restore_history() +{ + it_state.restore_history(); +} diff --git a/itjs/timer.js b/itjs/timer.js new file mode 100644 index 0000000..25ebbec --- /dev/null +++ b/itjs/timer.js @@ -0,0 +1,64 @@ +/** + * Start new timer + * Example: timer = new it_timer({ object:this, method:"timer", timeout:100}); + * + * @param p.object Object to call method in when timer fires + * @param p.method String with method name to call in object + * @param p.timeout Timeout in milliseconds + * @param p.continuous One-shot or continuous timer, default is one-shot + * @return timer id (deprecated, use method stop() instead) + */ +function it_timer(p) +{ + this.func = p.continuous ? "Interval" : "Timeout"; + return this.timer = window["set" + this.func](function() { p.object[p.method](p) }, p.timeout); +} + +it_timer.prototype = +{ + +stop: function() +{ + if (this.timer) + { + window["clear" + this.func](this.timer); + this.timer = null; + } +}/* NO COMMA */ + +} + +/** + * Global helper function to benchmark javascript + * @parameter label Message label like "start" or "end" + * @paramter print Whether to output timerlog via ED() and clear it (optional) + */ +function it_timerlog(label, print) +{ + if (window.it_timerlog_active) + { + var end = new Date().getTime(); + + if (typeof window.it_timernow != "undefined") + { + var start = window.it_timernow; + + if (window.it_timerlogmsg != "") + window.it_timerlogmsg += ", "; + + window.it_timerlogmsg += label + ":" + (end - start); + } + else + window.it_timerlogmsg = ""; + + window.it_timernow = end; + + if (print) + { + ED("timerlog: " + window.it_timerlogmsg); + window.it_timerlogmsg = ""; + } + } +} + +it_timerlog(""); // Set start time |