diff options
author | Christian Schneider | 2007-10-11 00:39:30 +0000 |
---|---|---|
committer | Christian Schneider | 2007-10-11 00:39:30 +0000 |
commit | 35fe33f7364329dacf415c950bff01b6de9ef88e (patch) | |
tree | b0e6b018b50038ca20266723c53750268f508df5 /itjs | |
parent | 1f95711ff3e9697cd85a54545ab42e5fd3611317 (diff) | |
download | itools-35fe33f7364329dacf415c950bff01b6de9ef88e.tar.gz itools-35fe33f7364329dacf415c950bff01b6de9ef88e.tar.bz2 itools-35fe33f7364329dacf415c950bff01b6de9ef88e.zip |
Populated release branch
Diffstat (limited to 'itjs')
-rw-r--r-- | itjs/boot.js | 162 | ||||
-rw-r--r-- | itjs/error.gif | bin | 0 -> 43 bytes | |||
-rw-r--r-- | itjs/it.js | 177 | ||||
-rw-r--r-- | itjs/loader.js | 275 | ||||
-rw-r--r-- | itjs/state.html | 31 | ||||
-rw-r--r-- | itjs/state.js | 120 | ||||
-rw-r--r-- | itjs/timer.js | 64 |
7 files changed, 829 insertions, 0 deletions
diff --git a/itjs/boot.js b/itjs/boot.js new file mode 100644 index 0000000..aaa0321 --- /dev/null +++ b/itjs/boot.js @@ -0,0 +1,162 @@ +// $Id$ + +var it_boot_status = "boot"; +var it_panictimer = window.setTimeout("it_panic(it_boot_status)", 31337), it_domtimer; +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; + } + } + + if (typeof it_boot.sequence != 'undefined') + stacktrace += "it_boot=" + it_boot.sequence + ";"; + if (typeof window.it_loader != 'undefined' && it_loader.sequence) + stacktrace += "it_loader=" + it_loader.sequence + ";"; + + 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(reason, msg) +{ + if (!document.location.href.match(/[?&]static=/)) // Avoid loop + window.setTimeout("document.location.href = it_boot_addparam(document.location.href, 'static=" + reason + "')", 500); + + return it_boot_report('panic ' + reason, '-', -1, (msg ? msg : '')); +} + +function it_boot_report(msg, file, line, more) +{ + 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(more); +} + +function it_boot_checkcss(style, key, value) +{ + return navigator.userAgent.match(/konqueror/i) || (style && style.getPropertyValue && (style.getPropertyValue(key) == value)); +} + +function it_boot_init() +{ + window.it_domtimer = null; + var konqueror = navigator.userAgent.match(/konqueror/i); + var doc = document; + var dom = doc && (dom = doc.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 = window.it_boot_checkcss(style, "visibility", "hidden"); // CSS active (inline style on tag) + var stylesheet = window.it_boot_checkcss(style, "display", "none"); // External stylesheet loaded + + if (!(doc || !(it_boot_status = "doc")) || !(dom || !(it_boot_status = "dom")) || (style && !(stylesheet || !(it_boot_status = "stylesheet")))) + { + window.it_domtimer = window.setTimeout("it_boot_init()" , 42); + + if (style && !css) + it_panic("css"); + + return; + } + + it_boot.sequence += "i"; + window.clearTimeout(window.it_panictimer); + window.it_boot_start(); + it_boot.sequence = ""; +} + +function it_boot(file, isretry) +{ + it_boot.file = file; + it_boot.sequence = isretry ? "r" : "b"; + + 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) + { + it_boot.sequence += "l"; + 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 + { + // check length cookie + var ln = String(loader.responseText).substr(-16).match(/\*sln:\s*([0-9]+)\*/); + if (ln && ln[1]-0 == loader.responseText.length) + { + it_boot.sequence += "e"; + var code = "try {" + loader.responseText + "} catch (e) { it_catcherr(e.message, it_boot.file, -1); }"; // Wrapped in try/catch as Konqueror does not support window.onerror + 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 = (ln ? "length mismatch: "+ln[1]+" != "+loader.responseText.length : "no length cookie"); + } + else + error = loader.statusText; + + if (error) + { + if (isretry) + it_panic('load', error); + else + it_boot(file, true); + } + } + } + loader.send(null); + } + else + { + var doc = document; + var dom = doc && (dom = doc.getElementById('it_boot_dom')); // HTML has been rendered + it_boot.sequence += "n"; + + if (window.opera || (document.all && navigator.platform.indexOf("Mac") >= 0)) + document.write('<sc'+'ript type="text/javascript" src="'+it_boot_addparam(file, 'boot=1')+'"><\/sc'+'ript>'); + else if (dom) + { + var tag = doc.createElement("script"); + tag.src = it_boot_addparam(file, 'boot=1&retry=1'); + dom.appendChild(tag); + } + else + window.it_domtimer = window.setTimeout("it_boot('" + file + "')" , 42); + } +} 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..a43ba3a --- /dev/null +++ b/itjs/it.js @@ -0,0 +1,177 @@ +// $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 typeof value == "undefined" ? "" : 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; +} + +/** + * Copy attributes from src to dst in a recursive manner. + * @param dst Destination object which gets attributes + * @param src Source object containing attributes + */ +function it_set(dst, src) +{ + if (dst) + { + for (var i in src) + { + if (typeof src[i] == 'object') + it_set(dst[i], src[i]); + else + dst[i] = src[i]; + } + } +} + +/** + * Return the current timestamp + */ +function it_now() +{ + return new Date().getTime(); +} + +/** + * Encodes arbitrary string for use in an url + * @param str string to be encoded + */ +function it_url_encode(str) +{ + var result = window.encodeURIComponent ? encodeURIComponent(str) : escape(str).replace(/\+/g, "%2B"); + + return result.replace(/%20/gi, "+").replace(/%2C/gi, ",").replace(/%28/gi, "(").replace(/%29/gi, ")"); +} diff --git a/itjs/loader.js b/itjs/loader.js new file mode 100644 index 0000000..de6ae9d --- /dev/null +++ b/itjs/loader.js @@ -0,0 +1,275 @@ +/** + * 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.instance = it_loader.instances++; + 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 }; + this.method = "GET"; + this.post_data = null; + + 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) + { + this.loader = null; + var samehost = (baseurl.indexOf('http://') < 0 || baseurl.indexOf(window.location.hostname) > 0); + + if (retry) + baseurl += "&retry=" + retry; + + // use XMLHTTP request if available + if (samehost) + { + 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(this.method, 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) { } + } + + this.starttime = new Date().getTime(); + this.retry = retry; + + if (this.loader) + { + if (this.method == "POST") + this.loader.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + + this.loader.send(this.post_data); + } + else + { + var req_url = baseurl + "&pos=" + pos + "&num=" + num + (query_volatile ? query_volatile : "") + (this.post_data ? '&' + this.post_data : "") + "&itjs_call=it_loader.__inst" + this.instance + "&itjs_callid=" + ++this.callid; + + if (samehost || (window.opera && !window.XMLHttpRequest)) // Opera 7 only works with iframes + { + this.scrpt = document.createElement("iframe"); + this.scrpt.style.width = this.scrpt.style.height = 1; + req_url += "&itjs_iframe=1"; + } + else + this.scrpt = document.createElement("script"); + + it_loader['__inst'+this.instance] = this; + this.loader = { starttime: this.starttime, retry: retry }; + try + { + if (!document.all) this.scrpt.src = req_url; + document.body.appendChild(this.scrpt); + if (document.all) this.scrpt.src = req_url; + } + catch (e) { return false; } + } + } + else + this.handler.render(this); + + return true; +}, + +post: function(baseurl, data) +{ + this.clear(); + this.method = "POST"; + this.baseurl = baseurl; + this.start = this.end = 10; + this.post_data = ""; + + if (typeof data == 'object') + { + for (var k in data) + this.post_data += (this.post_data ? "&" : "") + k + "=" + escape(data[k]); + } + else + this.post_data = data; + + this.load(baseurl, 0, 1, ""); +}, + +readyStateChanged: function() +{ + var loader = this.loader; // Avoid race conditions + it_loader.sequence += "r"; + + 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; + it_loader.sequence += "e"; + + 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); + + it_loader.sequence += "h"; + } + + this.unlink(); +}, + +stop: function() +{ + try + { + this.loader.abort(); + } + catch (e) { } + + this.unlink(); +}, + +unlink: function() +{ + if (it_loader['__inst'+this.instance]) + it_loader['__inst'+this.instance] = null; + + if (this.loader) + this.loader = null; + + if (this.scrpt) + { + document.body.removeChild(this.scrpt); + this.scrpt = null; + } + + it_loader.sequence = ""; +}/* NO COMMA */ + +} + +// static properties +it_loader.instances = 0; +it_loader.sequence = ""; diff --git a/itjs/state.html b/itjs/state.html new file mode 100644 index 0000000..36f36c0 --- /dev/null +++ b/itjs/state.html @@ -0,0 +1,31 @@ +<?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"> +var state_load_attempts = 0; +function state_onload() +{ + if (state_load_attempts < 30) + { + state_load_attempts++; + if (parent.it_state && parent.it_state.ready) + parent.setTimeout(parent.it_state_restore_history, 1); + else + window.setTimeout(state_onload, 90); + } + window[(document.all?'onbeforeunload':'onunload')] = state_onbeforeunload; +} +function state_onbeforeunload() +{ + if (parent.it_state && !parent.it_state.it_state_saved) + parent.it_state.store_state(); +} +</script> +</head> +<body onload="state_onload()"><form action="/itjs/state.html" method="get"><input type="hidden" id="state" name="s" value=""></form></body> +</html> +EOF +?> diff --git a/itjs/state.js b/itjs/state.js new file mode 100644 index 0000000..70d9909 --- /dev/null +++ b/itjs/state.js @@ -0,0 +1,120 @@ +/** + * Generic state object (singleton), for supporting Ajax browser history + * + * $Id$ + * + */ +var it_state = +{ +it_iframe: null, +it_history_field: null, +it_state_saved: false, +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) +{ + this.store_state(); + this.it_state_saved = true; + + if (!this.it_iframe && !(this.it_iframe = document.getElementById('it_state'))) + ED('it_state::new_history_entry(): it_state object not found!'); + + var idoc; + if ((idoc = it_get_iframe_document(this.it_iframe))) + { + idoc.title = document.title; + idoc.forms[0].submit(); + } + 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 + this.it_state_saved = false; + + if (this.it_history_field.value) + { + var res = eval('({' + this.it_history_field.value + '})'); + 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 || !idoc.location.href.match(/s=/)))) + 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); + } + + 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 |