/* * SPL - The SPL Programming Language * Copyright (C) 2006 Clifford Wolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mod_w2t.spl: The SPL Web 2.0 Toolkit */ /** * The SPL Web 2.0 Toolkit * * This module is EXPERIMENTAL and not so well document yet. * Expect more to come.. */ load "task"; load "cgi"; load "xml"; load "encode_xml"; /** * The core w2t browser-side scripts. * * This JavaScript code block defines the following functions: * * function w2t_encode_xml(text) * * XML-encode the parameter and return the encoded text * * function w2t_console_open() * * Open a W2T console window (for debug purposes). * Usually one adds a little button or link to the development * versions of an application which calls this function. * * function w2t_console(title, text) * * This function can be used to write a message to all open * W2T console windows. * * function w2t_proccess_dom(n) * function w2t_proccess(xmldata) * function w2t_update_status() * function w2t_gc() * * Some functions used internally by w2t_send() for creating the * XML requests and processing the XML responses. * * function w2t_send(xmldata, onfinish) * * Send a request specified in 'xmldata' to the server and call * 'onfinish' after the response has been processed. Usually * one is calling w2t_callback() instead of using w2t_send() * directly. * * function w2t_callback(callback_name, arguments) * * Call a callback on the server. * The second argument is an array of arguments. * * function w2t_animate(finish, workers, seconds, fps) * * A generic helper function for animations. The animation runs * for the specified number of seconds with the specified number * of frames per second. Frames are automatically skipped when * rendering becomes too slow. For each frame all functions in * the 'workers' array are called with a floating point value * as parameter (0 for the first frame up to 1 for the last one). * * function w2t_animate_fade(id, attr, newval) * function w2t_animate_svgmove(id, newx, newy) * * Helper functions which do return function pointers to * dynamically created worker functions for w2t_animate(). * */ var w2t_core_scripts = <> /*** core w2t functions ***/ var w2t_console_windows = [ ]; var w2t_last_gc_data = ""; var w2t_running_requests = 0; var w2t_gc_rev = 0; function w2t_encode_xml(text) { text = text.replace(/&/g, "&"); text = text.replace(//g, ">"); text = text.replace(/"/g, """); text = text.replace(/'/g, "'"); return text; } function w2t_console_open() { var w = window.open("about:blank", "_blank", "width=600,height=500,resizable=yes,scrollbars=yes"); w.document.write("SPL Web 2.0 Toolkit - Console\n"); w.document.write("\n"); w.document.write("

SPL Web 2.0 Toolkit - Console

\n"); w.document.write("
\n"); w2t_console_windows.push(w); } function w2t_console(title, text) { var htmlcode = "

" + w2t_encode_xml(title) + "

" + w2t_encode_xml(text) + "
\n"; for (var i=0; i " + n.textContent); on.setAttribute(n.getAttribute("name"), n.textContent); break; default: w2t_console("w2t_proccess_dom()", 'Unknown method: ' + method); } } function w2t_proccess(xmldata) { var dom_list = xmldata.evaluate("/response/*", xmldata, null, XPathResult.ANY_TYPE, null); for (var i = dom_list.iterateNext(); i; i = dom_list.iterateNext()) { switch (i.nodeName) { case "dom": w2t_proccess_dom(i); break; case "script": eval(i.textContent); break; case "callbacksrev": if (parseInt(w2t_gc_rev) < parseInt(i.textContent)) { if (w2t_running_requests == 1) w2t_gc_rev = i.textContent; else w2t_console("w2t_proccess()", 'Ignoring gc_rev updated to avoid race conditions: ' + w2t_running_requests + ' parallel active requests.'); } break; default: w2t_console("w2t_proccess()", 'I do not know what to do with a ' + i.nodeName + ' element.'); } } } function w2t_update_status() { var message; if (w2t_running_requests <= 0) message = "READY."; else if (w2t_running_requests == 1) message = "Waiting for server ..."; else message = "Waiting for server (" + w2t_running_requests + ")..."; window.defaultStatus = message; } function w2t_gc() { var callback_list = ""; var gctags = document.evaluate("//@w2t:callbacks", document, w2t_nsresolver, XPathResult.ANY_TYPE, null); for (var i = gctags.iterateNext(); i; i = gctags.iterateNext()) { if (callback_list != "") callback_list += ","; callback_list += i.textContent; } return "" + callback_list + "\n"; } function w2t_send(xmldata, onfinish) { var postdata = "\n\n" + xmldata + w2t_gc() + "\n"; var request; w2t_console("XML Request", postdata); function processReqChange() { // only if req shows "complete" if (request.readyState == 4) { // only if "OK" if (request.status == 200) { w2t_console("XML Response", request.getAllResponseHeaders() + "\n" + request.responseText); if (request.responseXML) w2t_proccess(request.responseXML); else alert("There was a problem parsing the XML response:\n\n" + request.responseText); } else { alert("There was a problem retrieving the XML response:\n" + request.statusText); } if (onfinish) onfinish(); w2t_running_requests--; w2t_update_status(); } } // branch for native XMLHttpRequest object if (window.XMLHttpRequest) { request = new XMLHttpRequest(); } else // branch for IE/Windows ActiveX version if (window.ActiveXObject) { request = new ActiveXObject("Microsoft.XMLHTTP"); } if (request) { w2t_running_requests++; w2t_update_status(); request.overrideMimeType('text/xml'); request.onreadystatechange = processReqChange; request.open("POST", w2t_url, true); request.setRequestHeader('Content-Type', 'text/xml'); request.setRequestHeader('Cache-Control', 'no-cache'); request.send(postdata); } else { alert("Can not create an XMLHttpRequest object!\n"); } } function w2t_callback(callback_name, arguments) { var reqdata = "\n"; if (arguments) { while (arguments.length > 0) reqdata += "" + arguments.shift() + "\n"; } reqdata += "\n"; w2t_send(reqdata); } function w2t_animate(finish, workers, seconds, fps) { var steps = seconds * fps; var interval = 1000 / fps; var start = new Date(); var step = 0; function update() { var now = new Date(); var currentms = now.getTime() - start.getTime(); while (step > 0 && step < steps && currentms >= ((step+1) * interval)) step++; for (i in workers) workers[i](step/steps); if (step < steps) { step++; window.setTimeout(update, interval); } else { if (finish) finish(); } } update(); } function w2t_animate_fade(id, attr, newval) { var element = document.getElementById(id); var oldval = parseFloat(element.getAttribute(attr)); function worker(scale) { var current = oldval * (1-scale) + newval * scale; element.setAttribute(attr, current); } return worker; } function w2t_animate_svgmove(id, newx, newy) { var element = document.getElementById(id); var oldx = parseFloat(element.getAttribute("x")); var oldy = parseFloat(element.getAttribute("y")); function worker(scale) { var current_x = oldx * (1-scale) + newx * scale; var current_y = oldy * (1-scale) + newy * scale; element.setAttribute("x", current_x); element.setAttribute("y", current_y); } return worker; } w2t_update_status(); ; /** * The W2t object. */ object W2t { /** * An array with JavaScript fragments. Other modules can push their * own script fragments to this array when loaded. This scripts are * then automatically included in the application startup page. */ static scripts = [ w2t_core_scripts ]; /** * An array with CSS stylesheet fragments. Other modules can push * their own css fragments to this array when loaded. This scripts * are then automatically included in the application startup page. */ static styles = [ ]; /** * The namespace URI for the "w2t" namespace prefix. */ static w2t_ns = "http://www.spl-scripting.org/xml/w2t"; /** * A hash of namespace declarations one usually wants to use for Web * 2.0 applications (prefix as key and URI as value). Other modules * (and w2t applications) may add additional namespaces. Per default * the following namespaces are defined: * * w2t [[.w2t_ns]] * svg http://www.w3.org/2000/svg * xhtml http://www.w3.org/1999/xhtml * xlink http://www.w3.org/1999/xlink * * This hash is used by [[.getxmlnsdecls()]]. */ static xmlns; xmlns["w2t"] = w2t_ns; xmlns["svg"] = "http://www.w3.org/2000/svg"; xmlns["xhtml"] = "http://www.w3.org/1999/xhtml"; xmlns["xlink"] = "http://www.w3.org/1999/xlink"; /** * The startup page content. This variable must be set before calling * [[.run()]]. */ var startup; /** * The mime type of the startup page. */ var mime_type = "text/xml"; /** * A hash with all callbacks. * * Use [[.register_callback()]] to add entries. */ var callbacks; /** * A hash with oncleanup event handlers for the callbacks. When the * callback garbage collector cleans up a callback, it also executes * the oncleanup handler if there is one. * * Use [[.register_callback()]] to add entries. */ var callbacks_oncleanup; /** * A revision counter for the callbacks list. It is automatically * incremented by [[.register_callback()]]. This counter is needed * to work arround possible race conditions in the client-side * garbage collection algorithm. */ var callbacks_rev = 0; /** * The current request (when [[.run()]] is active). This is an XML * document handler. */ var request; /** * The current response (when [[.run()]] is active). This is an XML * document handler. */ var response; /** * Register a new callback. */ method register_callback(id, handler, oncleanup) { callbacks[id] = handler; if (defined oncleanup) callbacks_oncleanup[id] = oncleanup; callbacks_rev++; } /** * Add a new XML snippet to the [[.response]] document. */ method response_add(xmltext) { page.response["/response"].add_xml_bottom = xmltext; } /** * Append a child node in the client DOM tree. */ method dom_appendchild(id, xmltext) { response_add('$xmltext'); } /** * Remove a node from the client DOM tree. */ method dom_remove(id) { response_add(''); } /** * Replace the inner XML text of node in the client DOM tree. */ method dom_replaceinner(id, xmltext) { response_add('$xmltext'); } /** * Set (and create if needed) an attribute. */ method dom_setattr(id, name, value) { response_add('${xml::value}'); } /** * Execute the javascript fragment passed as argument in the client. */ method execute(script) { response_add(''); } /** * Return the concatenated contents of [[.scripts]] with an additional * header. This must be included in a * block within the [[.startup]] document. */ method getscripts() { var data = <:> : : // This application is using the SPL Web 2.0 Toolkit : // http://www.clifford.at/spl/ : : /*** dynamic stuff from w2t.getscripts() ***/ : : var w2t_url = '${cgi.url}?sid=${cgi.sid}'; : var w2t_ns = "$w2t_ns"; : var w2t_xmlns = '$xmlns'; : : function w2t_nsresolver(nsprefix) { : if (nsprefix == "$ns") : return "${xmlns[ns]}"; : return null; : } : ; foreach[] d (scripts) data ~= d; return data; } /** * Return the concatenated contents of [[.styles]]. This must be * included in a * block within the [[.startup]] document. */ method getstyles() { var data; foreach[] d (styles) data ~= d; return data; } /** * Return a string with 'xmlns:PREFIX="URI"' xml namespace declarations * built using the [[.xmlns]] hash described above. */ method getxmlnsdecls() { var decls = ""; foreach ns (xmlns) decls ~= ($[ ? "" : " ") ~ 'xmlns:$ns="${xmlns[ns]}"'; return decls; } // the counter for getid() static __w2t_getid_counter = 1; /** * Get a unique id, e.g. for the id="" attribute of auto-generated * XML elements and for callback names. */ method getid() { return "w2tid${__w2t_getid_counter++}"; } /** * Main runloop. This function never returns. It just calls prior * registered callback functions. */ method run() { cgi.content_type = mime_type; write(startup); while (1) { task_pause(); cgi.silent_debug = 1; request = xml_parse(cgi.post_data); response = xml_parse(<:> : : : ); var gc_callbacks = request["/request/gc/callbacks"]; var gc_callbacks_rev = request["/request/gc/callbacks/@rev"]; if (defined gc_callbacks and gc_callbacks_rev == callbacks_rev) { var h; foreach[] c (gc_callbacks =~ /[^,]+/Ag) h[c] = undef; foreach c (callbacks) if (not declared h[c]) { if (declared callbacks_oncleanup[c]) { callbacks_oncleanup[c](c); delete callbacks_oncleanup[c]; } delete callbacks[c]; } } foreach[] c (request["/request/callback"].nodes) { var args = [ c ]; foreach[] n (c["argument"].nodes) push args, c[n]; callbacks[c["@callbackname"]](@args); } page.response_add('$callbacks_rev\n'); cgi.content_type = "text/xml"; write(xml_dump(response)); request = undef; response = undef; } } /** * Constructor. */ method init() { return this; } }