xiaoyafeng has asked for the wisdom of the Perl Monks concerning the following question:

Hi monks,
Today I take responsible to tune a web application in JS. it fetch data from oracle and generate a dynamic menu tree on left side. Below is the part of code:
tree.js
// Node object function Node(id, pid, name, url, title, target, icon, iconOpen, open) + { this.id = id; this.pid = pid; this.name = name; this.url = url; this.title = title; this.target = target; this.icon = icon; this.iconOpen = iconOpen; this._io = open || false; this._is = false; this._ls = false; this._hc = false; this._ai = 0; this._p; }; // Tree object function Tree(objName) { this.config = { target : null, folderLinks : true, useSelection : true, useCookies : true, useLines : true, useIcons : true, useStatusText : false, closeSameLevel : false, inOrder : false } this.icon = { root : '../../images/tree/base.gif', folder : '../../images/tree/folder.gif', folderOpen : '../../images/tree/folderopen.gif', node : '../../images/tree/page.gif', empty : '../../images/tree/empty.gif', line : '../../images/tree/line.gif', join : '../../images/tree/join.gif', joinBottom : '../../images/tree/joinbottom.gif', plus : '../../images/tree/plus.gif', plusBottom : '../../images/tree/plusbottom.gif', minus : '../../images/tree/minus.gif', minusBottom : '../../images/tree/minusbottom.gif', nlPlus : '../../images/tree/nolines_plus.gif', nlMinus : '../../images/tree/nolines_minus.gif' }; this.obj = objName; this.aNodes = []; this.aIndent = []; this.root = new Node(-1); this.selectedNode = null; this.selectedFound = false; this.completed = false; }; // Adds a new node to the node array Tree.prototype.add = function(id, pid, name, url, title, target, icon, + iconOpen, open) { this.aNodes[this.aNodes.length] = new Node(id, pid, name, url, tit +le, target, icon, iconOpen, open); }; // Open/close all nodes Tree.prototype.openAll = function() { this.oAll(true); }; Tree.prototype.closeAll = function() { this.oAll(false); }; // Outputs the tree to the page Tree.prototype.toString = function() { var str = '<div class="tree">\n'; if (document.getElementById) { if (this.config.useCookies) this.selectedNode = this.getSelect +ed(); str += this.addNode(this.root); } else str += 'Browser not supported.'; str += '</div>'; if (!this.selectedFound) this.selectedNode = null; this.completed = true; return str; }; // Creates the tree structure Tree.prototype.addNode = function(pNode) { var str = ''; var n=0; var cn; if (this.config.inOrder) n = pNode._ai; for (n; n<this.aNodes.length; n++) { if (this.aNodes[n].pid == pNode.id) { cn = this.aNodes[n]; cn._p = pNode; cn._ai = n; this.setCS(cn); if (!cn.target && this.config.target) cn.target = this.con +fig.target; if (cn._hc && !cn._io && this.config.useCookies) cn._io = +this.isOpen(cn.id); if (!this.config.folderLinks && cn._hc) cn.url = null; if (this.config.useSelection && cn.id == this.selectedNode + && !this.selectedFound) { cn._is = true; this.selectedNode = n; this.selectedFound = true; } str += this.node(cn, n); if (cn._ls) break; } } return str; }; // Creates the node icon, url and text Tree.prototype.node = function(node, nodeId) { var str = '<div class="treeNode">' + this.indent(node, nodeId); if (this.config.useIcons) { if (!node.icon) node.icon = (this.root.id == node.pid) ? this. +icon.root : ((node._hc) ? this.icon.folder : this.icon.node); if (!node.iconOpen) node.iconOpen = (node._hc) ? this.icon.fol +derOpen : this.icon.node; if (this.root.id == node.pid) { node.icon = this.icon.root; node.iconOpen = this.icon.root; } else // Ronnie add this line, we don't like the root node to d +isplay str += '<img id="i' + this.obj + nodeId + '" src="' + ((no +de._io) ? node.iconOpen : node.icon) + '" alt="" />'; } if (node.url) { str += '<a id="s' + this.obj + nodeId + '" class="' + ((this.c +onfig.useSelection) ? ((node._is ? 'nodeSel' : 'node')) : 'node') + ' +" href="' + node.url + '"'; if (node.title) str += ' title="' + node.title + '"'; if (node.target) str += ' target="' + node.target + '"'; if (this.config.useStatusText) str += ' onmouseover="window.st +atus=\'' + node.name + '\';return true;" onmouseout="window.status=\' +\';return true;" '; if (this.config.useSelection && ((node._hc && this.config.fold +erLinks) || !node._hc)) str += ' onclick="javascript: ' + this.obj + '.s(' + nodeI +d + ');"'; str += '>'; } else if ((!this.config.folderLinks || !node.url) && node._hc && no +de.pid != this.root.id) str += '<a href="javascript: ' + this.obj + '.o(' + nodeId + ' +);" class="node">'; str += node.name; if (node.url || ((!this.config.folderLinks || !node.url) && node._ +hc)) str += '</a>'; str += '</div>'; if (node._hc) { str += '<div id="d' + this.obj + nodeId + '" class="clip" styl +e="display:' + ((this.root.id == node.pid || node._io) ? 'block' : 'n +one') + ';">'; str += this.addNode(node); str += '</div>'; } this.aIndent.pop(); return str; }; // Adds the empty and line icons Tree.prototype.indent = function(node, nodeId) { var str = ''; if (this.root.id != node.pid) { for (var n=0; n<this.aIndent.length; n++) str += '<img src="' + ( (this.aIndent[n] == 1 && this.conf +ig.useLines) ? this.icon.line : this.icon.empty ) + '" alt="" />'; (node._ls) ? this.aIndent.push(0) : this.aIndent.push(1); if (node._hc) { str += '<a href="javascript: ' + this.obj + '.o(' + nodeId + + ');"><img id="j' + this.obj + nodeId + '" src="'; if (!this.config.useLines) str += (node._io) ? this.icon.n +lMinus : this.icon.nlPlus; else str += ( (node._io) ? ((node._ls && this.config.useLi +nes) ? this.icon.minusBottom : this.icon.minus) : ((node._ls && this. +config.useLines) ? this.icon.plusBottom : this.icon.plus ) ); str += '" alt="" /></a>'; } else str += '<img src="' + ( (this.config.useLines) ? ((node +._ls) ? this.icon.joinBottom : this.icon.join ) : this.icon.empty) + +'" alt="" />'; } return str; }; // Checks if a node has any children and if it is the last sibling Tree.prototype.setCS = function(node) { var lastId; for (var n=0; n<this.aNodes.length; n++) { if (this.aNodes[n].pid == node.id) node._hc = true; if (this.aNodes[n].pid == node.pid) lastId = this.aNodes[n].id +; } if (lastId==node.id) node._ls = true; }; // Returns the selected node Tree.prototype.getSelected = function() { var sn = this.getCookie('cs' + this.obj); return (sn) ? sn : null; }; // Highlights the selected node Tree.prototype.s = function(id) { if (!this.config.useSelection) return; var cn = this.aNodes[id]; if (cn._hc && !this.config.folderLinks) return; if (this.selectedNode != id) { if (this.selectedNode || this.selectedNode==0) { eOld = document.getElementById("s" + this.obj + this.selec +tedNode); eOld.className = "node"; } eNew = document.getElementById("s" + this.obj + id); eNew.className = "nodeSel"; this.selectedNode = id; if (this.config.useCookies) this.setCookie('cs' + this.obj, cn +.id); } }; // Toggle Open or close Tree.prototype.o = function(id) { var cn = this.aNodes[id]; this.nodeStatus(!cn._io, id, cn._ls); cn._io = !cn._io; if (this.config.closeSameLevel) this.closeLevel(cn); if (this.config.useCookies) this.updateCookie(); }; // Open or close all nodes Tree.prototype.oAll = function(status) { for (var n=0; n<this.aNodes.length; n++) { if (this.aNodes[n]._hc && this.aNodes[n].pid != this.root.id) +{ this.nodeStatus(status, n, this.aNodes[n]._ls) this.aNodes[n]._io = status; } } if (this.config.useCookies) this.updateCookie(); }; // Opens the tree to a specific node Tree.prototype.openTo = function(nId, bSelect, bFirst) { if (!bFirst) { for (var n=0; n<this.aNodes.length; n++) { if (this.aNodes[n].id == nId) { nId=n; break; } } } var cn=this.aNodes[nId]; if (cn.pid==this.root.id || !cn._p) return; cn._io = true; cn._is = bSelect; if (this.completed && cn._hc) this.nodeStatus(true, cn._ai, cn._ls +); if (this.completed && bSelect) this.s(cn._ai); else if (bSelect) this._sn=cn._ai; this.openTo(cn._p._ai, false, true); }; // Closes all nodes on the same level as certain node Tree.prototype.closeLevel = function(node) { for (var n=0; n<this.aNodes.length; n++) { if (this.aNodes[n].pid == node.pid && this.aNodes[n].id != nod +e.id && this.aNodes[n]._hc) { this.nodeStatus(false, n, this.aNodes[n]._ls); this.aNodes[n]._io = false; this.closeAllChildren(this.aNodes[n]); } } } // Closes all children of a node Tree.prototype.closeAllChildren = function(node) { for (var n=0; n<this.aNodes.length; n++) { if (this.aNodes[n].pid == node.id && this.aNodes[n]._hc) { if (this.aNodes[n]._io) this.nodeStatus(false, n, this.aNo +des[n]._ls); this.aNodes[n]._io = false; this.closeAllChildren(this.aNodes[n]); } } } // Change the status of a node(open or closed) Tree.prototype.nodeStatus = function(status, id, bottom) { eDiv = document.getElementById('d' + this.obj + id); eJoin = document.getElementById('j' + this.obj + id); if (this.config.useIcons) { eIcon = document.getElementById('i' + this.obj + id); eIcon.src = (status) ? this.aNodes[id].iconOpen : this.aNodes[ +id].icon; } eJoin.src = (this.config.useLines)? ((status)?((bottom)?this.icon.minusBottom:this.icon.minus):((botto +m)?this.icon.plusBottom:this.icon.plus)): ((status)?this.icon.nlMinus:this.icon.nlPlus); eDiv.style.display = (status) ? 'block': 'none'; }; // [Cookie] Clears a cookie Tree.prototype.clearCookie = function() { var now = new Date(); var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24); this.setCookie('co'+this.obj, 'cookieValue', yesterday); this.setCookie('cs'+this.obj, 'cookieValue', yesterday); }; // [Cookie] Sets value in a cookie Tree.prototype.setCookie = function(cookieName, cookieValue, expires, +path, domain, secure) { document.cookie = escape(cookieName) + '=' + escape(cookieValue) + (expires ? '; expires=' + expires.toGMTString() : '') + (path ? '; path=' + path : '') + (domain ? '; domain=' + domain : '') + (secure ? '; secure' : ''); }; // [Cookie] Gets a value from a cookie Tree.prototype.getCookie = function(cookieName) { var cookieValue = ''; var posName = document.cookie.indexOf(escape(cookieName) + '='); if (posName != -1) { var posValue = posName + (escape(cookieName) + '=').length; var endPos = document.cookie.indexOf(';', posValue); if (endPos != -1) cookieValue = unescape(document.cookie.subst +ring(posValue, endPos)); else cookieValue = unescape(document.cookie.substring(posValue +)); } return (cookieValue); }; // [Cookie] Returns ids of open nodes as a string Tree.prototype.updateCookie = function() { var str = ''; for (var n=0; n<this.aNodes.length; n++) { if (this.aNodes[n]._io && this.aNodes[n].pid != this.root.id) +{ if (str) str += '.'; str += this.aNodes[n].id; } } this.setCookie('co' + this.obj, str); }; // [Cookie] Checks if a node id is in a cookie Tree.prototype.isOpen = function(id) { var aOpen = this.getCookie('co' + this.obj).split('.'); for (var n=0; n<aOpen.length; n++) if (aOpen[n] == id) return true; return false; }; // If Push and pop is not implemented by the browser if (!Array.prototype.push) { Array.prototype.push = function array_push() { for(var i=0;i<arguments.length;i++) this[this.length]=arguments[i]; return this.length; } }; if (!Array.prototype.pop) { Array.prototype.pop = function array_pop() { lastElement = this[this.length-1]; this.length = Math.max(this.length-1,0); return lastElement; } };
containersTreeForMeteringData.jsp
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <html:html> <head> <title></title> <link rel="stylesheet" type="text/css" href="<%=request.getContext +Path()%>/css/tree.css"> <script type="text/javascript" src="<%=request.getContextPath()%>/ +script/tree.js"></script> <script language="javascript"> function changeContainerForMeteringData(id) { meteringDataContainersTree.openTo(id, false); } function changeMeterProxy(containerId, containerName, proxyMet +erId, proxyMeterName, adasId) { top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.containerId.value = containerId; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.containerName.value = containerName; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.proxyMeterId.value = proxyMeterId; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.proxyMeterName.value = proxyMeterName; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.adasId.value = adasId; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.dataSourceType.value = "MP"; top.RightFrame.contentFrame.viewMeteringDataItems("CLICK_T +REE"); } function changeActiveElement(containerId, containerName, activ +eElementId, activeElementName, activeElementType) { top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.containerId.value = containerId; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.containerName.value = containerName; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.activeElementId.value = activeElementId; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.activeElementName.value = activeElementName; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.activeElementType.value = activeElementType; top.RightFrame.contentFrame.document.meteringDataQueryCond +itionForm.dataSourceType.value = "AE"; top.RightFrame.contentFrame.viewMeteringDataItems("CLICK_T +REE"); } </script> </head> <body bgcolor="#D3DDE3"> <script type="text/javascript"> <!-- meteringDataContainersTree = new Tree("meteringDataContainersT +ree"); meteringDataContainersTree.icon.root = "<%=request.getContextP +ath()%>/images/tree/empty.gif"; meteringDataContainersTree.icon.folder = "<%=request.getContex +tPath()%>/images/tree/folder.gif"; meteringDataContainersTree.icon.folderOpen = "<%=request.getCo +ntextPath()%>/images/tree/folderopen.gif"; meteringDataContainersTree.icon.node = "<%=request.getContextP +ath()%>/images/tree/page.gif"; meteringDataContainersTree.icon.empty = "<%=request.getContext +Path()%>/images/tree/empty.gif"; meteringDataContainersTree.icon.line = "<%=request.getContextP +ath()%>/images/tree/empty.gif"; meteringDataContainersTree.icon.join = "<%=request.getContextP +ath()%>/images/tree/empty.gif"; meteringDataContainersTree.icon.joinBottom = "<%=request.getCo +ntextPath()%>/images/tree/empty.gif"; meteringDataContainersTree.icon.plus = "<%=request.getContextP +ath()%>/images/tree/nolines_plus.gif"; meteringDataContainersTree.icon.plusBottom = "<%=request.getCo +ntextPath()%>/images/tree/nolines_plus.gif"; meteringDataContainersTree.icon.minus = "<%=request.getContext +Path()%>/images/tree/nolines_minus.gif"; meteringDataContainersTree.icon.minusBottom = "<%=request.getC +ontextPath()%>/images/tree/nolines_minus.gif"; meteringDataContainersTree.icon.nlPlus = "<%=request.getContex +tPath()%>/images/tree/nolines_plus.gif"; meteringDataContainersTree.icon.nlMinus = "<%=request.getConte +xtPath()%>/images/tree/nolines_minus.gif"; meteringDataContainersTree.config.useCookies = false; meteringDataContainersTree.add(0, -1, ""); <logic:iterate name="userContainersListForData" id="container" + type="com.landisgyr.nuwa.conweb.dto.dataPublish.ContainerDTO" scope= +"session"> meteringDataContainersTree.add("<bean:write name='containe +r' property='id'/>", "<bean:write name='containe +r' property='parentId'/>", "<bean:write name='containe +r' property='name'/>", "javascript:changeContainer +ForMeteringData('<bean:write name="container" property="id"/>');", "", "", "<%=request.getContextPath( +)%>/images/common/blueDiamond.gif", "<%=request.getContextPath( +)%>/images/common/blueDiamond.gif"); <bean:define id="proxyMetersList" name="container" propert +y="proxyMeters" /> <logic:notEmpty name="proxyMetersList"> <logic:iterate name="proxyMetersList" id="proxyMeter" +type="com.landisgyr.nuwa.conweb.dto.dataPublish.ProxyMeterDTO"> meteringDataContainersTree.add("<bean:write name=' +proxyMeter' property='id'/>", "<bean:write name +='container' property='id'/>", "<bean:write name +='proxyMeter' property='name'/> <bean:message key='global.leftParenth +esis'/><bean:message key='dataPublish.prompt.proxyMeter'/><bean:messa +ge key='global.rightParenthesis'/>", "javascript:chang +eMeterProxy('<bean:write name="container" property="id"/>', '<bean:wr +ite name="container" property="name"/>', '<bean:write name="proxyMete +r" property="id"/>', '<bean:write name="proxyMeter" property="name"/> +', '<bean:write name="proxyMeter" property="adasId"/>');", "", "", "<%=request.getCo +ntextPath()%>/images/common/redDiamond.gif", "<%=request.getCo +ntextPath()%>/images/common/redDiamond.gif"); </logic:iterate> </logic:notEmpty> <bean:define id="activeElementsList" name="container" prop +erty="activeElements" /> <logic:notEmpty name="activeElementsList"> <logic:iterate name="activeElementsList" id="activeEle +ment" type="com.landisgyr.nuwa.conweb.dto.dataPublish.ActiveElementDT +O"> <logic:equal name="activeElement" property="type" +value="auxiliary_values"> meteringDataContainersTree.add("<bean:write na +me='activeElement' property='id'/>", "<bean:write na +me='container' property='id'/>", "<bean:write na +me='activeElement' property='name'/> <bean:message key='global.leftPa +renthesis'/><bean:message key='dataPublish.prompt.auxiliaryValue'/><b +ean:message key='global.rightParenthesis'/>", "javascript:cha +ngeActiveElement('<bean:write name="container" property="id"/>', '<be +an:write name="container" property="name"/>', '<bean:write name="acti +veElement" property="id"/>', '<bean:write name="activeElement" proper +ty="name"/>', '<bean:write name="activeElement" property="type"/>');" +, "", "", "<%=request.get +ContextPath()%>/images/common/redDiamond.gif", "<%=request.get +ContextPath()%>/images/common/redDiamond.gif"); </logic:equal> <logic:equal name="activeElement" property="type" +value="persistent_values"> meteringDataContainersTree.add("<bean:write na +me='activeElement' property='id'/>", "<bean:write na +me='container' property='id'/>", "<bean:write na +me='activeElement' property='name'/> <bean:message key='global.leftPa +renthesis'/><bean:message key='dataPublish.prompt.persistentValue'/>< +bean:message key='global.rightParenthesis'/>", "javascript:cha +ngeActiveElement('<bean:write name="container" property="id"/>', '<be +an:write name="container" property="name"/>', '<bean:write name="acti +veElement" property="id"/>', '<bean:write name="activeElement" proper +ty="name"/>', '<bean:write name="activeElement" property="type"/>');" +, "", "", "<%=request.get +ContextPath()%>/images/common/redDiamond.gif", "<%=request.get +ContextPath()%>/images/common/redDiamond.gif"); </logic:equal> </logic:iterate> </logic:notEmpty> </logic:iterate> document.write(meteringDataContainersTree); // meteringDataContainersTree.openAll(); //--> </script> </body> </html:html>

When customer click start button, containersTreeForMeteringData.jsp, which is create a tree object and add relative nodes to it, will be invoked. Recently, customers complained that IE often throw a warning box said that 'A script on this page is causing Internet Explore to run slowly..... do you want to abort the script?' when nodes is upto 1000.

After studying and test the code above carefully, I found the choke point is on node function and document.write. So my question is which way to tune is the best and fastest? what I think straightforward(seem stupid?) is gernating the static html in server side other than client browser. I hope monks could lighten my dull brain!

TIA


I am trying to improve my English skills, if you see a mistake please feel free to reply or /msg me a correction

Replies are listed 'Best First'.
Re: Web page display slowly!! [OT]
by moritz (Cardinal) on Nov 25, 2008 at 13:06 UTC
    After studying and test the code above carefully, I found the choke point is on node function and document.write.

    I find that a bit surprising, so I'd like to know how you came to that conclusion. Did you use some kind of javascript profiler? If yes, which?

Re: Web page display slowly!! [OT]
by Anonymous Monk on Nov 26, 2008 at 01:19 UTC
    JSP? Your chokepoint is in the java... psych