diff options
Diffstat (limited to 'WebContent/client/client.js')
-rw-r--r-- | WebContent/client/client.js | 1677 |
1 files changed, 1677 insertions, 0 deletions
diff --git a/WebContent/client/client.js b/WebContent/client/client.js new file mode 100644 index 0000000000..0c2266ffd9 --- /dev/null +++ b/WebContent/client/client.js @@ -0,0 +1,1677 @@ + +/** Creates new Millstone ajax client. + * @param windowElementNode Reference to element that will contain the + * application window. + * @param servletUrl Base URL to server-side ajax adapter. + * @param clientRoot Base URL to client-side ajax adapter resources. + * @constructor + * + * @author Oy IT Mill Ltd / Sami Ekblad + */ +function MillstoneAjaxClient(windowElementNode, servletUrl, clientRoot, waitElement) { + + // Store parameters + this.mainWindowElement = windowElementNode; + if (this.mainWindowElement == null) { + alert("Invalid window element. Ajax client not properly initialized."); + } + this.millstoneMainWindow = window; + + this.ajaxAdapterServletUrl = servletUrl; + if (this.ajaxAdapterServletUrl == null) { + alert("Invalid servlet URL. Ajax client not properly initialized."); + } + + // Wait element is shown during the ajax requests + this.waitElement = waitElement; + + // Root of the client scripts + if (clientRoot == null) + this.clientRoot = ""; + else (clientRoot.length > 0) + this.clientRoot = clientRoot + (clientRoot.match('/$') ? "" : "/" ); + + // Debugging is disabled by default + this.debugEnabled = false; + + // Initialize variableChangeQueue + this.variableStates = new Object(); + + // Create empty renderers list + this.renderers = new Object(); + + // Create windows list + this.documents = new Object(); + this.windows = new Object(); + + // Remove all eventListeners on window.unload + with (this) { + addEventListener(window,"unload", function () { + var removed = removeAllEventListeners(document); + if (window.eventMap) { + for (var t in window.eventMap) { + var i = window.eventMap[t].length; + while (i--) { + client.removeEventListener(window,t,window.eventMap[t][i]); + removed++; + } + } + window.eventMap = null; + } + + // FIXME remove + //alert("Removed " + removed + " event listeners."); + warn("Removed " + removed + " event listeners."); + //warn("Removed " + removeAllEventListeners(window) + " event listeners."); + // TODO close all windows + warn("Removed " + unregisterAllLayoutFunctions()+ " layout functions."); + + window.png = null; + }); + var client = this; + var func = function() { + client.resizeTimeout=null; + client.processAllLayoutFunctions() + }; + + addEventListener(window,"resize", function () { + if (client.resizeTimeout) clearTimeout(client.resizeTimeout); + client.resizeTimeout = setTimeout(func,500); + }); + + } + + window.png = function(img) { + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf("windows")<0) return; + var msie = ua.indexOf("msie"); + if (msie < 0) return; + var v = parseInt(ua.substring(msie+5,msie+6)); + if (!v || v < 5 || v > 6) return; + + var src = img.src; + var w = img.width; + var h = img.height; + + if (src && src.indexOf("pixel.gif")>0) return; + + img.onload = null; + img.src = clientRoot + "pixel.gif"; + img.style.height = h+"px"; + img.style.width = w+"px"; + img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+src+"', sizingMethod='crop');"; + } + +} + +/** Start the ajax client. + * Creates debug window and sends the initial request to server. + * + * @author Oy IT Mill Ltd / Sami Ekblad + */ +MillstoneAjaxClient.prototype.start = function() { + + if (this.debugEnabled) { + this.debug("Starting Ajax client"); + } + + // Send initial request + this.processVariableChanges(true); +} + + + +/** Creates new debug window. + * + * @return New debug window instance. + * @type Window + * @private + * + * @author Oy IT Mill Ltd / Sami Ekblad + */ +MillstoneAjaxClient.prototype.createDebugWindow = function() { + + var dw = window.open("","MillstoneAjaxDebugWindow","width=500,height=700,scrollbars=1,menubar=0,status=0,titlebar=0,toolbar=0,resizable=1"); + if (dw != null) { + with (dw.document) { + + if (dw.document.body != null) { + dw.document.body.innerHTML = ""; + } + + write("<html><head>"); + write("<title>Millstone Ajax Adapter Debug</title>"); + write("<link rel=\"stylesheet\" href=\""+this.clientRoot+"debug.css\" type=\"text/css\" >"); + write("</head>"); + write("<body><h2>Debug</h2>"); + write("</body></html>\n"); + } + } else { + return null; + } + return dw; +} + + +MillstoneAjaxClient.prototype.warn = function (message, folded, extraStyle, html) { + + // Check if we are in debug mode + if (!this.debugEnabled) { return; } + + this.debug(message, folded, "warn "+(extraStyle?extraStyle:""), html); +} +/** Write debug message to debug window. + * + * @param message The message to be written + * @param folded True if the message should be foldable and folded to default, + * false or missing otherwise. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.debug = function (message, folded, extraStyle, html) { + + // Check if we are in debug mode + if (!this.debugEnabled) { return; } + + // Ensure we have a debug window + if (this.debugwindow == null) { + this.debugwindow = this.createDebugWindow(); + } + + // If window is closed disable debug + if (this.debugwindow.closed) { + alert("Debug window closed. Disabling debug.\n Press reload to re-enable debug window."); + this.debugEnabled = false; + return; + } + + // Apply the extra style given + if (extraStyle != null) { + extraStyle = " "+extraStyle; + } else { + extraStyle = ""; + } + + // Use folded or normal view + if (folded) { + this.debugwindow.document.write("<div onclick=\"if (this.className == 'folded"+extraStyle+"') this.className = 'unfolded"+extraStyle+"'; else this.className = 'folded"+extraStyle+"';\" class='folded"+extraStyle+"'>"); + } else { + this.debugwindow.document.write("<div class='normal"+extraStyle+"'>"); + } + + // Print out as html or as preformatted + if (html) { + this.debugwindow.document.write(message); + } else { + this.debugwindow.document.write("<xmp>"+message+"</xmp>"); + } + this.debugwindow.document.write("</div>"); + + // Scroll to end + this.debugwindow.document.write("<script>window.scrollTo(0,document.body.scrollHeight);</script>"); +} + +/** Write object properties to debug window. + * + * @param obj The object that is debugged. + * @param level The recursion level that the properties are inspected. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.debugObject = function (obj,level) { + this.debug(this.printObject(obj,level),true,null,true); +} + +/** Write error message to debug window. + * + * @param message The message to be written + * @param causeException Exception that caused this error. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.error = function (message, causeException) { + + // Check if we are in debug mode + if (!this.debugEnabled) { return; } + + if (causeException != null) { + // If filename and line number is avaiable, append to output. + if (causeException.fileName != null) { + message += "\n\n FILE= '" + + causeException.fileName +"'" + + (causeException.lineNumber != null ? + " LINE="+causeException.lineNumber: + ""); + } + + // If stack trace is available, append it to output. + if (causeException.stack != null) { + message += "\n\n" + causeException.stack; + } + + // Dump all exception properties + message += "\n\nException properties:\n"; + for (var prop in causeException) { + if (prop != "stack") { + message += " " + prop + "=" +causeException[prop] + "\n"; + } + } + } + this.debug(message,causeException != null, "error"); + +} + +/** Creates new XMLHttpRequest object. + * + * NOTE: The return type of this function is platform dependent. + * + * @return New XMLHttpRequest or XMLHTTP (ActiveXObject) instance + * @type XMLHttpRequest | ActiveXObject + * + * @author Oy IT Mill Ltd / Sami Ekblad + */ +MillstoneAjaxClient.prototype.getXMLHttpRequest = function () { + + var req = false; + + if(window.XMLHttpRequest) { + + // Native XMLHttpRequest object + try { + req = new XMLHttpRequest(); + } catch(e) { + req = false; + } + + } else if(window.ActiveXObject) { + + // IE/Windows ActiveX version + try { + req = new ActiveXObject("Msxml2.XMLHTTP"); + } catch(e) { + try { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + req = false; + } + } + } + return req; +} + +/** Loads a document using XMLHttpRequest object and returns it as text. + * + * @param url The URL of document. + * @skipCache If true, does not use cached documents (or cache this result). + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.loadDocument = function (url,skipCache) { + + if (!skipCache) { + if (!this.loadcache) this.loadcache = new Object(); + var cached = this.loadcache["_"+url]; + if (cached != null) { + this.debug(url + " loaded from cache."); + return cached; + } + } + + var x = this.getXMLHttpRequest(); + x.open("GET",url, false); + x.send(null); + var response = x.responseText; + if (x.status != 200) { + this.error("Could not load (status 200) " + url); + return null; + } + delete x; + + if (!skipCache) { + this.loadcache["_"+url] = response; + } + + if (response) { + this.debug(url + " loaded."); + } else { + this.debug("Could not load " + url); + } + return response; +} + + +/** Registers new renderer function to ajax client. + * + * @param theme Theme instance where the renderer belongs to. + * @param tag UIDL Tag-name that this renderer supports. + * @param componentStyle The style attribute of component that this renderer supports. + * @param renderFunction Function that is performs the rendering of the UIDL. + * @return Newly created renderer object instance. + * @type Object + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.registerRenderer = function (theme, tag, componentStyle, renderFunction) { + + if (renderFunction == null) { + alert("Theme error: Invalid renderer function registered for '"+tag+(componentStyle == null ? "" : "." + componentStyle)+"'"); + } + + // Find previous (parent) renderer + var parentRenderer = this.findRenderer(tag,componentStyle); + + // Create new renderer information object + var renderer = new Object; + renderer.match = tag + (componentStyle == null ? "" : "__" + componentStyle); + renderer.doc = document; + renderer.client = this; + renderer.theme = theme; + renderer.tag = tag; + renderer.componentStyle = componentStyle; + renderer.renderFunction = renderFunction; + renderer.parentRenderer = parentRenderer; + + // This replaces the previous (parent) renderer + this.renderers[renderer.match] = renderer; + + // We return the created renderer object + this.debug("Registered renderer for "+tag +(componentStyle == null ? "" : " (" + componentStyle+")")+""); + return renderer; + +} + + +/** Unregisters all renderers in client. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.unregisterAllRenderers = function () { + + // We just create new, empty rederer map. + this.renderers = new Object(); + +} + +/** Create new response listener for the HTTPRequest object. + * This creates new function reference that is used to + * process the server response in httpRequest.onreadystatechange. + * + * @param client Reference to this client instance. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.createRequestChangeListener = function(client, req) { + + return (function() { + if (req.readyState != 4 || req.status == null) { + return; + } + + // Check status code + if (req.status != 200) { + alert("Server request failed: ("+req.status+", "+req.statusText+")"); + req.onreadystatechange = new Function(); + delete req; + return; + } + + // Get updates + var updates = req.responseXML; + if (updates == null) { + alert("Server did not return anything: "); + req.onreadystatechange = new Function(); + delete req; + return; + } + + // Debug request load time + if (client.debugEnabled) { + var loadedTime = (new Date()).getTime(); + client.debug("UIDL loaded in " + (loadedTime-client.requestStartTime) + "ms"); + client.debug("UIDL Changes: \n"+req.responseText,true); + } + + // Clean up + client.variableStates = new Object(); + req.onreadystatechange = new Function(); + delete req; + + // Process the updates + try { + if (updates.normalize) updates.normalize(); + } catch (e) { + if (client.debugEnabled) { + client.debug("normalize() FAILED"); + } + } + client.processUpdates(updates); + client.requestStartTime = -1; + + }); + +} + +/** Send pending variable changes to server. + * + * This function sends all pending (non-immediate) variable changes to the + * server and registers callback to render process the server response. + * + * @paran repaintAll True if full window UIDL should be requested from server. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.processVariableChanges = function (repaintAll) { + + if (this.waitElement) { + this.waitElement.style.display = "inline"; + } + + // Request start time + this.requestStartTime = (new Date()).getTime(); + + // Build variable change query string + var changes = ""; + for (var i in this.variableStates) { + changes += i + "=" + this.variableStates[i] + "&"; + } + + // Build up request URL + var url = this.ajaxAdapterServletUrl + (repaintAll ? "?repaintAll=1" : "?") + "&requestid=" +this.requestStartTime; + + // Run the HTTP request + this.debug("Send variable changes: " + url); + var activeRequest = this.getXMLHttpRequest(); + // Create callback for request state changes + var changeListener = this.createRequestChangeListener(this,activeRequest); + activeRequest.onreadystatechange = changeListener; + activeRequest.open("POST",url, true); + activeRequest.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded') + activeRequest.send(changes); + +} + +/** Get first child element in given parent. + * + * @param parent The parent element + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.getFirstChildElement = function (parent) { + /* + if (parent == null || parent.childNodes == null) { + return null; + } + for (var j=0; j<parent.childNodes.length; j++) { + var n = parent.childNodes.item(j); + if (n.nodeType == Node.ELEMENT_NODE) { + return n; + } + } + */ + try { + var child = parent.firstChild; + while (child) { + if (child.nodeType == Node.ELEMENT_NODE) { + return child; + } + child = child.nextSibling; + } + } catch (e) { + } + + return null; + + +} + + +/** Initializes new window. + * Creates a document element and initializes it to + * to contain a window component. + * + * @param win The window to be initialized. + * @param name Millstone name of the window to be initialized. + * @return reference to div in document that should contain the window. + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.initializeNewWindow = function (win,uidl,theme) { + + if (win == null) { + return null; + } + + // Special handling for framewindows + var framewindow = uidl.nodeName == "framewindow"; + var name = uidl.getAttribute("name"); + var caption = uidl.getAttribute("caption")||""; + + if (this.debugEnabled) { + this.debug("Initializing new "+(framewindow?"frame-":"")+"window '"+name+"' (PID="+uidl.getAttribute("id")+")"); + } + + // Create HTML content + var html=""; + if (framewindow) { + html = this.createFramesetHtml(uidl,theme) + } else { + html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><HTML><HEAD id=\"html-head\"><TITLE>"+caption+"</TITLE></HEAD>"+"<BODY STYLE=\" overflow: hidden; border: none; margin: 0px; padding: 0px;\"><div id=\"millstone-window\" class=\"window\"></div><\/BODY><\/HTML>"; + } + win.document.open(); + win.document.write(html); + win.document.close(); + win.document.ownerWindow = win; + win.document.renderUIDL = function(uidl,currentNode) { + this.client.renderUIDL(uidl,currentNode); + } + win.document.client = this; + with (this) { + addEventListener(win,"unload", function () { + try { + // TODO detect external url instead? + removeAllEventListeners(win.document); + removeAllEventListeners(win); + unregisterAllLayoutFunctions(win.document); + } catch (e) { + // IGNORED FIXME + } + }); + var client = this; + addEventListener(win,"resize", function () { + try { + // TODO detect external url instead? + setTimeout(function() {client.processAllLayoutFunctions()},1); + } catch (e) { + // IGNORED FIXME + } + }); + + } + // Add stylesheets + if (!framewindow) { + for (var si in this.mainDocument.styleSheets) { + var ss = this.mainDocument.styleSheets[si]; + var nss = win.document.createElement('link'); + nss.rel = 'stylesheet'; + nss.type = 'text/css'; + nss.media = ss.media; + nss.href = ss.href; + win.document.getElementById('html-head').appendChild(nss); + } + } + + // Register it to client + this.registerWindow(name, win, win.document); + + // Add unregister callback + var client = this; + win.onunload = function() { + client.unregisterWindow(name); + win.onunload = null; + } + + // Ensure the name + win.millstoneWindowName = name; + + // Assign the current node into that window + var winElement = win.document.getElementById("millstone-window"); + if (framewindow) { + winElement = win.document.getElementById(uidl.getAttribute("id")); + } + if (winElement == null && this.debugEnabled) { + this.debug("NOTE: Window element not found!"); + } + win.document.millstoneWindowElement = winElement; + + + if (!win.png) { + var clientRoot = this.clientRoot; + // PNG loading support in IE + win.png = function(img) { + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf("windows")<0) return; + var msie = ua.indexOf("msie"); + if (msie < 0) return; + var v = parseInt(ua.substring(msie+5,msie+6)); + if (!v || v < 5 || v > 6) return; + + var src = img.src; + var w = img.width; + var h = img.height; + + if (src && src.indexOf(clientRoot+"pixel.gif")>0) return; + + img.onload = null; + img.src = clientRoot + "pixel.gif"; + img.style.height = h+"px"; + img.style.width = w+"px"; + img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+src+"', sizingMethod='crop');"; + } + } + + // Return the content element + return winElement; +} + +/** Recursively create frameset html for FrameWindow initialization. + * + * @param win The window to be initialized. + * @param name Millstone name of the window to be initialized. + * @return reference to div in document that should contain the window. + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.createFramesetHtml = function(uidl,theme) { + + if (uidl == null) { + return ""; + } + var cols = uidl.getAttribute("cols"); + var rows = uidl.getAttribute("rows"); + var caption = uidl.getAttribute("caption")||""; + + // Open frameset + var html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><title>"+caption+"</title></head><frameset "; + if (cols) { + html += "cols=\""+cols+"\""; + } else if (rows) { + html += "rows=\""+rows+"\""; + } + html += " id=\""+uidl.getAttribute("id")+"\""; + html += " >"; + + // Sub-frames / -framesets + for (var i=0; i<uidl.childNodes.length; i++) { + var n = uidl.childNodes.item(i); + if (n.nodeType == Node.ELEMENT_NODE) { + if (n.nodeName == "frameset") { + html += this.createFramesetHtml(n); + } else if (n.nodeName == "frame") { + var name =n.getAttribute("name"); + var src = n.getAttribute("src"); + html += "<frame id=\""+name+"\" name=\""+name+"\""; + + if (src && src.indexOf("theme://")==0) { + src = (theme?theme.root:"themes/") + src.substring(8); + html += " src=\""+src+"\" "; + } + + html += "/>"; + } + } + } + + // Close frameset + html += "</frameset></html>"; + return html; +} + +/** Unregisters and closes a window. + + * @param windowName Name of the window + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.unregisterWindow = function (windowName) { + if (this.debugEnabled) { + this.debug("Unregistering window '"+windowName+"'"); + } + + var doc = this.documents[windowName]; + var win = this.windows[windowName]; + + if (doc) { + this.documents[windowName] = null; + try { + //if (win.location) win.location.href = "about:blank"; + this.windows[windowName] = null; + win.close(); + doc.ownerWindow = null; + } catch (e) { + if (this.debugEnabled) { + this.error("Exception when closing window '"+windowName+"'. Continuing...",e); + } + } + } else if (this.debugEnabled) { + this.debug("Failed to unregister '"+windowName+"'. Window not found."); + } +} + +/** Registers new window . + * This enabled to client update the components by id in this window. + + * @param windowName Name of the window + * @param doc The document element of window. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.registerWindow = function (windowName,win,doc) { + if (win != null && doc != null && windowName != null) { + doc.millstoneWindowName = windowName; + this.documents[windowName] = doc; + this.windows[windowName] = win; + if (this.debugEnabled) { + this.debug("Registered new window '"+windowName+"'"); + } + } +} + +/** Find a paintable by id. + * Searcher all windows for given id and returns the element + * or null if not found. + + * @param paintableId Id to look for. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.findPaintableById = function (paintableId) { + + if (this.documents == null) { + return null; + } + for (var name in this.documents) { + var d = this.documents[name]; + var win = this.windows[name]; + + try { + if (win && win.location && this.millstoneMainWindow.location.href != win.location.href) { + // referencing external url, we cannot access, but no problem: + // it can't contain the paitable either. + if (this.debugEnabled) { + this.debug("Window: '"+name+"' referencing external URL and can NOT contain Paintable '"+paintableId+"'"); + } + continue; + } + } catch (e) { + this.debug("Exception while examining window.location, assuming ext url."); + continue; + } + + try { + if (d != null && win && !win.closed) { + var el = d.getElementById(paintableId); + if (el != null) { + if (this.debugEnabled) { + var isMain = el.ownerDocument == this.mainDocument? "main":" child"; + this.debug("Paintable '"+paintableId+"' found in "+isMain+"-window: '"+d.millstoneWindowName+"'"); + } + return el; + } else { + if (this.debugEnabled) { + this.debug("Window: '"+d.millstoneWindowName+"' does NOT contain Paintable '"+paintableId+"'"); + } + } + } + } catch (e) { + if (this.debugEnabled) { + this.error("Exception when accessing window '"+name+"'. Closing and continuing...",e); + } + this.unregisterWindow(name); + } + } + if (this.debugEnabled) { + this.debug("Paintable '"+paintableId+"' NOT found in ANY of current windows."); + } + return null; +} + +/** Process UIDL updates from server. + * + * Renders user interface changes. The registered renderers + * are then used to render the changes to correct location. + * + * @param updates Updates UIDL updates from server. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.processUpdates = function (updates) { +var T = new Date().getTime(); + + if (this.debugEnabled) { + this.debug("Processing updates."); + } + + try { + // Iterate through the received changes + var changes = updates.getElementsByTagName("change"); + var cLen = changes.length; + for (var i=0; i<cLen; i++) { + + // Render start time + var renderStartTime = (new Date()).getTime(); + var change = changes.item(i); + var paintableId = change.getAttribute("pid"); + var windowName = change.getAttribute("windowname"); + var invisible = (change.getAttribute("visible") == "false"); + var changeContent = this.getFirstChildElement(change); + invisible = invisible || (changeContent && changeContent.getAttribute("invisible") == "true"); + var paintableName = (changeContent!= null?changeContent.nodeName:"(unknown)"); + + if (this.debugEnabled) { + this.debug(" "); + this.debug("Change "+i+" Id='"+paintableId+"' Paintable='"+paintableName+"'"); + } + + // Get the containing element from all current windows + var currentNode = this.findPaintableById(paintableId); + + // Use window element by default for windows + if (currentNode == null && changeContent != null) { + + if (changeContent.nodeName == "window" || changeContent.nodeName == "framewindow") { + var winName = changeContent.getAttribute("name"); + + if (this.mainDocument == null) { + // Initialize the main document/window + currentNode = this.mainWindowElement; + currentNode.ownerDocument.ownerWindow = window; + this.registerWindow(winName, window, currentNode.ownerDocument); + this.mainDocument = currentNode.ownerDocument; + this.mainDocument.isMainDocument = "true"; + } else { + // Open a new window if no document was found + var limit = new Date().getTime() + (1000*3); + var win = window.open("about:blank",winName); + while (new Date().getTime() < limit) { + try { + var url = win.location.href; + break; + } catch (e) { + // IE slow sometimes, buzy-loop for permission ( TODO better solution? ) + this.debug("Permission denied for window "+winName+", retrying."); + } + } + try { + var url = win.location.href; + } catch (e) { + alert("Could not open window."); + win = window.open("about:blank",winName); + } + + currentNode = this.initializeNewWindow(win,changeContent); + } + } + } + + if (currentNode != null) { + + if (invisible) { + //alert("invisible"); + // Special hiding procesedure for windows + if (windowName != null) { + this.unregisterWindow(windowName); + } else { + // Hide invisble components + currentNode.style.display = "none"; + } + + } else { + // Make sure we are visible + if (currentNode.style) currentNode.style.display = ""; + } + + + // Process all uidl nodes inside a change + var uidl = change.firstChild; + while (uidl) { + //var clen = change.childNodes.length; + //for (var j=0; j<clen; j++) { + + //var uidl = change.childNodes.item(j); + if (uidl.nodeType == Node.ELEMENT_NODE) { + + // Replace the contents of the current representation + // Create empty div for rendering + + /* Render method 2 code + var newNode = this.createPaintableElement(uidl); + + // Swap the old one with new one + if (currentNode.parentNode != null) { + var parent = currentNode.parentNode; + parent.replaceChild(newNode,currentNode); + newNode.id = paintableId; + // TODO working: + var removed = this.removeAllEventListeners(currentNode); + + + // Render to target div + this.renderUIDL(uidl,newNode,null,currentNode); + delete currentNode; + } + */ + + + if (!currentNode) { + currentNode = this.createPaintableElement(uidl); + } + this.warn("Removed " + this.removeAllEventListeners(currentNode) + " event listeners."); + this.warn("Removed " + this.unregisterAllLayoutFunctions(currentNode) + " layout functions."); + if (currentNode.ownerDocument.renderUIDL) { + currentNode.ownerDocument.renderUIDL(uidl,currentNode); + } else { + this.renderUIDL(uidl,currentNode); + } + } + uidl = uidl.nextSibling; + } + } else { + this.error("Change " + i +" node not found. Id='"+ paintableId+ "'. Paintable='"+ paintableName+"'"); + } + + if (this.debugEnabled) { + var renderEndTime = (new Date()).getTime(); + this.debug("Change " + i + " Id='"+ paintableId+ "'. Paintable='"+ paintableName +"' rendered in " + (renderEndTime-renderStartTime) + "ms"); + } + } + } catch (e) { + // Print out the exception + if (this.debugEnabled) { + this.error("Could not process changes: "+e.message,e); + } else { + alert("Failed to process all changes. \n Please enable debug logging to get detailed error description"); + } + } + + this.processAllLayoutFunctions(); + + var endTime = (new Date()).getTime(); + if (this.debugEnabled && this.requestStartTime > 0 ) { + this.debug("Total time for update " + (endTime-this.requestStartTime) + "ms"); + } + if (this.waitElement) { + this.waitElement.style.display = "none"; + } + +// FIXME REMOVE +//alert("EventListeners:" + document.eventListenerCount +"\nTotal ms: " + (new Date().getTime() -T)); +} + +/** Render the given UIDL to target. + * + * If no renderer is specified the the internal renderer registry is + * looked up for matching renderer. + * + * @param uidl The UIDL node that is rendered. + * @param target The targer element where the result should be appended. + * @param renderer The specific renderer instance that should be used (optional) + * @return This function returns whatever the utilized renderer returns. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.renderUIDL = function (uidl, target, renderer) { + + // Sanity check + if (uidl == null || uidl.nodeType != Node.ELEMENT_NODE) return; + + // Render the UIDL using the given renderer + if (renderer != null) { + + // Invoke renderer and return whatever it returns. + + // Function argument pass-through: + // Create arguments array and add all callers extra parameters + // to the end of arguments. + var args = new Array(); + args[args.length] = renderer; + args[args.length] = uidl; + args[args.length] = target; + for(var i=3; i<arguments.length; i++) { + args[args.length] = arguments[i]; + } + if (this.debugEnabled) { + this.debug("Theme '"+ renderer.theme.themeName + "' rendering '"+ uidl.nodeName + "' into '"+target.nodeName+"' (id="+target.id+")"); + } + try { + var res = renderer.renderFunction.apply(this,args); + return res; + } catch (e) { + // Print out the exception + this.error("Could not render "+ uidl.nodeName +" using '"+ renderer.theme.themeName + "': "+e.message,e); + } + + + } else { + + // Lookup for renderer + var style = uidl.getAttribute("style"); + var tag = uidl.nodeName; + var renderer = this.findRenderer(tag,style); + + // Render the UIDL using the found renderer + if (renderer != null) { + + // Function argument pass-through: + // Create arguments array and add all callers extra parameters + // to the end of arguments. + var args = new Array(); + args[args.length] = uidl; + args[args.length] = target; + args[args.length] = renderer; + for(var i=3; i<arguments.length; i++) { + args[args.length] = arguments[i]; + } + return this.renderUIDL.apply(this,args); + } + } + + // If no renderer is specified, render the UIDL as-is. + return this.renderHTML(uidl, target); +} + +/** Search the internal renderer registry for matching renderer. + * + * The matching process first looks up for exact tag and componentStyle + * match, but if no renderer is found it uses only the tag name matching. + * If still no renderer is found returns null. + * + * @param tag UIDL tag name. + * @param componentStyle The style attribute of the component (optional) + * @return A matching renderer instance + * @type Object + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.findRenderer = function (tag, componentStyle) { + var renderer = null; + var rendererId = tag + (componentStyle == null ? "" : "__" + componentStyle); + + // Try to find with specific style + if (componentStyle != null) { + renderer = this.renderers[rendererId]; + } + + // Try to find a renderer only using tag + if (renderer == null) { + renderer = this.renderers[tag]; + } + + return renderer; +} + + +/** Renders given XML as redable HTML. + * + * @param xml The XML node to be rendered as readable HTML. + * @param target The node where the result should be appended as child. + * @return The rendered element + * @type Node + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.renderHTML = function (xml, target) { + + var n = this.createElement("div",target); + target.appendChild(n); + n.setAttribute("style", "padding-left:10px;background-color:white;z-index:9999;position:relative;"); + var tn = this.createTextNode("<" + xml.nodeName, target); + n.appendChild(tn); + if (xml.attributes.length > 0) + for(var i=0; i<xml.attributes.length; i++) { + var a = xml.attributes.item(i); + n.appendChild(this.createTextNode(" " + a.name + "=\"" + a.value+"\"",target)); + } + n.appendChild(this.createTextNode(">",target)); + if (xml.hasChildNodes()) + for (var i=0; i<xml.childNodes.length; i++) { + var c = xml.childNodes.item(i); + if (c.nodeType == Node.ELEMENT_NODE) { + this.renderHTML(c,n); + } else if (c.nodeType == Node.TEXT_NODE && c.data != null) { + n.appendChild(this.createTextNode(c.data, target)); + } + } + n.appendChild(this.createTextNode("</"+xml.nodeName+">",target)); + return n; +} +/** Returns given XML as text. + * + * @param xml The XML node to be rendered as HTML. + * @return The XML as text + * @type String + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.getXMLtext = function(xml) { + var n = ""; + n += "<" + xml.nodeName; + if (xml.attributes.length > 0) + for(var i=0; i<xml.attributes.length; i++) { + var a = xml.attributes.item(i); + n += " " + a.name + "=\"" + a.value+"\""; + } + n += ">"; + if (xml.hasChildNodes()) + for (var i=0; i<xml.childNodes.length; i++) { + var c = xml.childNodes.item(i); + if (c.nodeType == Node.ELEMENT_NODE) { + n += this.getXMLtext(c); + } else if (c.nodeType == Node.TEXT_NODE && c.data != null) { + n += c.data; + } + } + n += "</"+xml.nodeName+">"; + return n; +} + +/** Send a change variable event to server. + * + * Changes a variable value and if 'immediate' is true invokes the + * processVariableChanges function. + * + * @param name The name of the variable to change. + * @param value New value of the variable. + * @immediate True if the variable change should immediately propagate to server. + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.changeVariable = function (name, value, immediate) { + this.debug("variableChange('" + name + "', '" + value + "', " + immediate + ");"); + + this.variableStates[name] = escape(value); + + if (immediate) + this.processVariableChanges(false); +} + +/** Create new containing element for a paintable (component). + * + * This function creates new containing element for a single + * paintable object, typically component. + * + * @param uidl The UIDL node of the paintable. + * @param target The target node where the new containing element should be + * appended to as child. + * @return The newly created element node. + * @type Node + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.createPaintableElement = function (uidl, target) { + + // Create DIV as container to right document. + var div = this.createElement("div", target); + + // Append to parent, if 'target' parameter specified + if (target != null) { + target.appendChild(div); + } + + // Add ID attribute + var pid = uidl.getAttribute("id"); + if (target != null && pid != null) { + div.setAttribute("id",pid); + } + + // Set visibility + var invisible = uidl.getAttribute("invisible"); + if (target != null && invisible == "true") { + div.style.display = "none"; + } else { + div.style.display = ""; + } + + // Return reference to newly created div + return div; +} + + +/** Assigns given CSS class to element. + * Cross-browser function for assigning CSS class attribute to + * an element. + * @param element The element where the class should be applied. + * @param className The CSS class name to apply. + * @return element. + * @type Node + * + * @author Oy IT Mill Ltd / Sami Ekblad + * + */ +MillstoneAjaxClient.prototype.setElementClassName = function(element,className) { + if (element == null) { return; } + element.style.className = className; + +} + +/** + * Add event listener function to an element. + * + * @param element The element + * @param type Type of event to listen for [click|mouseover|mouseout|...] + * @param func The function to call. Called with single parameter: event. + * + * @return the listener function added + */ +MillstoneAjaxClient.prototype.addEventListener = function(element,type,func) { + if (element.addEventListener) { + element.addEventListener(type, func, false); + + } else if (element.attachEvent) { + element.attachEvent("on" + type, func); + + } else { + element['on'+type] = func; + } + + // TODO add only to paintable? + + if (!element.eventMap) element.eventMap = new Object(); + if (!element.eventMap[type]) element.eventMap[type] = new Array(); + element.eventMap[type][element.eventMap[type].length] = func; + + // FIXME remove + if (!document.eventListenerCount) document.eventListenerCount = 0; + document.eventListenerCount++; + + return func; +} +/** + * Remove event listener function from a element. The parameters should match addEventListener() + * + * @param element The element + * @param type Type of event to listen for [click|mouseover|mouseout|...] + * @param func The listener function to remove. + * + */ +MillstoneAjaxClient.prototype.removeEventListener = function(element,type,func) { + if (element.removeEventListener) { + element.removeEventListener(type, func, false); + + } else if (element.detachEvent) { + element.detachEvent("on" + type, func); + + } else { + element['on'+type] = null; + } + if (element.eventMap && element.eventMap[type]) { + for (var f in element.eventMap[type]) { + if (element.eventMap[type][f]==func) { + element.eventMap[type][f] = null; + break; + } + } + } + document.eventListenerCount--; +} + +/** + * Remove all event listener functions from a element. + * + * @param element The element + * @param type Type of event to listen for [click|mouseover|mouseout|...] + * @param func The listener function to remove. + * + */ + MillstoneAjaxClient.prototype.removeAllEventListeners = function(element) { + var removed = 0; + if (element.eventMap) { + for (var t in element.eventMap) { + var i = element.eventMap[t].length; + while (i--) { + this.removeEventListener(element,t,element.eventMap[t][i]); + removed++; + } + } + + element.eventMap = null; + } + // TODO eventMAp -> paintable & only get DIV:s + var childs = element.getElementsByTagName("*"); + if (childs) { + var i = childs.length; + while (i--) { + element = childs[i]; + if (element.eventMap) { + for (var t in element.eventMap) { + var j = element.eventMap[t].length; + while (j--) { + this.removeEventListener(element,t,element.eventMap[t][j]); + removed++; + } + } + + element.eventMap = null; + } + } + } + + return removed; +} + +MillstoneAjaxClient.prototype.registerLayoutFunction = function (paintableElement,func) { + if (!paintableElement || !func) { + this.error("Invalid layout function registration; paintableElement:"+paintableElement+" func:"+func); + return; + } + + var pid = paintableElement.id; + if (!pid) { + this.error("Register layout function; paintableElement pid not found!" + paintableElement); + return; + } + + if (!this.layoutFunctionsOrder) { + this.layoutFunctionsOrder = new Object(); + this.layoutFunctions = new Array(); + } + + var idx = this.layoutFunctionsOrder[pid]; + if (typeof(idx) == "undefined") idx = this.layoutFunctions.length; + + this.layoutFunctionsOrder[pid] = idx; + this.layoutFunctions[idx] = func; + + this.debug("Registered layout function for ("+paintableElement.nodeName+") pid " + pid + " as number " + idx); +} +MillstoneAjaxClient.prototype.unregisterLayoutFunction = function (paintableElement) { + if (!paintableElement) { + this.error("unregisterLayoutFunction(): NULL paintableElement!"); + return false; + } + + if (!this.layoutFunctionsOrder) { + // no functions at all + return false; + } + + var pid = paintableElement.id; + if (!pid) { + this.error("unregisterLayoutFunction(): paintableElement pid not found!" + paintableElement); + return false; + } + + var idx = this.layoutFunctionsOrder[pid]; + if (typeof(idx) == "undefined") { + // no registered function + return false; + } + + this.layoutFunctions[idx] = null; + delete this.layoutFunctionsOrder[pid]; + + this.debug("Unregistered layout function " + pid); + + return true; +} +MillstoneAjaxClient.prototype.unregisterAllLayoutFunctions = function (paintableElement) { + var removed = 0; + if (!paintableElement) { + removed = (this.layoutFunctions?this.layoutFunctions.length:0); + this.layoutFunctions = null; + this.layoutFunctionsOrder = null; + this.debug("Unregistered ALL layout functions!"); + return removed; + } + + + + if (paintableElement.id) { + if (this.unregisterLayoutFunction(paintableElement)) removed++ + } + var cn = paintableElement.getElementsByTagName("div"); + if (cn) { + var len = cn.length; + for (var i=0;i<len;i++) { + if (cn[i].id) { + if (this.unregisterLayoutFunction(cn[i])) removed++ + } + } + } + + return removed; +} +MillstoneAjaxClient.prototype.processAllLayoutFunctions = function() { + if (this.layoutFunctions) { + this.debug("Processing layout functions..."); + var lf = this.layoutFunctions; + var lfo = this.layoutFunctionsOrder; + var cnt = 0; + for (var pid in lfo) { + var idx = lfo[pid]; + var func = lf[idx]; + try { + func(); + cnt++; + } catch (e) { + this.error("Layout function "+pid+" failed; "+ e + " Removing."); + delete this.layoutFunctionsOrder[pid]; + this.layoutFunctions[idx] = null; + } + } + this.debug("...processed " + cnt + " successfully"); + } +} + + +/** Returns a cross-browser object with useful event properties. + * e: the ('raw') event + * type: event type + * target: the target element + * targetX: X-position of the target element + * targetY: Y-position of the target element + * key: pressed key character + * alt: true if ALT -key was held + * shift: true if SHIFT -key was held + * ctrl: true if CTRL -key was held + * rightclick: true if the right mousebutton was clicked, or ctrl held while clicking + * mouseX: X-position of the mouse + * mouseY: Y-position of the mouse + * + * @param e The event, null for window.event (IE) + * + * @return Properties object. + */ +MillstoneAjaxClient.prototype.getEvent = function(e) { + var props = new Object() + + if (!e) var e = window.event; + props.e = e; + props.type = e.type; + + var targ; + if (e.target) { + targ = e.target; + } else if (e.srcElement) { + targ = e.srcElement; + } + if (targ.nodeType == 3) { + targ = targ.parentNode; + } + props.target = targ; + var p = this.getElementPosition(targ); + props.targetX = p.x; + props.targetY = p.y; + + var code; + if (e.keyCode) { + code = e.keyCode; + } else if (e.which) { + code = e.which; + } + if (code) { + props.key = String.fromCharCode(code); + } + + props.alt = e.altKey; + props.ctrl = e.ctrlKey; + props.shift = e.shiftKey; + + var rightclick; + if (e.which) { + rightclick = (e.which == 3 || (props.ctrl)); + } else if (e.button) { + rightclick = (e.button == 2|| (props.ctrl)); + } + props.rightclick = rightclick; + + /* New way to calculate mouse position, 9.6.2006 - Jouni Koivuviita */ + var d = document, v = window, window_w, window_h, window_l, window_t; + if( typeof v.innerWidth==='number' ) { + + window_w = v.innerWidth; + window_h = v.innerHeight; + window_l = v.pageXOffset; + window_t = v.pageYOffset; + + } else if( ( v = d.documentElement ) && + typeof v.clientWidth==='number' && + v.clientWidth !== 0 || ( v = document.body ) ) { + + window_w = v.clientWidth; + window_h = v.clientHeight; + window_l = v.scrollLeft; + window_t = v.scrollTop; + + } + + if( typeof e.pageX==='number' ) { + props.mouseX = e.pageX; + props.mouseY = e.pageY; + } else { + props.mouseX = e.x + window_l; + props.mouseY = e.y + window_t; + } + + //props.mouseX = e.pageX||e.clientX; + //props.mouseY = e.pageY||e.clientY; + + props.stop = function() { + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + if (e.preventDefault) e.preventDefault(); + return false; + } + + return props; +} + +MillstoneAjaxClient.prototype.getElementPosition = function(element) { + var props = new Object(); +// TODO scroll offsets testing in IE + var obj = element; + var x = obj.offsetLeft + (obj.scrollLeft||0); + var y = obj.offsetTop + (obj.scrollTop||0); + if (obj.parentNode||obj.offsetParent) { + while (obj.offsetParent||obj.parentNode) { + obj = obj.offsetParent||obj.parentNode; + if (obj.nodeName == "TBODY") continue; + x += (obj.offsetLeft||0) - (obj.scrollLeft||0); + y += (obj.offsetTop||0) - (obj.scrollTop||0); + } + } else if (obj.x) { + x += obj.x; + y += obj.y; + } + props.x = x; + props.y = y; + props.h = element.offsetHeight; + props.w = element.offsetWidth; + + return props; +} + +/** Prints objects properties into separate window. + * + * @obj Object to be printed + * @level recursion level + */ +MillstoneAjaxClient.prototype.debugObjectWindow = function(obj,level) { + + // Default level + if (level == null) { + level = 2; + } + + //print into string + var str = this.printObject(obj,level); + + // open a window for debug + var win = window.open("", "ms_ajax_debug"); + win.document.open(); + win.document.write("<html><body>"+str+"+</body></html>"); + win.document.close(); +} + +/** Print a object instace as html string. + * Prints (recursively) the objects properties into a html table. + * + * @obj Object to be printed + * @level recursion level + */ +MillstoneAjaxClient.prototype.printObject = function(obj,level) { + if (level == null || level < 1) { + level = 1; + } + + var str = "<table border=\"0\"><tr><td colspan=\"3\">Object: "+obj+"<hr /></td></tr>"; + try { + for (var prop in obj) { + str += "<tr><td valign=\"top\">"+prop+"</td><td valign=\"top\"> = </td><td valign=\"top\">"; + try { + if (typeof obj[prop] == "object" && level > 1) { + str += this.printObject(obj[prop],level-1); + } else { + str += obj[prop]; + } + } catch(ignored) { + str += "[EVAL FAILED]"; + } + str += "</td></tr>"; + } + } catch (e) { + str += "<tr><td colspan=\"3\">[Failed to list object properties: "+e.message+"]</td></tr>" + } + str += "</table>"; + return str; +} + +/** Creates a text node to the same document as target. + * If target is null or not given the document reference is + * used instead. + * + * @target Target element + * @text Textnode content + */ +MillstoneAjaxClient.prototype.createTextNode = function(text, target) { + if (target != null && target.ownerDocument != null) { + return target.ownerDocument.createTextNode(text); + } else { + return document.createTextNode(text); + } +} + +/** Creates a element node to the same document as target. + * If target is null or not given the document reference is + * used instead. + * + * @nodeName Element nodeName + * @text Textnode content + */ +MillstoneAjaxClient.prototype.createElement = function(nodeName, target) { + + if (target != null && target.ownerDocument != null) { + return target.ownerDocument.createElement(nodeName); + } else { + return document.createElement(nodeName); + } +} |