瀏覽代碼

ECLPlayground: Page loaded results.

On demand page loaded results and on demand page loaded rows.
Added some links from EclWatch Workunit Page.
Strip header when displaying a specific Wuid.
Better default row count and column widths.
General code tidy and consistent javascript.
Enable result viewing while WU is running.
Enable selecting default tab to display.

Signed-off-by: Gordon Smith <gordon.smith@lexisnexis.com>
Gordon Smith 13 年之前
父節點
當前提交
8875dccf62

+ 9 - 3
esp/eclwatch/ws_XSLT/wuidcommon.xslt

@@ -43,9 +43,11 @@
                   <xsl:value-of select="$wuid"/>
                 </xsl:when>
                 <xsl:otherwise>
-                  <a href="/esp/iframe?esp_iframe_title=ECL Workunit XML - {$wuid}&amp;inner=/WsWorkunits/WUFile%3fWuid%3d{$wuid}%26Type%3dXML" >
-                    <xsl:value-of select="$wuid"/>
-                  </a>
+                  <xsl:value-of select="$wuid"/>
+                  &nbsp;
+                  <a href="/esp/iframe?esp_iframe_title=ECL Workunit XML - {$wuid}&amp;inner=/WsWorkunits/WUFile%3fWuid%3d{$wuid}%26Type%3dXML" >XML</a>
+                  &nbsp;
+                  <a href="/esp/iframe?esp_iframe_title=ECL Playground - {$wuid}&amp;inner=/esp/files/ECLPlayground.htm%3fWuid%3d{$wuid}" >ECL Playground</a>
                 </xsl:otherwise>
               </xsl:choose>
             </td>
@@ -397,6 +399,8 @@
                 <A href="javascript:void(0)" onclick="toggleElement('Results');" id="explinkresults" class="wusectionexpand">
                   Results: (<xsl:value-of select="ResultCount"/>)
                 </A>
+                &nbsp;-&nbsp;
+                <a href="/esp/iframe?esp_iframe_title=ECL Playground (Results) - {$wuid}&amp;inner=/esp/files/ECLPlaygroundResults.htm%3fWuid%3d{$wuid}" >Show</a>
               </div>
             </div>
             <div id="Results" class="wusectioncontent">
@@ -1042,6 +1046,8 @@
           </xsl:otherwise>
         </xsl:choose>
         <xsl:if test="number(IsSupplied)"> supplied</xsl:if>
+        &nbsp;-&nbsp;
+        <a href="javascript:void(0);" onclick="getLink(document.getElementById('ECL_Result_{position()}'), '/esp/files/ECLPlaygroundResults.htm?Wuid={$wuid}&amp;Sequence={Link}');return false;">Show</a>
       </td>
      <xsl:choose>
        <xsl:when test="number(ShowFileContent) and string-length(Link)">

+ 1 - 0
esp/files/CMakeLists.txt

@@ -53,6 +53,7 @@ FOREACH( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/useradd.html
     ${CMAKE_CURRENT_SOURCE_DIR}/groupadd.html
     ${CMAKE_CURRENT_SOURCE_DIR}/ECLPlayground.htm
+    ${CMAKE_CURRENT_SOURCE_DIR}/ECLPlaygroundResults.htm
     ${CMAKE_CURRENT_SOURCE_DIR}/ECLPlayground.css
     ${CMAKE_CURRENT_SOURCE_DIR}/ECLPlayground.js
 )

+ 7 - 11
esp/files/ECLPlayground.htm

@@ -20,7 +20,7 @@
 <html>
 <head>
     <meta charset="utf-8">
-    <title>Demo: Graph Control</title>
+    <title>HPCC: ECL Playground</title>
     <link href="CodeMirror2/lib/codemirror.css" rel="stylesheet">
     <script src="CodeMirror2/lib/codemirror.js"></script>
     <script src="CodeMirror2/mode/ecl/ecl.js"></script>
@@ -28,6 +28,7 @@
     <link href="ECLPlayground.css" media="screen" rel="stylesheet">
     <link href="dijit/themes/claro/claro.css" media="screen" rel="stylesheet">
     <link href="dojox/grid/resources/Grid.css" rel="stylesheet">
+    <link href="dojox/grid/enhanced/resources/claro/EnhancedGrid.css" rel="stylesheet">
     <link href="dojox/grid/resources/claroGrid.css" rel="stylesheet">
     <!-- load dojo and provide config via dojoConfig global -->
     <script>
@@ -55,11 +56,7 @@
     </script>
     <script src="dojo/dojo.js"></script>
     <script>
-        require(["dijit/layout/BorderContainer", "dijit/layout/ContentPane", "dijit/layout/TabContainer", "dijit/layout/AccordionContainer", "dijit/layout/AccordionPane",
-	"dojo/domReady!"]);
-    </script>
-    <script>
-        require(["main/ECLPlayground", "dojo/domReady!"], function (app) {
+        require(["main/ECLPlayground", "dijit/layout/BorderContainer", "dijit/layout/ContentPane", "dijit/layout/TabContainer", "dojo/domReady!"], function (app) {
             app.init();
         });
     </script>
@@ -76,9 +73,8 @@
             <div id="title" style="display: inline-block; vertical-align: middle">
                 ECL Playground</div>
             <div style="float: right; display: inline-block; vertical-align: middle">
-                <label for="sampleSelect">
-                    Sample:</label>
-                <div id="sampleSelect">
+                <label id="sampleSelectLabel" for="sampleSelect">Sample:</label>
+                <div id="sampleSelect" style="overflow: visible !important;">
                 </div>
             </div>
         </div>
@@ -107,10 +103,10 @@ somePeople;
         </div>
         <div id="rightPane" class="edgePanel" data-dojo-props="minSize:120, region: 'right', splitter:true"
             data-dojo-type="dijit.layout.TabContainer" style="width: 240px;">
-            <div id="graphs" style="width: 100%; height: 100%">
+            <div id="graphs" style="width: 100%; height: 100%; overflow: hidden">
             </div>
         </div>
-        <div id="bottomPane" class="edgePanel" data-dojo-props="minSize:120, region: 'bottom', splitter:true, tabPosition: 'bottom'"
+        <div id="resultsPane" class="edgePanel" data-dojo-props="minSize:120, region: 'bottom', splitter:true, tabPosition: 'bottom'"
             data-dojo-type="dijit.layout.TabContainer" style="height: 240px">
         </div>
         <div id="submitPane" class="edgePanel" data-dojo-props="region: 'bottom'" data-dojo-type="dijit.layout.ContentPane">

+ 90 - 62
esp/files/ECLPlayground.js

@@ -15,39 +15,74 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ############################################################################## */
 define([
-	"dojo/_base/config",
 	"dojo/_base/fx",
 	"dojo/_base/window",
 	"dojo/dom",
 	"dojo/dom-style",
 	"dojo/dom-geometry",
 	"dojo/on",
+	"dojo/ready",
+	"dijit/registry",
 	"hpcc/EclEditorControl",
 	"hpcc/GraphControl",
 	"hpcc/ResultsControl",
 	"hpcc/SampleSelectControl",
 	"hpcc/ESPWorkunit"
-], function (baseConfig, baseFx, baseWindow, dom, domStyle, domGeometry, on, EclEditor, GraphControl, ResultsControl, Select, Workunit) {
-	var editorControl = null,
+], function (fx, baseWindow, dom, domStyle, domGeometry, on, ready, registry, EclEditor, GraphControl, ResultsControl, Select, Workunit) {
+	var		wu = null,
+			editorControl = null,
 			graphControl = null,
 			resultsControl = null,
 			sampleSelectControl = null,
 
 			initUi = function () {
+				var wuid = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search[0] === "?" ? 1 : 0)))["Wuid"];
 				on(dom.byId("submitBtn"), "click", doSubmit);
-				initSamples();
+
+				if (wuid) {
+					dojo.destroy("topPane");
+					registry.byId("appLayout").resize();
+				} else {
+					initSamples();
+				}
 				initEditor();
 				initResultsControl();
+
 				//  ActiveX will flicker if created before initial layout
 				setTimeout(function () {
 					initGraph();
-					var wuid = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search[0] === "?" ? 1 : 0)))["wuid"];
 					if (wuid) {
-						doLoad(wuid);
+						wu = new Workunit({
+							wuid: wuid
+						});
+						wu.fetchText(function (text) {
+							editorControl.setText(text);
+						});
+						wu.monitor(monitorEclPlayground);
 					}
 				}, 1);
 			},
 
+			initUiResults = function () {
+				var wuid = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search[0] === "?" ? 1 : 0)))["Wuid"];
+				var sequence = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search[0] === "?" ? 1 : 0)))["Sequence"];
+				initResultsControl(sequence);
+				if (wuid) {
+					wu = new Workunit({
+						wuid: wuid
+					});
+					var monitorCount = 4;
+					wu.monitor(function () {
+						dom.byId("loadingMessage").innerHTML = wu.state;
+						if (wu.isComplete() || ++monitorCount % 5 == 0) {
+							wu.getInfo({
+								onGetResults: displayResults
+							});
+						}
+					});
+				}
+			},
+
 			initSamples = function () {
 				sampleSelectControl = new Select({
 					id: "sampleSelect",
@@ -90,16 +125,20 @@ define([
 								var startPos = props.definition.indexOf("(");
 								var endPos = props.definition.lastIndexOf(")");
 								var pos = props.definition.slice(startPos + 1, endPos).split(",");
-								editorControl.highlightLine(parseInt(pos[0], 10));
+								var lineNo = parseInt(pos[0], 10);
+								editorControl.highlightLine(lineNo);
+								editorControl.setCursor(lineNo, 0);
 							}
 						}
+
 					}
 				}, dom.byId("graphs"));
 			},
 
-			initResultsControl = function () {
-				var _resultsControl = resultsControl = new ResultsControl({
-					resultsSheetID: "bottomPane",
+			initResultsControl = function (sequence) {
+				resultsControl = new ResultsControl({
+					resultsSheetID: "resultsPane",
+					sequence: sequence,
 					onErrorClick: function (line, col) {
 						editorControl.setCursor(line, col);
 					}
@@ -113,42 +152,39 @@ define([
 				resultsControl.clear();
 			},
 
-			doLoad = function (wuid) {
-				wu = new Workunit({
-					wuid: wuid,
+			monitorEclPlayground = function () {
+				dom.byId("status").innerHTML = wu.state;
+				if (wu.isComplete()) {
+					wu.getInfo({
+						onGetExceptions: displayExceptions,
+						onGetResults: displayResults,
+						onGetGraphs: displayGraphs
+					});
+				}
+			},
 
-					onMonitor: function () {
-						dom.byId("status").innerHTML = wu.state;
-						if (wu.isComplete())
-							wu.getInfo();
-					},
-					onGetText: function () {
-						editorControl.setText(wu.getText());
-					},
-					onGetInfo: function () {
-						if (wu.errors.length) {
-							editorControl.setErrors(wu.errors);
-							resultsControl.addExceptionTab(wu.errors);
-						}
-						wu.getResults();
-						wu.getGraphs();
-					},
-					onGetGraph: function (idx) {
-						graphControl.loadXGMML(wu.graphs[idx].xgmml, true);
-					},
-					onGetResult: function (idx) {
-						resultsControl.addDatasetTab(wu.results[idx].dataset);
-					}
-				});
-				wu.getText();
-				wu.monitor();
+			displayExceptions = function (exceptions) {
+				if (exceptions.length) {
+					editorControl.setErrors(wu.exceptions);
+					resultsControl.addExceptionTab(wu.exceptions);
+				}
+			},
+
+			displayResults = function (results) {
+				resultsControl.refreshResults(wu);
+			},
+
+			displayGraphs = function (graphs) {
+				for (var i = 0; i < graphs.length; ++i) {
+					wu.fetchGraphXgmml(i, function (xgmml) {
+						graphControl.loadXGMML(xgmml, true);
+					});
+				}
 			},
 
 			doSubmit = function (evt) {
 				resetPage();
 				wu = new Workunit({
-					wuid: "",
-
 					onCreate: function () {
 						wu.update(editorControl.getText());
 					},
@@ -156,25 +192,7 @@ define([
 						wu.submit("hthor");
 					},
 					onSubmit: function () {
-					},
-					onMonitor: function () {
-						dom.byId("status").innerHTML = wu.state;
-						if (wu.isComplete())
-							wu.getInfo();
-					},
-					onGetInfo: function () {
-						if (wu.errors.length) {
-							editorControl.setErrors(wu.errors);
-							resultsControl.addExceptionTab(wu.errors);
-						}
-						wu.getResults();
-						wu.getGraphs();
-					},
-					onGetGraph: function (idx) {
-						graphControl.loadXGMML(wu.graphs[idx].xgmml, true);
-					},
-					onGetResult: function (idx) {
-						resultsControl.addDatasetTab(wu.results[idx].dataset);
+						wu.monitor(monitorEclPlayground);
 					}
 				});
 			},
@@ -192,8 +210,9 @@ define([
 			},
 
 			endLoading = function () {
-				baseFx.fadeOut({
+				fx.fadeOut({
 					node: dom.byId("loadingOverlay"),
+					duration: 175,
 					onEnd: function (node) {
 						domStyle.set(node, "display", "none");
 					}
@@ -203,8 +222,17 @@ define([
 	return {
 		init: function () {
 			startLoading();
-			initUi();
-			endLoading();
+			ready(function () {
+				initUi();
+				endLoading();
+			});
+		},
+		initResults: function () {
+			startLoading();
+			ready(function () {
+				initUiResults();
+				endLoading();
+			});
 		}
 	};
 });

+ 76 - 0
esp/files/ECLPlaygroundResults.htm

@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<!--
+##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+##############################################################################
+-->
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>HPCC: ECL Playground (Results)</title>
+    <link href="CodeMirror2/lib/codemirror.css" rel="stylesheet">
+    <script src="CodeMirror2/lib/codemirror.js"></script>
+    <script src="CodeMirror2/mode/ecl/ecl.js"></script>
+    <link href="css/ecl.css" rel="stylesheet">
+    <link href="ECLPlayground.css" media="screen" rel="stylesheet">
+    <link href="dijit/themes/claro/claro.css" media="screen" rel="stylesheet">
+    <link href="dojox/grid/resources/Grid.css" rel="stylesheet">
+    <link href="dojox/grid/enhanced/resources/claro/EnhancedGrid.css" rel="stylesheet">
+    <link href="dojox/grid/resources/claroGrid.css" rel="stylesheet">
+    <!-- load dojo and provide config via dojoConfig global -->
+    <script>
+        var dojoConfig = (function () {
+            var base = location.href.split("/");
+            var developerMode = base[0] == "file:"; //  If URL is local then we are in web developer mode!
+            base.pop();
+            base = base.join("/");
+
+            return {
+                async: true,
+                isDebug: developerMode,
+                parseOnLoad: true,
+                urlBase: base,
+                serverIP: developerMode ? "192.168.2.68" : "", //IP of any ESP server
+                packages: [{
+                    name: "main",
+                    location: base + "/"
+                }, {
+                    name: "hpcc",
+                    location: base + "/scripts/"
+                }]
+            };
+        })();
+    </script>
+    <script src="dojo/dojo.js"></script>
+    <script>
+        require(["main/ECLPlayground", "dijit/layout/BorderContainer", "dijit/layout/ContentPane", "dijit/layout/TabContainer", "dojo/domReady!"], function (app) {
+            app.initResults();
+        });
+    </script>
+</head>
+<body class="claro">
+    <!-- overlay -->
+    <div id="loadingOverlay" class="pageOverlay">
+        <div id="loadingMessage" class="loadingMessage">
+            Loading...</div>
+    </div>
+    <!-- application -->
+    <div id="appLayout" class="demoLayout" data-dojo-props="design: 'headline'" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="resultsPane" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.TabContainer">
+        </div>
+    </div>
+</body>
+</html>

+ 2 - 0
esp/files/scripts/CMakeLists.txt

@@ -46,6 +46,8 @@ FOREACH( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/EclEditorControl.js
     ${CMAKE_CURRENT_SOURCE_DIR}/ESPBase.js
     ${CMAKE_CURRENT_SOURCE_DIR}/ESPWorkunit.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/ESPResult.js
+    ${CMAKE_CURRENT_SOURCE_DIR}/WUResultStore.js
     ${CMAKE_CURRENT_SOURCE_DIR}/GraphControl.js
     ${CMAKE_CURRENT_SOURCE_DIR}/ResultsControl.js
     ${CMAKE_CURRENT_SOURCE_DIR}/SampleSelectControl.js

+ 46 - 66
esp/files/scripts/ESPBase.js

@@ -15,9 +15,9 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ############################################################################## */
 define([
-	"dojo/_base/config",
-	"dojo/_base/declare"
-], function (baseConfig, declare) {
+	"dojo/_base/declare",
+	"dojo/_base/config"
+], function (declare, config) {
 	return declare(null, {
 
 		constructor: function (args) {
@@ -28,7 +28,7 @@ define([
 			var value = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search[0] === "?" ? 1 : 0)))[key];
 			if (value)
 				return value;
-			return baseConfig[key];
+			return config[key];
 		},
 
 		getBaseURL: function () {
@@ -38,79 +38,59 @@ define([
 			return "/WsWorkunits";
 		},
 
-		parseKeyValue: function (xmlDom, nodeLabel) {
-			var items = xmlDom.getElementsByTagName(nodeLabel);
-			if (items.length && items[0].childNodes.length) {
-				return items[0].childNodes[0].nodeValue;
+		getValue: function (domXml, tagName, knownObjectArrays) {
+			var retVal = this.getValues(domXml, tagName, knownObjectArrays);
+			if (retVal.length == 0) {
+				return null;
+			} else if (retVal.length != 1) {
+				alert("Invalid length:  " + retVal.length);
 			}
-			return "";
+			return retVal[0];
 		},
 
-		parseKeyChildren: function (xmlDom, nodeLabel) {
-			var items = xmlDom.getElementsByTagName(nodeLabel);
-			if (items.length && items[0].childNodes.length) {
-				return items[0].childNodes;
-			}
-			return null;
-		},
-
-		parseRows: function (xmlDom, nodeLabel) {
-			var rows = [];
-			var items = xmlDom.getElementsByTagName(nodeLabel);
+		getValues: function (domXml, tagName, knownObjectArrays) {
+			var retVal = [];
+			var items = domXml.getElementsByTagName(tagName);
+			var parentNode = items.length ? items[0].parentNode : null; //  Prevent <Dataset><row><field><row> scenario
 			for (var i = 0; i < items.length; ++i) {
-				var item = items[i];
-				var cols = {};
-				for (var c = 0; c < item.childNodes.length; ++c) {
-					colNode = item.childNodes[c];
-					if (colNode.childNodes.length && colNode.childNodes[0].nodeValue) {
-						cols[colNode.nodeName] = colNode.childNodes[0].nodeValue;
-					} else {
-						cols[colNode.nodeName] = "";
-					}
-				}
-				rows.push(cols);
+				if (items[i].parentNode == parentNode)
+					retVal.push(this.flattenXml(items[i], knownObjectArrays));
 			}
-			return rows;
-		},
-
-		//  <XXX><YYY/><YYY/><YYY/><YYY/></XXX>
-		parseDataset: function (xmlDom, _name, nodeLabel) {
-			var retVal = {};
-			var retValRows = this.parseRows(xmlDom, nodeLabel);
-			var retValHeader = [];
-			if (retValRows.length) {
-				for (var key in retValRows[0]) {
-					retValHeader.push(key)
-				}
-			}
-			retVal = {
-				name: _name,
-				header: retValHeader,
-				rows: retValRows
-			};
 			return retVal;
-
 		},
 
-		parseDatasets: function (xmlDom, nodeLabel, innerNodeLabel) {
-			var retVal = [];
-			var datasets = xmlDom.getElementsByTagName(nodeLabel);
-			for (var d = 0; d < datasets.length; ++d) {
-				var dataset = datasets[d];
-				var retValRows = this.parseRows(dataset, innerNodeLabel);
-				var retValHeader = [];
-				if (retValRows.length) {
-					for (var key in retValRows[0]) {
-						retValHeader.push(key)
+		flattenXml: function (domXml, knownObjectArrays) {
+			var retValArr = [];
+			var retValStr = "";
+			var retVal = {};
+			for (var i = 0; i < domXml.childNodes.length; ++i) {
+				var childNode = domXml.childNodes[i];
+				if (childNode.childNodes) {
+					if (childNode.nodeName && knownObjectArrays != null && dojo.indexOf(knownObjectArrays, childNode.nodeName) >= 0) {
+						retValArr.push(this.flattenXml(childNode, knownObjectArrays));
+					} else if (childNode.nodeName == "#text") {
+						retValStr += childNode.nodeValue;
+					} else if (childNode.childNodes.length == 0) {
+						retVal[childNode.nodeName] = null;
+					} else {
+						var value = this.flattenXml(childNode, knownObjectArrays);
+						if (retVal[childNode.nodeName] == null) {
+							retVal[childNode.nodeName] = value;
+						} else if (dojo.isArray(retVal[childNode.nodeName])) {
+							retVal[childNode.nodeName].push(value);
+						} else if (dojo.isObject(retVal[childNode.nodeName])) {
+							var tmp = retVal[childNode.nodeName];
+							retVal[childNode.nodeName] = [];
+							retVal[childNode.nodeName].push(tmp);
+							retVal[childNode.nodeName].push(value);
+						}
 					}
 				}
-
-				retVal.push({
-					name: dataset.getAttribute("name"),
-					header: retValHeader,
-					rows: retValRows
-				});
 			}
+			if (retValArr.length)
+				return retValArr;
+			else if (retValStr.length)
+				return retValStr;
 			return retVal;
 		}
 	});

+ 93 - 0
esp/files/scripts/ESPResult.js

@@ -0,0 +1,93 @@
+/*##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+define([
+	"dojo/_base/declare",
+	"dojo/data/ObjectStore",
+	"hpcc/WUResultStore",
+	"hpcc/ESPBase"
+], function (declare, ObjectStore, WUResultStore, ESPBase) {
+	return declare(ESPBase, {
+		store: null,
+		Total: "-1",
+
+		constructor: function (args) {
+			declare.safeMixin(this, args);
+			this.store = new WUResultStore({
+				wuid: this.wuid,
+				sequence: this.Sequence,
+				isComplete: this.isComplete()
+			});
+		},
+
+		getName: function () {
+			return this.Name;
+		},
+
+		isComplete: function () {
+			return this.Total != "-1";
+		},
+
+		getStructure: function () {
+			var retVal = [];
+			retVal.push({
+				name: "##",
+				field: this.store.idProperty,
+				width: "40px"
+			});
+			for (var i = 0; i < this.ECLSchemas.length; ++i) {
+				retVal.push({
+					name: this.ECLSchemas[i].ColumnName,
+					field: this.ECLSchemas[i].ColumnName,
+					width: this.extractWidth(this.ECLSchemas[i].ColumnType, this.ECLSchemas[i].ColumnName)
+				});
+			}
+			return retVal;
+		},
+
+		extractWidth: function (type, name) {
+			var numStr = "0123456789";
+			var retVal = -1;
+			var i = type.length;
+			while (i >= 0) {
+				if (numStr.indexOf(type.charAt(--i)) == -1)
+					break;
+			}
+			if (i > 0)
+				retVal = parseInt(type.substring(i + 1, type.length));
+
+			if (retVal < name.length)
+				retVal = name.length;
+
+			return Math.round(retVal * 2 / 3);
+		},
+
+		getObjectStore: function () {
+			return ObjectStore({
+				objectStore: this.store
+			});
+		},
+
+		getECLRecord: function () {
+			var retVal = "RECORD\n";
+			for (var i = 0; i < this.ECLSchemas.length; ++i) {
+				retVal += "\t" + this.ECLSchemas[i].ColumnType + "\t" + this.ECLSchemas[i].ColumnName + ";\n";
+			}
+			retVal += "END;\n";
+			return retVal;
+		}
+	});
+});

+ 94 - 123
esp/files/scripts/ESPWorkunit.js

@@ -15,11 +15,12 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ############################################################################## */
 define([
-	"dojo/_base/config",
 	"dojo/_base/declare",
+	"dojo/_base/lang",
 	"dojo/_base/xhr",
+	"hpcc/ESPResult",
 	"hpcc/ESPBase"
-], function (baseConfig, declare, baseXhr, ESPBase) {
+], function (declare, lang, xhr, ESPResult, ESPBase) {
 	return declare(ESPBase, {
 		wuid: "",
 
@@ -31,11 +32,9 @@ define([
 		resultCount: 0,
 		results: [],
 
-		graphNameIndex: [],
 		graphs: [],
 
 		exceptions: [],
-		errors: [],
 		timers: [],
 
 		onCreate: function () {
@@ -44,16 +43,6 @@ define([
 		},
 		onSubmit: function () {
 		},
-		onMonitor: function () {
-		},
-		onComplete: function () {
-		},
-		onGetText: function () {
-		},
-		onGetInfo: function () {
-		},
-		onGetGraph: function (name) {
-		},
 		constructor: function (args) {
 			declare.safeMixin(this, args);
 
@@ -75,23 +64,27 @@ define([
 			}
 			return false;
 		},
-		monitor: function () {
+		monitor: function (callback) {
 			var request = {};
 			request['Wuid'] = this.wuid;
 			request['rawxml_'] = "1";
 
 			var context = this;
-			baseXhr.post({
+			xhr.post({
 				url: this.getBaseURL() + "/WUQuery",
 				handleAs: "xml",
 				content: request,
 				load: function (xmlDom) {
-					context.stateID = context.parseKeyValue(xmlDom, "StateID");
-					context.state = context.parseKeyValue(xmlDom, "State");
-					context.onMonitor();
+					var workunit = context.getValue(xmlDom, "ECLWorkunit");
+					context.stateID = workunit.StateID;
+					context.state = workunit.State;
+					if (callback) {
+						callback(context);
+					}
+
 					if (!context.isComplete()) {
 						setTimeout(function () {
-							context.monitor();
+							context.monitor(callback);
 						}, 200);
 					}
 				},
@@ -100,35 +93,34 @@ define([
 				}
 			});
 		},
-		create: function (ecl, _sync) {
+		create: function (ecl) {
 			var request = {};
 			request['rawxml_'] = "1";
 
 			var context = this;
-			baseXhr.post({
+			xhr.post({
 				url: this.getBaseURL() + "/WUCreate",
 				handleAs: "xml",
 				content: request,
 				load: function (xmlDom) {
-					context.wuid = context.parseKeyValue(xmlDom, "Wuid");
+					context.wuid = context.getValue(xmlDom, "Wuid");
 					context.onCreate();
 				},
 				error: function () {
 				}
 			});
 		},
-		update: function (ecl, _sync) {
+		update: function (ecl) {
 			var request = {};
 			request['Wuid'] = this.wuid;
 			request['QueryText'] = ecl;
 			request['rawxml_'] = "1";
 
 			var context = this;
-			baseXhr.post({
+			xhr.post({
 				url: this.getBaseURL() + "/WUUpdate",
 				handleAs: "xml",
 				content: request,
-				sync: _sync,
 				load: function (xmlDom) {
 					context.onUpdate();
 				},
@@ -136,147 +128,126 @@ define([
 				}
 			});
 		},
-		submit: function (target, _sync) {
+		submit: function (target) {
 			var request = {};
 			request['Wuid'] = this.wuid;
 			request['Cluster'] = target;
 			request['rawxml_'] = "1";
 
 			var context = this;
-			baseXhr.post({
+			xhr.post({
 				url: this.getBaseURL() + "/WUSubmit",
 				handleAs: "xml",
 				content: request,
-				sync: _sync,
 				load: function (xmlDom) {
 					context.onSubmit();
-					context.monitor();
 				},
 				error: function () {
 				}
 			});
 		},
-		getInfoEx: function (_sync, func, IncludeExceptions, IncludeGraphs, IncludeSourceFiles, IncludeResults, IncludeResultsViewNames, IncludeVariables, IncludeTimers, IncludeDebugValues, IncludeApplicationValues, IncludeWorkflows, IncludeResultSchemas) {
-			var request = {};
-			request['Wuid'] = this.wuid;
-			request['IncludeExceptions'] = IncludeExceptions;
-			request['IncludeGraphs'] = IncludeGraphs;
-			request['IncludeSourceFiles'] = IncludeSourceFiles;
-			request['IncludeResults'] = IncludeResults;
-			request['IncludeResultsViewNames'] = IncludeResultsViewNames;
-			request['IncludeVariables'] = IncludeVariables;
-			request['IncludeTimers'] = IncludeTimers;
-			request['IncludeDebugValues'] = IncludeDebugValues;
-			request['IncludeApplicationValues'] = IncludeApplicationValues;
-			request['IncludeWorkflows'] = IncludeWorkflows;
-			request['SuppressResultSchemas'] = !IncludeResultSchemas;
-			request['rawxml_'] = "1";
+		getInfo: function (args) {
+			var request = {
+				Wuid: this.wuid,
+				IncludeExceptions: args.onGetExceptions ? true : false,
+				IncludeGraphs: args.onGetGraphs ? true : false,
+				IncludeSourceFiles: false,
+				IncludeResults: args.onGetResults ? true : false,
+				IncludeResultsViewNames: false,
+				IncludeVariables: false,
+				IncludeTimers: args.onGetTimers ? true : false,
+				IncludeDebugValues: false,
+				IncludeApplicationValues: false,
+				IncludeWorkflows: false,
+				SuppressResultSchemas: args.onGetResults ? false : true,
+				rawxml_: true
+			};
 
-			baseXhr.post({
+			var context = this;
+			xhr.post({
 				url: this.getBaseURL() + "/WUInfo",
 				handleAs: "xml",
 				content: request,
-				sync: _sync,
-				load: func,
+				load: function (xmlDom) {
+					var workunit = context.getValue(xmlDom, "Workunit", ["ECLException", "ECLResult", "ECLGraph", "ECLTimer", "ECLSchemaItem"]);
+					if (workunit.Query.Text && args.onGetText) {
+						context.text = workunit.Query.Text;
+						args.onGetText(context.text);
+					}
+					if (workunit.Exceptions && args.onGetExceptions) {
+						context.exceptions = workunit.Exceptions;
+						args.onGetExceptions(context.exceptions);
+					}
+					if (workunit.Results && args.onGetResults) {
+						context.results = [];
+						var results = workunit.Results;
+						for (var i = 0; i < results.length; ++i) {
+							context.results.push(new ESPResult(lang.mixin({ wuid: context.wuid }, results[i])));
+						}
+						args.onGetResults(context.results);
+					}
+					if (workunit.Timers && args.onGetTimers) {
+						context.timers = workunit.Timers;
+						args.onGetTimers(context.timers);
+					}
+					if (workunit.Graphs && args.onGetGraphs) {
+						context.graphs = workunit.Graphs;
+						args.onGetGraphs(context.graphs)
+					}
+					if (args.onGetAll) {
+						args.onGetAll(workunit);
+					}
+				},
 				error: function () {
 				}
 			});
 		},
-		getText: function () {
-			var context = this;
-			this.getInfoEx(false, function (xmlDom) {
-				context.text = context.parseKeyValue(xmlDom, "Text");
-				context.onGetText();
+		fetchText: function (onFetchText) {
+			if (this.text) {
+				onFetchText(this.text);
+				return;
+			}
+
+			this.getInfo({
+				onGetText: onFetchText
 			});
-			return wu.text;
-		},
-		getInfo: function (_sync) {
-			var context = this;
-			this.getInfoEx(_sync, function (xmlDom) {
-				context.exceptions = context.parseRows(xmlDom, "Exception");
-				context.errors = context.parseRows(xmlDom, "ECLException");
-				context.timers = context.parseRows(xmlDom, "ECLTimer");
-				context.graphs = context.parseRows(xmlDom, "ECLGraph");
-				for (var i = 0; i < context.graphs.length; ++i) {
-					context.graphNameIndex[context.graphs[i].Name] = i;
-				}
-				context.results = context.parseRows(xmlDom, "ECLResult");
-				context.onGetInfo();
-			}, true, true, false, true, false, false, true, false, false, false, false);
 		},
-		getGraphs: function () {
-			for (var i = 0; i < this.graphs.length; ++i) {
-				this.getGraph(i);
+		fetchResults: function (onFetchResults) {
+			if (this.results && this.results.length) {
+				onFetchResults(this.results);
+				return;
 			}
-		},
-		getGraph: function (idx, _sync) {
-			var request = {};
-			request['Wuid'] = this.wuid;
-			request['GraphName'] = this.graphs[idx].Name;
-			request['rawxml_'] = "1";
 
-			var context = this;
-			baseXhr.post({
-				url: this.getBaseURL() + "/WUGetGraph",
-				handleAs: "xml",
-				content: request,
-				sync: _sync,
-				load: function (xmlDom) {
-					context.graphs[idx].xgmml = context.parseKeyValue(xmlDom, "Graph");
-					context.onGetGraph(idx);
-				},
-				error: function () {
-				}
+			this.getInfo({
+				onGetResults: onFetchResults
 			});
 		},
-		getResults: function () {
-			for (var i = 0; i < this.results.length; ++i) {
-				this.getResult(i);
+		fetchGraphs: function (onFetchGraphs) {
+			if (this.graphs && this.graphs.length) {
+				onFetchGraphs(this.graphs);
+				return;
 			}
-		},
-		getResult: function (idx, _sync) {
-			var request = {};
-			request['Wuid'] = this.wuid;
-			request['Sequence'] = this.results[idx].Sequence;
-			request['Start'] = 0;
-			request['Count'] = 999;
-			request['rawxml_'] = "1";
 
-			var context = this;
-			baseXhr.post({
-				url: this.getBaseURL() + "/WUResult",
-				handleAs: "xml",
-				content: request,
-				sync: _sync,
-				load: function (xmlDom) {
-					var name = context.parseKeyValue(xmlDom, "Name");
-					var resultDom = xmlDom.getElementsByTagName("Result");
-					if (resultDom.length) {
-						context.results[idx].dataset = context.parseDataset(resultDom[0], name, "Row");
-					}
-					context.onGetResult(idx);
-				},
-				error: function () {
-				}
+			this.getInfo({
+				onGetGraphs: onFetchGraphs
 			});
 		},
-		getInfoFast: function (_sync) {
+		fetchGraphXgmml: function (idx, onFetchGraphXgmml) {
 			var request = {};
 			request['Wuid'] = this.wuid;
+			request['GraphName'] = this.graphs[idx].Name;
 			request['rawxml_'] = "1";
 
 			var context = this;
-			baseXhr.post({
-				url: this.getBaseURL() + "/WUQuery",
+			xhr.post({
+				url: this.getBaseURL() + "/WUGetGraph",
 				handleAs: "xml",
 				content: request,
-				sync: _sync,
 				load: function (xmlDom) {
-					context.stateID = context.parseKeyValue(xmlDom, "StateID");
-					context.state = context.parseKeyValue(xmlDom, "State");
+					context.graphs[idx].xgmml = context.getValue(xmlDom, "Graph");
+					onFetchGraphXgmml(context.graphs[idx].xgmml);
 				},
 				error: function () {
-					done = true;
 				}
 			});
 		}

+ 3 - 3
esp/files/scripts/GraphControl.js

@@ -18,7 +18,7 @@ define([
 	"dojo/_base/declare",
 	"dojo/_base/sniff",
 	"dojo/dom"
-], function (declare, has, dom) {
+], function (declare, sniff, dom) {
 	return declare(null, {
 		id: "gvc",
 		width: "",
@@ -48,7 +48,7 @@ define([
 		constructor: function (args, parentNode) {
 			declare.safeMixin(this, args);
 
-			if (has("ie")) {
+			if (sniff("ie")) {
 				this.installed = (function () {
 					try {
 						var o = new ActiveXObject("HPCCSystems.HPCCSystemsGraphViewControl.1");
@@ -160,7 +160,7 @@ define([
 
 		registerEvent: function (evt, func) {
 			if (this.obj) {
-				if (has("ie")) {
+				if (sniff("ie")) {
 					this.obj.attachEvent("on" + evt, func);
 				} else {
 					this.obj.addEventListener(evt, func, false);

+ 87 - 36
esp/files/scripts/ResultsControl.js

@@ -16,16 +16,23 @@
 ############################################################################## */
 define([
 	"dojo/_base/declare",
-	"dojo/dom",
 	"dojo/store/Memory",
 	"dojo/data/ObjectStore",
 	"dojox/grid/DataGrid",
+	"dojox/grid/EnhancedGrid",
+	"dojox/grid/enhanced/plugins/Pagination",
+	"dojox/grid/enhanced/plugins/Filter",
+	"dojox/grid/enhanced/plugins/NestedSorting",
 	"dijit/registry",
 	"dijit/layout/ContentPane"
-], function (declare, dom, Memory, ObjectStore, DataGrid, registry, ContentPane) {
+], function (declare, Memory, ObjectStore, DataGrid, EnhancedGrid, Pagination, Filter, NestedSorting, registry, ContentPane) {
 	return declare(null, {
+		workunit: null,
 		paneNum: 0,
 		resultsSheetID: "",
+		resultSheet: {},
+		sequenceResultStoreMap: [],
+		delayLoad: [],
 
 		//  Callbacks
 		onErrorClick: function (line, col) {
@@ -34,60 +41,103 @@ define([
 		// The constructor    
 		constructor: function (args) {
 			declare.safeMixin(this, args);
+
+			this.resultSheet = registry.byId(this.resultsSheetID);
+			var context = this;
+			this.resultSheet.watch("selectedChildWidget", function (name, oval, nval) {
+				if (nval.id in context.delayLoad) {
+					context.delayLoad[nval.id].placeAt(nval.containerNode, "last");
+					context.delayLoad[nval.id].startup();
+					delete context.delayLoad[nval.id];
+				}
+			});
 		},
 
 		clear: function () {
-			var resultSheet = registry.byId(this.resultsSheetID);
-			var tabs = resultSheet.getChildren();
+			this.delayLoad = [];
+			this.sequenceResultStoreMap = [];
+			var tabs = this.resultSheet.getChildren();
 			for (var i = 0; i < tabs.length; ++i) {
-				resultSheet.removeChild(tabs[i]);
+				this.resultSheet.removeChild(tabs[i]);
 			}
 		},
 
-		addTab: function (label) {
-			var resultSheet = registry.byId(this.resultsSheetID);
-			var paneID = "Pane_" + ++this.paneNum;
+		getNextPaneID: function () {
+			return "Pane_" + ++this.paneNum;
+		},
+
+		addTab: function (label, paneID) {
+			if (paneID == null) {
+				paneID = this.getNextPaneID();
+			}
 			var pane = new ContentPane({
 				title: label,
 				id: paneID,
-				closable: "true",
-				style: { padding: "0px" },
-				content: "<div id=\"Div_" + paneID + "\"></div>"
+				closable: true,
+				style: {
+					overflow: "hidden",
+					padding: 0
+				}
 			});
-			resultSheet.addChild(pane);
-			return dom.byId(pane.id);
+			this.resultSheet.addChild(pane);
+			return pane;
 		},
 
-		addDatasetTab: function (dataset) {
-			var resultNode = this.addTab(dataset.name);
+		addResultTab: function (result) {
+			var paneID = this.getNextPaneID();
+			var grid = EnhancedGrid({
+				store: result.getObjectStore(),
+				query: { id: "*" },
+				structure: result.getStructure(),
+				canSort: function (col) {
+					return false;
+				},
+				loadingMessage: result.isComplete() ? "<span class=\'dojoxGridLoading\'>Loading...</span>" : "<span class=\'dojoxGridLoading\'>Calculating...</span>",
+				plugins: {
+					//					nestedSorting: true,
+					pagination: {
+						pageSizes: [25, 50, 100, "All"],
+						defaultPageSize: 50,
+						description: true,
+						sizeSwitch: true,
+						pageStepper: true,
+						gotoButton: true,
+						maxPageStep: 4,
+						position: "bottom"
+					}
+				}
+			});
+			this.delayLoad[paneID] = grid;
+			this.sequenceResultStoreMap[result.Sequence] = result.store;
+			return this.addTab(result.getName(), paneID);
+		},
 
-			var gridLayout = [];
-			for (var h = 0; h < dataset.header.length; ++h) {
-				gridLayout.push({
-					name: dataset.header[h],
-					field: dataset.header[h],
-					width: "auto"
-				});
+		refreshResults: function (wu) {
+			if (this.workunit != wu) {
+				this.clear();
+				this.workunit = wu;
+			}
+			for (var i = 0; i < this.workunit.results.length; ++i) {
+				var result = this.workunit.results[i];
+				if (result.Sequence in this.sequenceResultStoreMap) {
+					this.sequenceResultStoreMap[result.Sequence].isComplete = result.isComplete();
+				} else {
+					pane = this.addResultTab(result);
+					if (this.sequence && this.sequence == result.Sequence) {
+						this.resultSheet.selectChild(pane);
+					}
+				}
 			}
-			store = new Memory({ data: dataset.rows });
-			dataStore = new ObjectStore({ objectStore: store });
-
-			grid = new DataGrid({
-				store: dataStore,
-				query: {},
-				structure: gridLayout
-			}, "Div_" + resultNode.id);
-			grid.startup();
 		},
 
-		addExceptionTab: function (errors) {
-			var resultNode = this.addTab("Error(s)");
-			store = new Memory({ data: errors });
+		addExceptionTab: function (exceptions) {
+			var resultNode = this.addTab("Error/Warning(s)");
+			store = new Memory({ data: exceptions });
 			dataStore = new ObjectStore({ objectStore: store });
 
 			grid = new DataGrid({
 				store: dataStore,
-				query: {},
+				query: { id: "*" },
 				structure: [
 					{ name: "Severity", field: "Severity" },
 					{ name: "Line", field: "LineNo" },
@@ -95,7 +145,8 @@ define([
 					{ name: "Code", field: "Code" },
 					{ name: "Message", field: "Message", width: "auto" }
 					]
-			}, "Div_" + resultNode.id);
+			});
+			grid.placeAt(resultNode.containerNode, "last");
 			grid.startup();
 
 			var context = this;

+ 7 - 3
esp/files/scripts/SampleSelectControl.js

@@ -19,7 +19,7 @@ define([
 	"dojo/_base/xhr",
 	"dojo/data/ItemFileReadStore",
 	"dijit/form/Select"
-], function (declare, baseXhr, ItemFileReadStore, Select) {
+], function (declare, xhr, ItemFileReadStore, Select) {
 	return declare(null, {
 		id: null,
 		samplesURL: null,
@@ -38,10 +38,14 @@ define([
 				name: this.id,
 				store: sampleStore,
 				value: "default.ecl",
-				//maxHeight: -1 // tells _HasDropDown to fit menu within viewport
+				maxHeight: -1,
+				style: {
+					overflow: "overflow: visible !important",
+					padding: 0
+				},
 				onChange: function () {
 					var filename = dijit.byId(this.id).get("value");
-					baseXhr.get({
+					xhr.get({
 						url: "ecl/" + filename,
 						handleAs: "text",
 						load: function (eclText) {

+ 83 - 0
esp/files/scripts/WUResultStore.js

@@ -0,0 +1,83 @@
+/*##############################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+define([
+	"dojo/_base/declare",
+	"dojo/_base/xhr",
+	"dojo/_base/lang",
+	"dojo/_base/Deferred",
+	"dojo/store/util/QueryResults",
+	"hpcc/ESPBase"
+], function (declare, xhr, lang, Deferred, QueryResults, ESPBase) {
+	return declare(ESPBase, {
+		idProperty: "myInjectedRowNum",
+		wuid: "",
+		sequence: 0,
+		isComplete: false,
+
+		constructor: function (args) {
+			declare.safeMixin(this, args);
+		},
+
+		getIdentity: function (object) {
+			return object[this.idProperty];
+		},
+
+		queryWhenComplete: function (query, options, deferredResults) {
+			var context = this;
+			if (this.isComplete == true) {
+				var request = {};
+				request['Wuid'] = this.wuid;
+				request['Sequence'] = this.sequence;
+				request['Start'] = options.start;
+				request['Count'] = options.count;
+				request['rawxml_'] = "1";
+
+				var results = xhr.post({
+					url: this.getBaseURL() + "/WUResult",
+					handleAs: "xml",
+					content: request,
+					load: function(domXml) {
+						var rows = context.getValues(domXml, "Row");
+						for (var i = 0; i < rows.length; ++i) {
+							rows[i].myInjectedRowNum = options.start + i + 1;
+						}
+						rows.total = context.getValue(domXml, "Total");
+						deferredResults.resolve(rows);
+					}
+				});
+			} else {
+				setTimeout(function() {
+					context.queryWhenComplete(query, options, deferredResults);
+				}, 100);
+			}
+		},
+
+		query: function (query, options) {
+			var deferredResults = new Deferred();
+
+			var context = this;
+			this.queryWhenComplete(query, options, deferredResults);
+
+			var retVal = lang.delegate(deferredResults);
+			retVal.total = Deferred.when(deferredResults, function (rows) {
+				return rows.total;
+			});
+
+			return QueryResults(retVal);
+		}
+	});
+});