Browse Source

gh-3158 New Graph Web Page

Updated ECLPlayground to match new JS layouts.
Moved common css file into css folder.
Removed old DeveloperMode.
Renamed demoLayout to hpccApp.
Added mising files for install.
Reworked cmake files to create a "web_files" project.

Signed-off-by: Gordon Smith <gordon.smith@lexisnexis.com>
Gordon Smith 13 years ago
parent
commit
a215751c72

+ 2 - 0
esp/eclwatch/ws_XSLT/wuidcommon.xslt

@@ -525,6 +525,8 @@
                 <A href="javascript:void(0)" onclick="toggleElement('Graphs');" id="explinkgraphs" class="wusectionexpand">
                   Graphs: (<xsl:value-of select="GraphCount"/>)
                 </A>
+                &nbsp;-&nbsp;
+                <a href="/esp/iframe?esp_iframe_title=Graphs - {$wuid}&amp;inner=/esp/files/WUGraph.htm%3fWuid%3d{$wuid}" >Show</a>
               </div>
             </div>
           </div>

+ 230 - 47
esp/files/CMakeLists.txt

@@ -17,48 +17,237 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ################################################################################
 
-FOREACH( iFILES
-    ${CMAKE_CURRENT_SOURCE_DIR}/default.css
-    ${CMAKE_CURRENT_SOURCE_DIR}/gen_form.css
-    ${CMAKE_CURRENT_SOURCE_DIR}/wsecl2_form.css
-    ${CMAKE_CURRENT_SOURCE_DIR}/configmgr.html
-    ${CMAKE_CURRENT_SOURCE_DIR}/GraphViewCtl.cab
-    ${CMAKE_CURRENT_SOURCE_DIR}/npGraphViewCtl.xpi
-    ${CMAKE_CURRENT_SOURCE_DIR}/update.rdf
-    ${CMAKE_CURRENT_SOURCE_DIR}/base64.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/qmalert.html
-    ${CMAKE_CURRENT_SOURCE_DIR}/calendar.html
-    ${CMAKE_CURRENT_SOURCE_DIR}/calendar_xs.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/components.xml
-    ${CMAKE_CURRENT_SOURCE_DIR}/empty.svg
-    ${CMAKE_CURRENT_SOURCE_DIR}/gen_form.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/get_input.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/graph.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/hashtable.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/hint.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/joblist.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/jobqueue.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/minus.gif
-    ${CMAKE_CURRENT_SOURCE_DIR}/plus.gif
-    ${CMAKE_CURRENT_SOURCE_DIR}/popup.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/req_array.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/select.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/stack.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/stringbuffer.js
-    ${CMAKE_CURRENT_SOURCE_DIR}/submitNavForm.html
-    ${CMAKE_CURRENT_SOURCE_DIR}/tabularForm.xslt
-    ${CMAKE_CURRENT_SOURCE_DIR}/transformDlg.html
-    ${CMAKE_CURRENT_SOURCE_DIR}/esp_app_tree.html
-    ${CMAKE_CURRENT_SOURCE_DIR}/esp_app.html
-    ${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
+project( web_files ) 
+
+set ( HTML_FILES
+    html/searchfile.html
+)
+source_group(html FILES ${HTML_FILES})
+
+set ( IMG_FILES
+    img/collapse.gif
+    img/expand.gif
+    img/save.gif
+    img/cloud.jpg
+    img/wizard.gif
+    img/tooltip.jpg
+    img/hpcc_logo.png
+    img/favicon.ico
+    img/base.gif
+    img/blank.png
+    img/information.png
+    img/cal.gif
+    img/close_wnd.gif
+    img/config.png
+    img/copyurl.png
+    img/downsimple.png
+    img/espbtns_tiled.gif
+    img/folder.gif
+    img/folderopen.gif
+    img/form_minus.gif
+    img/form_more.gif
+    img/form_plus.gif
+    img/home.png
+    img/join.gif
+    img/joinbottom.gif
+    img/keyfile.png
+    img/hpccsystemsECLWatch.png
+    img/hpccsystemsWsECL.png
+    img/line.gif
+    img/loading.gif
+    img/locked.gif
+    img/menu1.png
+    img/menudown.png
+    img/menuup.png
+    img/minus.gif
+    img/minusbottom.gif
+    img/next.gif
+    img/next_year.gif
+    img/outlet.png
+    img/page.gif
+    img/pixel.gif
+    img/plus.gif
+    img/plusbottom.gif
+    img/prev.gif
+    img/prev_year.gif
+    img/refresh.png
+    img/refreshdisabled.png
+    img/refreshenabled.png
+    img/relogin.png
+    img/reqxml.gif
+    img/respxml.gif
+    img/selectall.gif
+    img/tab_bottom.gif
+    img/topurl.png
+    img/unselectall.gif
+    img/upsimple.png
+    img/wsdl.gif
+    img/xsd.gif
+    img/zip.gif
+    img/zipbig.gif
+    img/accept.png
+    img/bullet_green.png
+    img/bullet_orange.png
+    img/cog.png
+    img/delete.png
+    img/folder_table.png
+    img/control_pause_blue.png
+    img/bullet_error.png
+    img/bullet_red.png
+    img/bullet_white.png
+    img/bullet_yellow.png
+    img/loadingAnimation.gif
+)
+source_group(img FILES ${IMG_FILES})
+
+set ( LOGO_FILES
+    logo/ln_small.gif
+    logo/seisint_small.gif
+    logo/slogo.gif
+)
+source_group(logo FILES ${LOGO_FILES})
+
+set ( CSS_FILES
+    css/espdefault.css
+    css/eclwatch.css
+    css/graph.css
+    css/winclassic.css
+    css/headerFooter.css
+    css/list.css
+    css/rightSideBar.css
+    css/sortabletable.css
+    css/tabs.css
+    css/hpcc.css
+    css/ecl.css
+)
+source_group(css FILES ${CSS_FILES})
+
+set ( TEMPLATES_FILES
+    templates/ECLPlaygroundWidget.html
+    templates/GraphPageWidget.html
+    templates/GraphWidget.html
+    templates/ResultsWidget.html
+    templates/SampleSelectWidget.html
+    templates/TargetSelectWidget.html
 )
-    Install( FILES ${iFILES} DESTINATION componentfiles/files COMPONENT Runtime )
-ENDFOREACH ( iFILES )
+source_group(templates FILES ${TEMPLATES_FILES})
+
+set ( SCRIPTS_FILES
+    scripts/espdefault.js
+    scripts/CMultiSelect.js
+    scripts/bpsreport.js
+    scripts/builder.js
+    scripts/controls.js
+    scripts/dragdrop.js
+    scripts/effects.js
+    scripts/fixedTables.js
+    scripts/graphgvc.js
+    scripts/multiselect.js
+    scripts/objtree.js
+    scripts/prototype.js
+    scripts/range.js
+    scripts/timer.js
+    scripts/slider.js
+    scripts/prototype_helpers.js
+    scripts/rightSideBar.js
+    scripts/scriptaculous.js
+    scripts/sortabletable.js
+    scripts/tabularForm.js
+    scripts/tooltip.js
+    scripts/tree.js
+    scripts/tree_template.js
+    scripts/ui_engine.js
+
+    scripts/ESPBase.js
+    scripts/ESPWorkunit.js
+    scripts/ESPResult.js
+    scripts/EclEditorControl.js
+    scripts/WsWorkunits.js
+
+    scripts/ResultsControl.js
+
+    scripts/ECLPlaygroundWidget.js
+    scripts/GraphPageWidget.js
+    scripts/GraphWidget.js
+    scripts/ResultsWidget.js
+    scripts/SampleSelectWidget.js
+    scripts/TargetSelectWidget.js
+)
+source_group(scripts FILES ${SCRIPTS_FILES})
+
+set ( MAIN_FILES 
+	dummy.cpp
+    default.css
+    gen_form.css
+    wsecl2_form.css
+    configmgr.html
+    GraphViewCtl.cab
+    npGraphViewCtl.xpi
+    update.rdf
+    base64.js
+    qmalert.html
+    calendar.html
+    calendar_xs.js
+    components.xml
+    empty.svg
+    gen_form.js
+    get_input.js
+    graph.js
+    hashtable.js
+    hint.js
+    joblist.js
+    jobqueue.js
+    minus.gif
+    plus.gif
+    popup.js
+    req_array.js
+    select.js
+    stack.js
+    stringbuffer.js
+    submitNavForm.html
+    tabularForm.xslt
+    transformDlg.html
+    esp_app_tree.html
+    esp_app.html
+    useradd.html
+    groupadd.html
+    ECLPlayground.htm
+    ECLPlayground.js
+    ECLPlaygroundResults.htm
+    ECLPlaygroundResults.js
+    WUGraph.htm
+    WUGraph.js
+    )
+source_group("Source Files" FILES ${MAIN_FILES})
+
+set ( SRCS ${MAIN_FILES} ${HTML_FILES} ${IMG_FILES} ${LOGO_FILES} ${CSS_FILES} ${SCRIPTS_FILES} ${TEMPLATES_FILES})
+HPCC_ADD_LIBRARY( web_files ${SRCS} )
+
+Install ( FILES html/searchfile.html DESTINATION componentfiles/files COMPONENT Runtime )
+FOREACH( iFile ${IMG_FILES} )
+    Install( FILES ${iFile} DESTINATION componentfiles/files/img COMPONENT Runtime )
+ENDFOREACH ( iFile )
+Install ( FILES img/favlogo.png DESTINATION componentfiles/files COMPONENT Runtime )
+
+FOREACH( iFile ${LOGO_FILES} )
+    Install( FILES ${iFile} DESTINATION componentfiles/files/logo COMPONENT Runtime )
+ENDFOREACH ( iFile )
+
+FOREACH( iFile ${CSS_FILES} )
+    Install( FILES ${iFile} DESTINATION componentfiles/files/css COMPONENT Runtime )
+ENDFOREACH ( iFile )
+
+FOREACH( iFile ${SCRIPTS_FILES} )
+    Install( FILES ${iFile} DESTINATION componentfiles/files/scripts COMPONENT Runtime )
+ENDFOREACH ( iFile )
+
+FOREACH( iFile ${TEMPLATES_FILES} )
+    Install( FILES ${iFile} DESTINATION componentfiles/files/templates COMPONENT Runtime )
+ENDFOREACH ( iFile )
+
+FOREACH( iFile ${MAIN_FILES} )
+    Install( FILES ${iFile} DESTINATION componentfiles/files COMPONENT Runtime )
+ENDFOREACH ( iFile )
 
 Install ( FILES ${CMAKE_CURRENT_SOURCE_DIR}/popup.js DESTINATION componentfiles/files/scripts COMPONENT Runtime )
 
@@ -69,9 +258,3 @@ Install ( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dojox DESTINATION componentfiles
 Install ( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dijit DESTINATION componentfiles/files COMPONENT Runtime USE_SOURCE_PERMISSIONS PATTERN ".svn" EXCLUDE )
 Install ( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/CodeMirror2 DESTINATION componentfiles/files COMPONENT Runtime USE_SOURCE_PERMISSIONS PATTERN ".svn" EXCLUDE )
 Install ( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ecl DESTINATION componentfiles/files COMPONENT Runtime USE_SOURCE_PERMISSIONS PATTERN ".svn" EXCLUDE )
-
-add_subdirectory (css)
-add_subdirectory (html)
-add_subdirectory (img)
-add_subdirectory (logo)
-add_subdirectory (scripts)

+ 0 - 140
esp/files/ECLPlayground.css

@@ -1,140 +0,0 @@
-/*##############################################################################
-#    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, body {
-  background: white; 
-  width: 100%;
-  height: 100%;
-  margin: 0px;
-  color: rgb(51, 51, 51); 
-  font-family: Lucida Sans,Lucida Grande,Arial !important; 
-  font-size: 13px !important;
-  padding-top:0px;
-  padding-right:0px;
-  padding-bottom:0px;
-  padding-left:0px;
-}
-button {
-	background-position: top; 
-	padding: 2px 8px 4px; 
-	border-radius: 4px; 
-	border: 1px solid rgb(118, 157, 192); 
-	font-size: 1em; 
-	background-image: url("http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dijit/themes/claro/form/images/button.png"); 
-	background-repeat: repeat-x; 
-	background-color: rgb(228, 242, 255); 
-	-webkit-transition: background-color 0.2s linear; 
-	-moz-border-radius: 4px 4px 4px 4px; 
-	-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
-}
-button:hover {
-	color: rgb(0, 0, 0); 
-	background-color: rgb(175, 217, 255);
-}
-h1 {
-	font-size: 1.5em;
-}
-
-#eclContent {
-	height: 80%;
-}
-.CodeMirror {
-}
-.CodeMirror-scroll {
-	position: absolute;
-	height: 99%;
-	top: 0;
-	bottom: 0;
-	left: 0;
-	right: 0;
-}
-
-#title {
-	font-weight: bold;
-	font-size: x-large;
-}
-
-#appLayout {
-	height: 100%;
-}
-.claro .demoLayout .topPanel {
-	background-color: rgb(208, 233, 252);
-}
-.claro .demoLayout .edgePanel {
-	background-color: rgb(208, 233, 252);
-}
-.pageOverlay {
-	left: 0px;
-	top: 0px;
-	width: 100%;
-	height: 100%;
-	display: block;
-	position: absolute;
-	z-index: 1001;
-}
-#loadingOverlay {
-	background: url("/esp/files/img/loadingAnimation.gif") no-repeat 10px 23px rgb(255, 255, 255);
-}
-#loadingOverlay .loadingMessage {
-	padding: 25px 40px;
-	color: rgb(153, 153, 153);
-}
-.searchInputColumn {
-	width: 100%;
-	margin-right: -10em;
-	float: left;
-}
-.searchInputColumnInner {
-	padding-top: 2px;
-	padding-right: 11em;
-}
-.searchButtonColumn {
-	width: 10em;
-	float: left;
-}
-#searchBtn {
-	width: 100%;
-	display: block;
-}
-#searchTerms {
-	width: 100%;
-	display: block;
-}
-.demoImageList {
-	margin: 0px;
-	padding: 0px;
-	background-color: rgb(204, 204, 204);
-}
-.demoImageList li.item img {
-	overflow: hidden;
-	margin-right: 10px;
-	cursor: pointer;
-	max-height: 50px;
-}
-.demoImageList li.item {
-	margin: 0px;
-	padding: 4px 15px 3px 10px;
-	overflow: hidden;
-	border-top-color: rgb(255, 255, 255);
-	border-bottom-color: rgb(153, 153, 153);
-	border-top-width: 2px;
-	border-bottom-width: 1px;
-	border-top-style: solid;
-	border-bottom-style: solid;
-	position: relative;
-	max-height: 60px;
-	background-color: rgb(238, 238, 238);
-}

+ 7 - 59
esp/files/ECLPlayground.htm

@@ -25,7 +25,7 @@
     <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="css/hpcc.css" 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">
@@ -34,16 +34,13 @@
     <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 + "/"
@@ -56,7 +53,9 @@
     </script>
     <script src="dojo/dojo.js"></script>
     <script>
-        require(["main/ECLPlayground", "dijit/layout/BorderContainer", "dijit/layout/ContentPane", "dijit/layout/TabContainer", "dojo/domReady!"], function (app) {
+        require([
+        "main/ECLPlayground", "hpcc/ECLPlaygroundWidget",
+        "dojo/domReady!"], function (app) {
             app.init();
         });
     </script>
@@ -65,62 +64,11 @@
     <!-- overlay -->
     <div id="loadingOverlay" class="pageOverlay">
         <div class="loadingMessage">
-            Loading...</div>
+            Loading...
+        </div>
     </div>
     <!-- application -->
-    <div id="appLayout" class="demoLayout" data-dojo-props="design: 'headline'" data-dojo-type="dijit.layout.BorderContainer">
-        <div id="topPane" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.layout.ContentPane">
-            <div id="title" style="display: inline-block; vertical-align: middle">
-                ECL Playground</div>
-            <div style="float: right; display: inline-block; vertical-align: middle">
-                <label id="sampleSelectLabel" for="sampleSelect">Sample:</label>
-                <div id="sampleSelect">
-                </div>
-            </div>
-        </div>
-        <div id="tabs" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
-            <div id="eclContent">
-                <textarea id="eclCode">
-/*
-    Example code - use without restriction.  
-*/
-Layout_Person := RECORD
-  UNSIGNED1 PersonID;
-  STRING15  FirstName;
-  STRING25  LastName;
-END;
-
-allPeople := DATASET([ {1,'Fred','Smith'},
-                       {2,'Joe','Blow'},
-                       {3,'Jane','Smith'}],Layout_Person);
-
-somePeople := allPeople(LastName = 'Smith');
-
-//  Outputs  ---
-somePeople;
-			</textarea>
-            </div>
-        </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%; overflow: hidden">
-            </div>
-        </div>
-        <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">
-            <div style="display: inline-block; vertical-align: middle">
-                <button id="submitBtn">
-                    Submit</button>
-                <div id="targetSelect" style="overflow: visible !important">
-                </div>
-	    </div>
-            <div style="float: right; display: inline-block; vertical-align: middle">
-                <div id="status">
-                    ...</div>
-            </div>
-        </div>
+    <div id="appLayout" class="hpccApp" data-dojo-props="design: 'headline'" data-dojo-type="ECLPlaygroundWidget">
     </div>
 </body>
 </html>

+ 54 - 278
esp/files/ECLPlayground.js

@@ -17,287 +17,63 @@
 define([
 	"dojo/_base/fx",
 	"dojo/_base/window",
-	"dojo/_base/xhr",
 	"dojo/dom",
 	"dojo/dom-style",
 	"dojo/dom-geometry",
-	"dojo/on",
+	"dojo/io-query",
 	"dojo/ready",
-	"dijit/registry",
-	"hpcc/EclEditorControl",
-	"hpcc/GraphControl",
-	"hpcc/ResultsControl",
-	"hpcc/SampleSelectControl",
-	"hpcc/ESPBase",
-	"hpcc/ESPWorkunit",
-	"dijit/form/Select"
-], function (fx, baseWindow, xhr, dom, domStyle, domGeometry, on, ready, registry, EclEditor, GraphControl, ResultsControl, Select, ESPBase, Workunit, dijitSelect) {
-	var wu = null,
-			target = "",
-			editorControl = null,
-			graphControl = null,
-			resultsControl = null,
-			sampleSelectControl = null,
 
-			initUi = function () {
-				var _target = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Target"];
-				if (_target) {
-					target = _target;
-				}
-				var wuid = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Wuid"];
-				if (wuid) {
-					dojo.destroy("topPane");
-					registry.byId("appLayout").resize();
-				} else {
-					initSamples();
-				}
-				initEditor();
-				initResultsControl();
-				initTargets();
-
-				//  ActiveX will flicker if created before initial layout
-				setTimeout(function () {
-					initGraph();
-					if (wuid) {
-						wu = new Workunit({
-							wuid: wuid
-						});
-						wu.fetchText(function (text) {
-							editorControl.setText(text);
-						});
-						wu.monitor(monitorEclPlayground);
-					} else {
-						graphControl.watchSelect(sampleSelectControl.select);
-					}
-				}, 1);
-				on(dom.byId("submitBtn"), "click", doSubmit);
-			},
-
-			initUiResults = function () {
-				var wuid = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Wuid"];
-				var sequence = dojo.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 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,
-								onGetAll: displayAll
-							});
-						}
-					});
-				}
-			},
-
-			initSamples = function () {
-				sampleSelectControl = new Select({
-					id: "sampleSelect",
-					samplesURL: "ecl/ECLPlaygroundSamples.json",
-					onNewSelection: function (eclText) {
-						resetPage();
-						editorControl.setText(eclText);
-					}
-				});
-			},
-
-			initEditor = function () {
-				editorControl = new EclEditor({
-					domId: "eclCode"
-				});
-			},
-
-			initGraph = function () {
-				graphControl = new GraphControl({
-					id: "gvc",
-					width: "100%",
-					height: "100%",
-					onInitialize: function () {
-					},
-
-					onLayoutFinished: function () {
-						graphControl.obj.setMessage('');
-						graphControl.obj.centerOnItem(0, true);
-					},
-
-					onMouseDoubleClick: function (item) {
-						graphControl.obj.centerOnItem(item, true);
-					},
-
-					onSelectionChanged: function (items) {
-						editorControl.clearHighlightLines();
-						for (var i = 0; i < items.length; ++i) {
-							var props = graphControl.obj.getProperties(items[i]);
-							if (props.definition) {
-								var startPos = props.definition.indexOf("(");
-								var endPos = props.definition.lastIndexOf(")");
-								var pos = props.definition.slice(startPos + 1, endPos).split(",");
-								var lineNo = parseInt(pos[0], 10);
-								editorControl.highlightLine(lineNo);
-								editorControl.setCursor(lineNo, 0);
-							}
-						}
-
-					}
-				}, dom.byId("graphs"));
-			},
-
-			initResultsControl = function (sequence) {
-				resultsControl = new ResultsControl({
-					resultsSheetID: "resultsPane",
-					sequence: sequence,
-					onErrorClick: function (line, col) {
-						editorControl.setCursor(line, col);
-					}
-				});
-			},
-
-			initTargets = function () {
-				var base = new ESPBase({
-				});
-				var request = {
-					rawxml_: true
-				};
-
-				xhr.post({
-					url: base.getBaseURL("WsTopology") + "/TpTargetClusterQuery",
-					handleAs: "xml",
-					content: request,
-					load: function (xmlDom) {
-						var targetData = base.getValues(xmlDom, "TpTargetCluster");
-						var options = [];
-						var has_hthor = false;
-						for (var i = 0; i < targetData.length; ++i) {
-							options.push({
-								label: targetData[i].Name, 
-								value: targetData[i].Name							
-							});
-							if (targetData[i].Name == "hthor") {
-								has_hthor = true;
-							}
-						}
-
-						var select = new dijitSelect({
-								name: "targetSelect",
-								options: options,
-								maxHeight: -1,
-								onChange: function () {
-									target = dijit.byId(this.id).get("value");
-								}
-						}, "targetSelect");
-
-						if (target == "") {
-							if (has_hthor) {
-								target = "hthor";
-								select.set("value", target);
-							} else {
-								target = options[0].value;
-							}
-						} else {
-							select.set("value", target);
-						}
-						select.startup();
-					},
-					error: function () {
-					}
-				});
-			},
-
-			resetPage = function () {
-				editorControl.clearErrors();
-				editorControl.clearHighlightLines();
-				graphControl.clear();
-				resultsControl.clear();
-			},
-
-			monitorEclPlayground = function () {
-				dom.byId("status").innerHTML = wu.state;
-				if (wu.isComplete()) {
-					wu.getInfo({
-						onGetExceptions: displayExceptions,
-						onGetResults: displayResults,
-						onGetGraphs: displayGraphs,
-						onGetAll: displayAll
-					});
-				}
-			},
-
-			displayExceptions = function (exceptions) {
-			},
-
-			displayResults = function (results) {
-			},
-
-			displayGraphs = function (graphs) {
-				for (var i = 0; i < graphs.length; ++i) {
-					wu.fetchGraphXgmml(i, function (xgmml) {
-						graphControl.loadXGMML(xgmml, true);
-					});
-				}
-			},
-
-			displayAll = function (workunit) {
-				if (wu.exceptions.length) {
-					editorControl.setErrors(wu.exceptions);
-				}
-				resultsControl.refresh(wu);
-			},
-
-			doSubmit = function (evt) {
-				resetPage();
-				wu = new Workunit({
-					onCreate: function () {
-						wu.update(editorControl.getText());
-					},
-					onUpdate: function () {
-						wu.submit(target);
-					},
-					onSubmit: function () {
-						wu.monitor(monitorEclPlayground);
-					}
-				});
-			},
-
-			startLoading = function (targetNode) {
-				var overlayNode = dom.byId("loadingOverlay");
-				if ("none" == domStyle.get(overlayNode, "display")) {
-					var coords = domGeometry.getMarginBox(targetNode || baseWindow.body());
-					domGeometry.setMarginBox(overlayNode, coords);
-					domStyle.set(dom.byId("loadingOverlay"), {
-						display: "block",
-						opacity: 1
-					});
-				}
-			},
-
-			endLoading = function () {
-				fx.fadeOut({
-					node: dom.byId("loadingOverlay"),
-					duration: 175,
-					onEnd: function (node) {
-						domStyle.set(node, "display", "none");
-					}
-				}).play();
-			}
-
-	return {
-		init: function () {
-			startLoading();
-			ready(function () {
-				initUi();
-				endLoading();
-			});
-		},
-		initResults: function () {
-			startLoading();
-			ready(function () {
-				initUiResults();
-				endLoading();
-			});
-		}
-	};
+	"dijit/registry"
+], function (fx, baseWindow, dom, domStyle, domGeometry, ioQuery, ready, registry) {
+    var initUi = function () {
+        var wuid = "";
+        var target = "hthor";
+
+        var urlWuid = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Wuid"];
+        if (urlWuid) {
+            wuid = urlWuid;
+        }
+        var urlTarget = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Target"];
+        if (urlTarget) {
+            target = urlTarget;
+        }
+
+        var eclPlayground = registry.byId("appLayout");
+        if (wuid) {
+            eclPlayground.hideTitle();
+        }
+        eclPlayground.init(wuid, target);
+    },
+
+	startLoading = function (targetNode) {
+	    var overlayNode = dom.byId("loadingOverlay");
+	    if ("none" == domStyle.get(overlayNode, "display")) {
+	        var coords = domGeometry.getMarginBox(targetNode || baseWindow.body());
+	        domGeometry.setMarginBox(overlayNode, coords);
+	        domStyle.set(dom.byId("loadingOverlay"), {
+	            display: "block",
+	            opacity: 1
+	        });
+	    }
+	},
+
+	endLoading = function () {
+	    fx.fadeOut({
+	        node: dom.byId("loadingOverlay"),
+	        duration: 175,
+	        onEnd: function (node) {
+	            domStyle.set(node, "display", "none");
+	        }
+	    }).play();
+	}
+
+    return {
+        init: function () {
+            startLoading();
+            ready(function () {
+                initUi();
+                endLoading();
+            });
+        }
+    };
 });

+ 9 - 15
esp/files/ECLPlaygroundResults.htm

@@ -21,11 +21,7 @@
 <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="css/hpcc.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">
@@ -34,16 +30,13 @@
     <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 + "/"
@@ -56,21 +49,22 @@
     </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();
+        require([
+        "main/ECLPlaygroundResults", "hpcc/ResultsWidget",
+        "dojo/domReady!"], function (app) {
+            app.init();
         });
     </script>
 </head>
 <body class="claro">
     <!-- overlay -->
     <div id="loadingOverlay" class="pageOverlay">
-        <div id="loadingMessage" class="loadingMessage">
-            Loading...</div>
+        <div 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 id="appLayout" class="hpccApp" data-dojo-props="design: 'headline'" data-dojo-type="ResultsWidget">
     </div>
 </body>
 </html>

+ 76 - 0
esp/files/ECLPlaygroundResults.js

@@ -0,0 +1,76 @@
+/*##############################################################################
+#    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/fx",
+	"dojo/_base/window",
+	"dojo/dom",
+	"dojo/dom-style",
+	"dojo/dom-geometry",
+	"dojo/io-query",
+	"dojo/ready",
+
+	"dijit/registry"
+], function (fx, baseWindow, dom, domStyle, domGeometry, ioQuery, ready, registry) {
+    var initUi = function () {
+        var wuid = "";
+        var sequence = "";
+
+        var urlWuid = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Wuid"];
+        if (urlWuid) {
+            wuid = urlWuid;
+        }
+        var urlSequence = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Sequence"];
+        if (urlSequence) {
+            sequence = urlSequence;
+        }
+
+        var results = registry.byId("appLayout");
+        results.init(wuid, sequence);
+    },
+
+	startLoading = function (targetNode) {
+	    var overlayNode = dom.byId("loadingOverlay");
+	    if ("none" == domStyle.get(overlayNode, "display")) {
+	        var coords = domGeometry.getMarginBox(targetNode || baseWindow.body());
+	        domGeometry.setMarginBox(overlayNode, coords);
+	        domStyle.set(dom.byId("loadingOverlay"), {
+	            display: "block",
+	            opacity: 1
+	        });
+	    }
+	},
+
+	endLoading = function () {
+	    fx.fadeOut({
+	        node: dom.byId("loadingOverlay"),
+	        duration: 175,
+	        onEnd: function (node) {
+	            domStyle.set(node, "display", "none");
+	        }
+	    }).play();
+	}
+
+    return {
+        init: function () {
+            startLoading();
+            ready(function () {
+                initUi();
+                endLoading();
+            });
+        }
+    };
+});

+ 70 - 0
esp/files/WUGraph.htm

@@ -0,0 +1,70 @@
+<!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: WU Graph</title>
+    <link href="css/hpcc.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("/");
+            base.pop();
+            base = base.join("/");
+
+            return {
+                async: true,
+                parseOnLoad: true,
+                urlBase: base,
+                packages: [{
+                    name: "main",
+                    location: base + "/"
+                }, {
+                    name: "hpcc",
+                    location: base + "/scripts/"
+                }]
+            };
+        })();
+    </script>
+    <script src="dojo/dojo.js"></script>
+    <script>
+        require([
+		"main/WUGraph", "hpcc/GraphPageWidget",
+        "dojo/domReady!"], function (app) {
+            app.init();
+        });
+    </script>
+</head>
+<body class="claro">
+    <!-- overlay -->
+    <div id="loadingOverlay" class="pageOverlay">
+        <div class="loadingMessage">
+            Loading...
+        </div>
+    </div>
+    <!-- application -->
+    <div id="appLayout" class="hpccApp" data-dojo-props="design: 'headline'" data-dojo-type="GraphPageWidget">
+    </div>
+</body>
+</html>

+ 76 - 0
esp/files/WUGraph.js

@@ -0,0 +1,76 @@
+/*##############################################################################
+#    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/fx",
+	"dojo/_base/window",
+	"dojo/dom",
+	"dojo/dom-style",
+	"dojo/dom-geometry",
+	"dojo/io-query",
+	"dojo/ready",
+
+	"dijit/registry"
+], function (fx, baseWindow, dom, domStyle, domGeometry, ioQuery, ready, registry) {
+    var initUi = function () {
+        var wuid = "W20120722-100052";//"W20120601-121930";//"W20120530-153214";//
+        var graphName = "";
+
+        var urlWuid = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["Wuid"];
+        if (urlWuid) {
+            wuid = urlWuid;
+        }
+        var urlGraphName = ioQuery.queryToObject(dojo.doc.location.search.substr((dojo.doc.location.search.substr(0, 1) == "?" ? 1 : 0)))["GraphName"];
+        if (urlGraphName) {
+            graphName = urlGraphName;
+        }
+
+        var graphControl = registry.byId("appLayout");
+        graphControl.setWuid(wuid, graphName);
+    },
+
+	startLoading = function (targetNode) {
+	    var overlayNode = dom.byId("loadingOverlay");
+	    if ("none" == domStyle.get(overlayNode, "display")) {
+	        var coords = domGeometry.getMarginBox(targetNode || baseWindow.body());
+	        domGeometry.setMarginBox(overlayNode, coords);
+	        domStyle.set(dom.byId("loadingOverlay"), {
+	            display: "block",
+	            opacity: 1
+	        });
+	    }
+	},
+
+	endLoading = function () {
+	    fx.fadeOut({
+	        node: dom.byId("loadingOverlay"),
+	        duration: 175,
+	        onEnd: function (node) {
+	            domStyle.set(node, "display", "none");
+	        }
+	    }).play();
+	}
+
+    return {
+        init: function () {
+            startLoading();
+            ready(function () {
+                initUi();
+                endLoading();
+            });
+        }
+    };
+});

+ 99 - 0
esp/files/css/hpcc.css

@@ -0,0 +1,99 @@
+/*##############################################################################
+#    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, body {
+    background: white;
+    width: 100%;
+    height: 100%;
+    margin: 0px;
+    color: rgb(51, 51, 51);
+    font-family: Lucida Sans,Lucida Grande,Arial !important;
+    font-size: 13px !important;
+    padding-top: 0px;
+    padding-right: 0px;
+    padding-bottom: 0px;
+    padding-left: 0px;
+}
+
+button {
+    background-position: top;
+    padding: 2px 8px 4px;
+    border-radius: 4px;
+    border: 1px solid rgb(118, 157, 192);
+    font-size: 1em;
+    background-image: url("esp/files/dijit/themes/claro/form/images/button.png");
+    background-repeat: repeat-x;
+    background-color: rgb(228, 242, 255);
+    -webkit-transition: background-color 0.2s linear;
+    -moz-border-radius: 4px 4px 4px 4px;
+    -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+
+button:hover {
+        color: rgb(0, 0, 0);
+        background-color: rgb(175, 217, 255);
+    }
+
+h1 {
+    font-size: 1.5em;
+}
+
+.CodeMirror {
+}
+.CodeMirror-scroll {
+    position: absolute;
+    height: 100%;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+}
+
+.claro .hpccApp .topPanel {
+    background-color: rgb(208, 233, 252);
+}
+
+.claro .hpccApp .edgePanel {
+    background-color: rgb(208, 233, 252);
+}
+
+#appLayout {
+    height: 100%;
+}
+
+.pageOverlay {
+    left: 0px;
+    top: 0px;
+    width: 100%;
+    height: 100%;
+    display: block;
+    position: absolute;
+    z-index: 1001;
+}
+
+#loadingOverlay {
+    background: url("/esp/files/img/loadingAnimation.gif") no-repeat 10px 23px rgb(255, 255, 255);
+}
+
+    #loadingOverlay .loadingMessage {
+        padding: 25px 40px;
+        color: rgb(153, 153, 153);
+    }
+
+#timings, #properties {
+    padding: 0px;
+    overflow: hidden;
+}

+ 2 - 0
esp/files/dummy.cpp

@@ -0,0 +1,2 @@
+//  Empty File for cmake
+

+ 228 - 0
esp/files/scripts/ECLPlaygroundWidget.js

@@ -0,0 +1,228 @@
+/*##############################################################################
+#    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/>.
+############################################################################## */
+require([
+	"dojo/_base/declare",
+	"dojo/_base/xhr",
+	"dojo/dom",
+
+	"dijit/layout/_LayoutWidget",
+	"dijit/_TemplatedMixin",
+	"dijit/_WidgetsInTemplateMixin",
+	"dijit/layout/BorderContainer",
+	"dijit/layout/TabContainer",
+	"dijit/layout/ContentPane",
+	"dijit/registry",
+
+	"hpcc/EclEditorControl",
+	"hpcc/TargetSelectWidget",
+	"hpcc/SampleSelectWidget",
+	"hpcc/GraphWidget",
+	"hpcc/ResultsWidget",
+	"hpcc/ESPWorkunit",
+
+	"dojo/text!./templates/ECLPlaygroundWidget.html"
+], function (declare, xhr, dom,
+				_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, TabContainer, ContentPane, registry,
+				EclEditor, TargetSelectWidget, SampleSelectWidget, GraphWidget, ResultsWidget, Workunit,
+				template) {
+    return declare("ECLPlaygroundWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "ECLPlaygroundWidget",
+        wu: null,
+        editorControl: null,
+        graphControl: null,
+        resultsWidget: null,
+        targetSelectWidget: null,
+        sampleSelectWidget: null,
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this._initControls();
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.borderContainer.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        //  Implementation  ---
+        _initControls: function () {
+            var context = this;
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
+            this.targetSelectWidget = registry.byId(this.id + "TargetSelect");
+            this.resultsWidget = registry.byId(this.id + "Results");
+            this.resultsWidget.onErrorClick = function (line, col) {
+                context.editorControl.setCursor(line, col);
+            };
+        },
+
+        hideTitle: function () {
+            var topPane = dom.byId(this.id + "TopPane");
+            dojo.destroy(topPane);
+            this.borderContainer.resize();
+        },
+
+        init: function (wuid, target) {
+            this.wuid = wuid;
+            this.targetSelectWidget.setValue(target);
+
+            this.initEditor();
+            //this.initresultsWidget();
+
+            //  ActiveX will flicker if created before initial layout
+            var context = this;
+            this.initGraph();
+            if (wuid) {
+                this.wu = new Workunit({
+                    wuid: wuid
+                });
+                this.wu.fetchText(function (text) {
+                    context.editorControl.setText(text);
+                });
+                this.wu.monitor(function () {
+                    context.monitorEclPlayground();
+                });
+            } else {
+                this.initSamples();
+                this.graphControl.watchSelect(this.sampleSelectWidget.selectControl);
+            }
+            this.graphControl.watchSplitter(this.borderContainer.getSplitter("right"));
+            this.graphControl.watchSplitter(this.borderContainer.getSplitter("bottom"));
+        },
+
+        initSamples: function () {
+            var context = this;
+            this.sampleSelectWidget = registry.byId(this.id + "SampleSelect");
+            this.sampleSelectWidget.onNewSelection = function (eclText) {
+                context.resetPage();
+                context.editorControl.setText(eclText);
+            };
+            this.sampleSelectWidget.load();
+        },
+
+        initEditor: function () {
+            this.editorControl = new EclEditor({
+                domId: this.id + "EclCode"
+            });
+        },
+
+        initGraph: function () {
+            var context = this;
+            this.graphControl = registry.byId(this.id + "Graphs");
+            this.graphControl.onSelectionChanged = function (items) {
+                context.editorControl.clearHighlightLines();
+                for (var i = 0; i < items.length; ++i) {
+                    var props = context.graphControl.plugin.getProperties(items[i]);
+                    if (props.definition) {
+                        var startPos = props.definition.indexOf("(");
+                        var endPos = props.definition.lastIndexOf(")");
+                        var pos = props.definition.slice(startPos + 1, endPos).split(",");
+                        var lineNo = parseInt(pos[0], 10);
+                        context.editorControl.highlightLine(lineNo);
+                        context.editorControl.setCursor(lineNo, 0);
+                    }
+                }
+            };
+        },
+
+        resetPage: function () {
+            this.editorControl.clearErrors();
+            this.editorControl.clearHighlightLines();
+            this.graphControl.clear();
+            this.resultsWidget.clear();
+        },
+
+        monitorEclPlayground: function () {
+            dom.byId(this.id + "Status").innerHTML = this.wu.state;
+            var context = this;
+            if (this.wu.isComplete()) {
+                this.wu.getInfo({
+                    onGetExceptions: function (exceptions) {
+                        context.displayExceptions(exceptions);
+                    },
+                    onGetResults: function (results) {
+                        context.displayResults(results);
+                    },
+                    onGetGraphs: function (graphs) {
+                        context.displayGraphs(graphs);
+                    },
+                    onGetAll: function (workunit) {
+                        context.displayAll(workunit);
+                    }
+                });
+            }
+        },
+
+        displayExceptions: function (exceptions) {
+        },
+
+        displayResults: function (results) {
+        },
+
+        displayGraphs: function (graphs) {
+            for (var i = 0; i < graphs.length; ++i) {
+                var context = this;
+                if (i == 0) {
+                    this.wu.fetchGraphXgmml(i, function (xgmml) {
+                        context.graphControl.loadXGMML(xgmml);
+                    });
+                } else {
+                    this.wu.fetchGraphXgmml(i, function (xgmml) {
+                        context.graphControl.loadXGMML(xgmml, true);
+                    });
+                }
+            }
+        },
+
+        displayAll: function (workunit) {
+            if (this.wu.exceptions.length) {
+                this.editorControl.setErrors(this.wu.exceptions);
+            }
+            this.resultsWidget.refresh(this.wu);
+        },
+
+        _onSubmit: function (evt) {
+            this.resetPage();
+            var context = this;
+            this.wu = new Workunit({
+                onCreate: function () {
+                    context.wu.update(context.editorControl.getText());
+                },
+                onUpdate: function () {
+                    context.wu.submit(context.targetSelectWidget.getValue());
+                },
+                onSubmit: function () {
+                    context.wu.monitor(function () {
+                        context.monitorEclPlayground();
+                    });
+                }
+            });
+        }
+    });
+});

+ 6 - 6
esp/files/scripts/ESPResult.js

@@ -48,11 +48,11 @@ define([
 				field: this.store.idProperty,
 				width: "40px"
 			});
-			for (var i = 0; i < this.ECLSchemas.length; ++i) {
+			for (var i = 0; i < this.ECLSchemas.ECLSchemaItem.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)
+					name: this.ECLSchemas.ECLSchemaItem[i].ColumnName,
+					field: this.ECLSchemas.ECLSchemaItem[i].ColumnName,
+					width: this.extractWidth(this.ECLSchemas.ECLSchemaItem[i].ColumnType, this.ECLSchemas.ECLSchemaItem[i].ColumnName)
 				});
 			}
 			return retVal;
@@ -83,8 +83,8 @@ define([
 
 		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";
+			for (var i = 0; i < this.ECLSchemas.ECLSchemaItem.length; ++i) {
+				retVal += "\t" + this.ECLSchemas.ECLSchemaItem[i].ColumnType + "\t" + this.ECLSchemas.ECLSchemaItem[i].ColumnName + ";\n";
 			}
 			retVal += "END;\n";
 			return retVal;

+ 142 - 53
esp/files/scripts/ESPWorkunit.js

@@ -52,30 +52,28 @@ define([
 		},
 		isComplete: function () {
 			switch (this.stateID) {
-				case '3':
-					//WUStateCompleted:
-				case '4':
-					//WUStateFailed:
-				case '5':
-					//WUStateArchived:
-				case '7':
-					//WUStateAborted:
+				case 3:	//WUStateCompleted:
+				case 4:	//WUStateFailed:
+				case 5:	//WUStateArchived:
+				case 7:	//WUStateAborted:
 					return true;
 			}
 			return false;
 		},
-		monitor: function (callback) {
+		monitor: function (callback, monitorDuration) {
+			if (!monitorDuration)
+				monitorDuration = 0;
 			var request = {};
 			request['Wuid'] = this.wuid;
 			request['rawxml_'] = "1";
 
 			var context = this;
 			xhr.post({
-				url: this.getBaseURL() + "/WUQuery",
-				handleAs: "xml",
+				url: this.getBaseURL() + "/WUQuery.json",
+				handleAs: "json",
 				content: request,
-				load: function (xmlDom) {
-					var workunit = context.getValue(xmlDom, "ECLWorkunit");
+				load: function (response) {
+					var workunit = response.WUQueryResponse.Workunits.ECLWorkunit[0];
 					context.stateID = workunit.StateID;
 					context.state = workunit.State;
 					if (callback) {
@@ -83,9 +81,22 @@ define([
 					}
 
 					if (!context.isComplete()) {
+						var timeout = 30;	// Seconds
+
+						if (monitorDuration < 5) {
+							timeout = 1;
+						} else if (monitorDuration < 10) {
+							timeout = 2;
+						} else if (monitorDuration < 30) {
+							timeout = 5;
+						} else if (monitorDuration < 60) {
+							timeout = 10;
+						} else if (monitorDuration < 120) {
+							timeout = 20;
+						}
 						setTimeout(function () {
-							context.monitor(callback);
-						}, 200);
+							context.monitor(callback, monitorDuration + timeout);
+						}, timeout * 1000);
 					}
 				},
 				error: function () {
@@ -99,56 +110,76 @@ define([
 
 			var context = this;
 			xhr.post({
-				url: this.getBaseURL() + "/WUCreate",
-				handleAs: "xml",
+				url: this.getBaseURL() + "/WUCreate.json",
+				handleAs: "json",
 				content: request,
-				load: function (xmlDom) {
-					context.wuid = context.getValue(xmlDom, "Wuid");
+				load: function (response) {
+					context.wuid = response.WUCreateResponse.Workunit.Wuid;
 					context.onCreate();
 				},
 				error: function () {
 				}
 			});
 		},
-		update: function (ecl) {
+		update: function (ecl, graphName, svg) {
 			var request = {};
 			request['Wuid'] = this.wuid;
-			request['QueryText'] = ecl;
+			if (ecl) {
+				request['QueryText'] = ecl;
+			}
+			if (graphName && svg) {
+				/*
+				request['ApplicationValues'] = {
+					ApplicationValue: {
+						itemcount: 1,
+						Application: "ESPWorkunit.js",
+						Name: graphName + "_SVG",
+						Value: svg
+					}
+				}
+				*/
+				request['ApplicationValues.ApplicationValue.itemcount'] = 1;
+				request['ApplicationValues.ApplicationValue.0.Application'] = "ESPWorkunit.js";
+				request['ApplicationValues.ApplicationValue.0.Name'] = graphName + "_SVG";
+				request['ApplicationValues.ApplicationValue.0.Value'] = svg;
+			}
 			request['rawxml_'] = "1";
 
 			var context = this;
 			xhr.post({
-				url: this.getBaseURL() + "/WUUpdate",
-				handleAs: "xml",
+				url: this.getBaseURL() + "/WUUpdate.json",
+				handleAs: "json",
 				content: request,
-				load: function (xmlDom) {
+				load: function (response) {
 					context.onUpdate();
 				},
-				error: function () {
+				error: function (error) {
 				}
 			});
 		},
 		submit: function (target) {
-			var request = {};
-			request['Wuid'] = this.wuid;
-			request['Cluster'] = target;
+			var request = {
+				Wuid: this.wuid,
+				Cluster: target
+			};
 			request['rawxml_'] = "1";
 
 			var context = this;
 			xhr.post({
-				url: this.getBaseURL() + "/WUSubmit",
-				handleAs: "xml",
+				url: this.getBaseURL() + "/WUSubmit.json",
+				handleAs: "json",
 				content: request,
-				load: function (xmlDom) {
+				load: function (response) {
 					context.onSubmit();
 				},
-				error: function () {
+				error: function (error) {
 				}
 			});
 		},
 		getInfo: function (args) {
 			var request = {
 				Wuid: this.wuid,
+				TruncateEclTo64k: args.onGetText ? false : true,
 				IncludeExceptions: args.onGetExceptions ? true : false,
 				IncludeGraphs: args.onGetGraphs ? true : false,
 				IncludeSourceFiles: false,
@@ -157,51 +188,96 @@ define([
 				IncludeVariables: false,
 				IncludeTimers: args.onGetTimers ? true : false,
 				IncludeDebugValues: false,
-				IncludeApplicationValues: false,
+				IncludeApplicationValues: args.onGetApplicationValues ? true : false,
 				IncludeWorkflows: false,
 				SuppressResultSchemas: args.onGetResults ? false : true,
-				rawxml_: true
 			};
+			request['rawxml_'] = "1";
 
 			var context = this;
 			xhr.post({
-				url: this.getBaseURL() + "/WUInfo",
-				handleAs: "xml",
+				url: this.getBaseURL() + "/WUInfo.json",
+				handleAs: "json",
 				content: request,
-				load: function (xmlDom) {
-					var workunit = context.getValue(xmlDom, "Workunit", ["ECLException", "ECLResult", "ECLGraph", "ECLTimer", "ECLSchemaItem"]);
-					if (workunit.Query.Text && args.onGetText) {
+				load: function (response) {
+					//var workunit = context.getValue(xmlDom, "Workunit", ["ECLException", "ECLResult", "ECLGraph", "ECLTimer", "ECLSchemaItem", "ApplicationValue"]);
+					var workunit = response.WUInfoResponse.Workunit;
+					if (args.onGetText && workunit.Query.Text) {
 						context.text = workunit.Query.Text;
 						args.onGetText(context.text);
 					}
-					if (workunit.Exceptions && args.onGetExceptions) {
-						context.exceptions = workunit.Exceptions;
+					if (args.onGetExceptions && workunit.Exceptions && workunit.Exceptions.ECLException) {
+						context.exceptions = [];
+						for (var i = 0; i < workunit.Exceptions.ECLException.length; ++i) {
+							if (workunit.Exceptions.ECLException[i].Severity == "Error" || 
+								workunit.Exceptions.ECLException[i].Severity == "Warning")
+							context.exceptions.push(workunit.Exceptions.ECLException[i]);						
+						}
 						args.onGetExceptions(context.exceptions);
 					}
-					if (workunit.Results && args.onGetResults) {
+					if (args.onGetApplicationValues && workunit.ApplicationValues && workunit.ApplicationValues.ApplicationValue) {
+						context.applicationValues = workunit.ApplicationValues.ApplicationValue;
+						args.onGetApplicationValues(context.applicationValues)
+					}
+					if (args.onGetResults && workunit.Results && workunit.Results.ECLResult) {
 						context.results = [];
-						var results = workunit.Results;
+						var results = workunit.Results.ECLResult;
 						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;
+					if (args.onGetTimers && workunit.Timers && workunit.Timers.ECLTimer) {
+						context.timers = workunit.Timers.ECLTimer;
 						args.onGetTimers(context.timers);
 					}
-					if (workunit.Graphs && args.onGetGraphs) {
-						context.graphs = workunit.Graphs;
+					if (args.onGetGraphs && workunit.Graphs && workunit.Graphs.ECLGraph) {
+						context.graphs = workunit.Graphs.ECLGraph;
+						if (context.timers || context.applicationValues) {
+							for (var i = 0; i < context.graphs.length; ++i) {
+								if (context.timers) {
+									context.graphs[i].Time = 0;
+									for (var j = 0; j < context.timers.length; ++j) {
+										if (context.timers[j].GraphName == context.graphs[i].Name) {
+											context.graphs[i].Time += parseFloat(context.timers[j].Value);
+										}
+										context.graphs[i].Time = Math.round(context.graphs[i].Time * 1000) / 1000;
+									}
+								}
+								if (context.applicationValues) {
+									var idx = context.getApplicationValueIndex("ESPWorkunit.js", context.graphs[i].Name + "_SVG");
+									if (idx >= 0) {
+										context.graphs[i].svg = context.applicationValues[idx].Value;
+									}
+								}
+							}
+						}
 						args.onGetGraphs(context.graphs)
 					}
 					if (args.onGetAll) {
 						args.onGetAll(workunit);
 					}
 				},
-				error: function () {
+				error: function (e) {
 				}
 			});
 		},
+		getGraphIndex: function (name) {
+			for (var i = 0; i < this.graphs.length; ++i) {
+				if (this.graphs[i].Name == name) {
+					return i;
+				}
+			}
+			return -1;
+		},
+		getApplicationValueIndex: function (application, name) {
+			for (var i = 0; i < this.applicationValues.length; ++i) {
+				if (this.applicationValues[i].Application == application && this.applicationValues[i].Name == name) {
+					return i;
+				}
+			}
+			return -1;
+		},
 		fetchText: function (onFetchText) {
 			if (this.text) {
 				onFetchText(this.text);
@@ -232,6 +308,12 @@ define([
 				onGetGraphs: onFetchGraphs
 			});
 		},
+		fetchGraphXgmmlByName: function (name, onFetchGraphXgmml) {
+			var idx = this.getGraphIndex(name);
+			if (idx >= 0) {
+				this.fetchGraphXgmml(idx, onFetchGraphXgmml);
+			}
+		},
 		fetchGraphXgmml: function (idx, onFetchGraphXgmml) {
 			var request = {};
 			request['Wuid'] = this.wuid;
@@ -240,16 +322,23 @@ define([
 
 			var context = this;
 			xhr.post({
-				url: this.getBaseURL() + "/WUGetGraph",
-				handleAs: "xml",
+				url: this.getBaseURL() + "/WUGetGraph.json",
+				handleAs: "json",
 				content: request,
-				load: function (xmlDom) {
-					context.graphs[idx].xgmml = context.getValue(xmlDom, "Graph");
-					onFetchGraphXgmml(context.graphs[idx].xgmml);
+				load: function (response) {
+					context.graphs[idx].xgmml = response.WUGetGraphResponse.Graphs.ECLGraphEx[0].Graph;
+					onFetchGraphXgmml(context.graphs[idx].xgmml, context.graphs[idx].svg);
 				},
 				error: function () {
 				}
 			});
+		},
+		setGraphSvg: function (graphName, svg) {
+			var idx = this.getGraphIndex(graphName);
+			if (idx >= 0) {
+				this.graphs[idx].svg = svg;
+				this.update(null, graphName, svg);
+			}
 		}
 	});
 });

+ 0 - 186
esp/files/scripts/GraphControl.js

@@ -1,186 +0,0 @@
-/*##############################################################################
-#    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/sniff",
-	"dojo/aspect",
-	"dojo/dom"
-], function (declare, sniff, aspect, dom) {
-	return declare(null, {
-		id: "gvc",
-		width: "",
-		height: "",
-		installed: false,
-		markup: "",
-		obj: {},
-		eventsRegistered: false,
-
-		onInitialize: function () {
-			//  Creater can override.
-		},
-
-		onLayoutFinished: function () {
-			//  Creater can override.
-		},
-
-		onMouseDoubleClick: function (item) {
-			//  Creater can override.
-		},
-
-		onSelectionChanged: function (items) {
-			//  Creater can override.
-		},
-
-		// The constructor    
-		constructor: function (args, parentNode) {
-			declare.safeMixin(this, args);
-
-			if (sniff("ie")) {
-				this.installed = (function () {
-					try {
-						var o = new ActiveXObject("HPCCSystems.HPCCSystemsGraphViewControl.1");
-						return true;
-					} catch (e) { }
-					return false;
-				})();
-
-				if (!this.installed) {
-					this.markup = this.getInstallMarkup();
-				} else {
-					this.markup = '<object type="application/x-hpccsystemsgraphviewcontrol" '
-						+ 'id="' + this.id + '" '
-						+ 'width="' + this.width + '" '
-						+ 'height="' + this.height + '"></object>';
-				}
-			} else {
-				this.installed = (function () {
-					for (var i = 0, p = navigator.plugins, l = p.length; i < l; i++) {
-						if (p[i].name.indexOf("HPCCSystemsGraphViewControl") > -1) {
-							return true;
-						}
-					}
-					return false;
-				})();
-
-				if (!this.installed) {
-					this.markup = this.getInstallMarkup();
-				} else {
-					this.markup = '<embed type="application/x-hpccsystemsgraphviewcontrol" '
-						+ 'id="' + this.id + '" '
-						+ 'name="' + this.id + '" '
-						+ 'width="' + this.width + '" '
-						+ 'height="' + this.height + '"></embed>';
-				}
-			}
-			parentNode.innerHTML = this.markup;
-			this.obj = dom.byId(this.id);
-			var context = this;
-			setTimeout(function () {
-				context.onInitialize();
-			}, 20);
-		},
-
-		getInstallMarkup: function () {
-			return "<h4>Graph View</h4>" +
-			"<p>To enable graph views, please install the Graph View Control plugin:</p>" +
-			"<a href=\"http://graphcontrol.hpccsystems.com/stable/SetupGraphControl.msi\">Internet Explorer + Firefox (32bit)</a><br>" +
-			"<a href=\"http://graphcontrol.hpccsystems.com/stable/SetupGraphControl64.msi\">Internet Explorer + Firefox (64bit)</a><br>" +
-			"<a href=\"https://github.com/hpcc-systems/GraphControl\">Linux/Other (sources)</a>";
-		},
-
-		clear: function () {
-			if (this.obj) {
-				this.obj.clear();
-			}
-		},
-
-		loadXGMML: function (xgmml, merge) {
-			if (this.obj) {
-				this.registerEvents();
-				this.obj.setMessage("Loading Data...");
-				if (merge)
-					this.obj.mergeXGMML(xgmml);
-				else
-					this.obj.loadXGMML(xgmml);
-				this.obj.setMessage("Performing Layout...");
-				this.obj.startLayout("dot");
-			}
-		},
-
-		loadDOT: function (dot) {
-			this.load(dot, "dot");
-		},
-
-		load: function (dot, layout) {
-			if (this.obj) {
-				this.registerEvents();
-				this.obj.setMessage("Loading Data...");
-				this.obj.loadDOT(dot);
-				this.obj.setMessage("Performing Layout...");
-				this.obj.startLayout(layout);
-			}
-		},
-
-		setLayout: function (layout) {
-			if (this.obj) {
-				this.registerEvents();
-				this.obj.setMessage("Performing Layout...");
-				this.obj.startLayout(layout);
-			}
-		},
-
-		centerOn: function (globalID) {
-			var item = this.obj.getItem(globalID);
-			this.obj.centerOnItem(item, true);
-			var items = [item];
-			this.obj.setSelected(items, true);
-		},
-
-		watchSelect: function (select) {
-			if (sniff("chrome") && select) {
-				var context = this;
-
-				aspect.before(select, "openDropDown", function () {
-					dojo.style(context.obj, "height", "0px");
-				});
-
-				aspect.after(select, "closeDropDown", function (focus) {
-					dojo.style(context.obj, "height", "100%");
-				});
-			}
-		},
-
-		registerEvents: function () {
-			if (!this.eventsRegistered) {
-				this.eventsRegistered = true;
-				this.registerEvent("LayoutFinished", this.onLayoutFinished);
-				this.registerEvent("MouseDoubleClick", this.onMouseDoubleClick);
-				this.registerEvent("SelectionChanged", this.onSelectionChanged);
-			}
-		},
-
-		registerEvent: function (evt, func) {
-			if (this.obj) {
-				if (sniff("ie")) {
-					this.obj.attachEvent("on" + evt, func);
-				} else {
-					this.obj.addEventListener(evt, func, false);
-				}
-			}
-		}
-	});
-});

+ 369 - 0
esp/files/scripts/GraphPageWidget.js

@@ -0,0 +1,369 @@
+/*##############################################################################
+#    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/>.
+############################################################################## */
+require([
+    "dojo/_base/declare",
+    "dojo/_base/sniff",
+    "dojo/_base/array",
+    "dojo/dom",
+    "dojo/dom-construct",
+    "dojo/on",
+    "dojo/store/Memory",
+    "dojo/data/ObjectStore",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/layout/BorderContainer",
+    "dijit/layout/TabContainer",
+    "dijit/layout/ContentPane",
+    "dijit/registry",
+    "dijit/Dialog",
+
+    "dojox/grid/DataGrid",
+
+    "hpcc/GraphWidget",
+    "hpcc/ESPWorkunit",
+    "dojo/text!./templates/GraphPageWidget.html",
+
+    "dijit/form/Select",
+    "dijit/form/TextBox"
+], function (declare, sniff, array, dom, domConstruct, on, Memory, ObjectStore,
+            _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, TabContainer, ContentPane, registry, Dialog,
+            DataGrid, GraphWidget, ESPWorkunit, template) {
+    return declare("GraphPageWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "GraphPageWidget",
+        borderContainer: null,
+        rightBorderContainer: null,
+        graphName: "",
+        wu: null,
+        editorControl: null,
+        main: null,
+        overview: null,
+        local: null,
+        graphSelect: null,
+        timingGrid: null,
+        findField: null,
+        findText: "",
+        found: [],
+        foundIndex: 0,
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this._initControls();
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+
+            var splitter = this.borderContainer.getSplitter("right");
+            this.main.watchSplitter(splitter);
+            this.overview.watchSplitter(splitter);
+            this.local.watchSplitter(splitter);
+
+            splitter = this.rightBorderContainer.getSplitter("bottom");
+            this.main.watchSplitter(splitter);
+            this.overview.watchSplitter(splitter);
+            this.local.watchSplitter(splitter);
+
+            this.main.watchSelect(this.graphSelect);
+            this.overview.watchSelect(this.graphSelect);
+            this.local.watchSelect(this.graphSelect);
+
+            this.overview.watchStyleChange();
+            this.local.watchStyleChange();
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.borderContainer.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        //  Implementation  ---
+        _initControls: function () {
+            this.borderContainer = registry.byId(this.id + "BorderContainer");
+            this.rightBorderContainer = registry.byId(this.id + "RightBorderContainer");
+            this.findField = registry.byId(this.id + "FindField");
+            this._initGraphControls();
+            this._initGraphSelect()
+            this._initTimings();
+            //this._initProperties();
+        },
+
+        _initGraphControls: function () {
+            var context = this;
+            this.main = registry.byId(this.id + "MainGraphWidget");
+            this.main.onSelectionChanged = function (items) {
+                context.syncSelectionFrom(context.main);
+            };
+            this.main.onLayoutFinished = function () {
+                context.wu.setGraphSvg(context.graphName, context.main.svg);
+            };
+
+            this.overview = registry.byId(this.id + "MiniGraphWidget");
+            this.overview.onSelectionChanged = function (items) {
+                context.syncSelectionFrom(context.overview);
+            };
+            this.overview.onDoubleClick = function (globalID) {
+                var mainItem = context.main.getItem(globalID);
+                context.main.centerOnItem(mainItem, true);
+            };
+
+            this.local = registry.byId(this.id + "LocalGraphWidget");
+            this.local.onSelectionChanged = function (items) {
+                context.syncSelectionFrom(context.local);
+            };
+            this.local.onDoubleClick = function (globalID) {
+                var mainItem = context.main.getItem(globalID);
+                context.main.centerOnItem(mainItem, true);
+            };
+        },
+
+        _initGraphSelect: function () {
+            this.graphSelect = registry.byId(this.id + "GraphSelect");
+            var context = this;
+            this.graphSelect.onChange = function () {
+                context.graphName = this.getValue();
+                context.loadGraph(context.wu, context.graphName);
+                context.timingGrid.setQuery({
+                    GraphName: context.graphName
+                });
+            }
+        },
+
+        _initTimings: function () {
+            this.timingGrid = registry.byId(this.id + "TimingsGrid");
+
+            var context = this;
+            this.timingGrid.on("RowClick", function (evt) {
+                context.syncSelectionFrom(context.timingGrid);
+            }, true);
+
+            var context = this;
+            this.timingGrid.on("RowDblClick", function (evt) {
+                var idx = evt.rowIndex;
+                var item = this.getItem(idx);
+                if (this.store.getValue(item, "SubGraphId")) {
+                    var subgraphID = parseInt(this.store.getValue(item, "SubGraphId"), 10);
+                    var mainItem = context.main.getItem(subgraphID);
+                    context.main.centerOnItem(mainItem, true);
+                }
+            }, true);
+        },
+
+        _initProperties: function () {
+            this.propertyGrid = new DataGrid({
+                //store: objStore,
+                query: { id: "*" },
+                structure: [
+                { name: "Property", field: "key", width: "auto" },
+                { name: "Value", field: "value", width: "auto" }
+                ]
+            });
+            this.propertyGrid.placeAt(this.id + "Properties");
+            this.propertyGrid.startup();
+        },
+
+        _onLayout: function () {
+            this.main.setMessage("Performing Layout...");
+            this.main.startLayout("dot");
+        },
+
+        _onLocalSync: function () {
+            this.syncSelectionFrom(this.main);
+        },
+
+        _doFind: function (prev) {
+            if (this.findText != this.findField.value) {
+                this.findText = this.findField.value;
+                this.found = this.main.find(this.findText);
+                this.main.setSelected(this.found);
+                this.syncSelectionFrom(this.main);
+                this.foundIndex = -1;
+            }
+            this.foundIndex += prev ? -1 : +1;
+            if (this.foundIndex < 0) {
+                this.foundIndex = this.found.length - 1;
+            } else if (this.foundIndex >= this.found.length) {
+                this.foundIndex = 0;
+            }
+            if (this.found.length) {
+                this.main.centerOnItem(this.found[this.foundIndex], true);
+            }
+        },
+
+        _onFind: function (prev) {
+            this.findText = "";
+            this._doFind(false);
+        },
+
+        _onFindNext: function () {
+            this._doFind(false);
+        },
+
+        _onFindPrevious: function () {
+            this._doFind(true);
+        },
+
+        setWuid: function (wuid, graphName) {
+            this.graphName = graphName;
+            this.wu = new ESPWorkunit({
+                wuid: wuid
+            });
+
+            var firstLoad = true;
+            var context = this;
+            this.wu.monitor(function () {
+                context.wu.getInfo({
+                    onGetApplicationValues: function (applicationValues) {
+                    },
+                    onGetGraphs: function (graphs) {
+                        if (firstLoad == true) {
+                            firstLoad = false;
+                            context.loadGraphSelect(graphs);
+                        } else {
+                            context.refreshGraph(context.wu, context.graphName);
+                        }
+                    },
+                    onGetTimers: function (timers) {
+                        context.loadTimings(timers);
+                    }
+                });
+            });
+        },
+
+        loadGraphSelect: function (graphs) {
+            this.graphSelect.options = [];
+            for (var i = 0; i < graphs.length; ++i) {
+                if (!this.graphName) {
+                    this.graphName = graphs[i].Name;
+                }
+                this.graphSelect.options.push({
+                    label: graphs[i].Name + (graphs[i].Time ? " (" + graphs[i].Time + ")" : ""),
+                    value: graphs[i].Name
+                });
+            }
+            this.graphSelect.setValue(this.graphName);
+        },
+
+        loadGraph: function (wu, graphName) {
+            var context = this;
+            wu.fetchGraphXgmmlByName(graphName, function (xgmml, svg) {
+                context.main.setMessage("Loading Data...");
+                context.main.loadXGMML(xgmml);
+                context.overview.loadXGMML(context.main.getLocalisedXGMML([0]));
+                if (svg) {
+                    context.main.setMessage("Loading Layout...");
+                    if (context.main.mergeSVG(svg)) {
+                        context.main.centerOnItem(0, true);
+                        context.main.setMessage("");
+                        return;
+                    }
+                }
+                context.main.setMessage("Performing Layout...");
+                context.main.startLayout("dot");
+            });
+        },
+
+        refreshGraph: function (wu, graphName) {
+            var context = this;
+            wu.fetchGraphXgmmlByName(graphName, function (xgmml) {
+                context.main.mergeXGMML(xgmml);
+            });
+        },
+
+        loadTimings: function (timers) {
+            var store = new Memory({ data: timers });
+            var dataStore = new ObjectStore({ objectStore: store });
+            this.timingGrid.setStore(dataStore);
+            this.timingGrid.setQuery({
+                GraphName: this.graphName
+            });
+        },
+
+        syncSelectionFrom: function (sourceControl) {
+            var selItems = [];
+            if (sourceControl == this.timingGrid) {
+                var items = sourceControl.selection.getSelected();
+                for (var i = 0; i < items.length; ++i) {
+                    if (items[i].SubGraphId) {
+                        selItems.push(parseInt(items[i].SubGraphId, 10));
+                    }
+                }
+            } else {
+                selItems = sourceControl.getSelectionAsGlobalID();
+            }
+
+            if (sourceControl != this.timingGrid && this.timingGrid.store) {
+                for (var i = 0; i < this.timingGrid.store.objectStore.data.length; ++i) {
+                    var row = this.timingGrid.store.objectStore.data[i];
+                    this.timingGrid.selection.setSelected(i, (row.SubGraphId && array.indexOf(selItems, row.SubGraphId) != -1));
+                }
+            }
+            if (sourceControl != this.main)
+                this.main.setSelectedAsGlobalID(selItems);
+            if (sourceControl != this.overview)
+                this.overview.setSelectedAsGlobalID(selItems);
+
+            var mainItems = [];
+            for (var i = 0; i < selItems.length; ++i) {
+                mainItems.push(this.main.getItem(selItems[i]));
+            }
+
+            if (sourceControl != this.local) {
+                var xgmml = this.main.getLocalisedXGMML(mainItems, 2);
+                this.local.loadXGMML(xgmml);
+                this.local.setSelectedAsGlobalID(selItems);
+            }
+
+            var propertiesDom = dom.byId(this.id + "Properties");
+            propertiesDom.innerHTML = "";
+            for (var i = 0; i < mainItems.length; ++i) {
+                this.main.displayProperties(mainItems[i], propertiesDom);
+            }
+        },
+
+        showHelpAbout: function () {
+            myDialog = new Dialog({
+                title: "About Graph sourceControl",
+                content: "Version:  " + main.getVersion(),
+                style: "width: 300px"
+            });
+            myDialog.show();
+        },
+
+        resetPage: function () {
+            this.main.clear();
+        },
+
+        displayGraphs: function (graphs) {
+            for (var i = 0; i < graphs.length; ++i) {
+                this.wu.fetchGraphXgmml(i, function (xgmml) {
+                    this.main.loadXGMML(xgmml, true);
+                });
+            }
+        }
+    });
+});

+ 459 - 0
esp/files/scripts/GraphWidget.js

@@ -0,0 +1,459 @@
+/*##############################################################################
+#    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/>.
+############################################################################## */
+require([
+    "dojo/_base/declare",
+    "dojo/aspect",
+    "dojo/has",
+    "dojo/dom",
+    "dojo/dom-construct",
+    "dojo/dom-class",
+
+    "dijit/layout/_LayoutWidget",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/layout/BorderContainer",
+    "dijit/layout/ContentPane",
+
+    "dojo/text!./templates/GraphWidget.html",
+
+    "dijit/Toolbar", "dijit/ToolbarSeparator", "dijit/form/Button"
+],
+    function (declare, aspect, has, dom, domConstruct, domClass,
+            _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, BorderContainer, ContentPane,
+            template) {
+        return declare("GraphWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+            templateString: template,
+            baseClass: "GraphWidget",
+            borderContainer: null,
+            graphContentPane: null,
+            _isPluginInstalled: false,
+            plugin: null,
+            eventsRegistered: false,
+            xgmml: "",
+            dot: "",
+            svg: "",
+
+            _onClickZoomAll: function (args) {
+                this.centerOnItem(0, true);
+            },
+
+            _onClickZoomWidth: function (args) {
+                this.centerOnItem(0, true, true);
+            },
+
+            onSelectionChanged: function (items) {
+            },
+
+            onDoubleClick: function (globalID) {
+            },
+
+            onLayoutFinished: function () {
+            },
+
+            buildRendering: function (args) {
+                this.inherited(arguments);
+            },
+
+            postCreate: function (args) {
+                this.inherited(arguments);
+                this.borderContainer = dijit.byId(this.id + "BorderContainer");
+                this.graphContentPane = dijit.byId(this.id + "GraphContentPane");
+            },
+
+            startup: function (args) {
+                this.inherited(arguments);
+                this._isPluginInstalled = this.isPluginInstalled();
+                this.createPlugin();
+            },
+
+            resize: function (args) {
+                this.inherited(arguments);
+                this.borderContainer.resize();
+            },
+
+            layout: function (args) {
+                this.inherited(arguments);
+            },
+
+            //  Plugin wrapper  ---
+            clear: function () {
+                if (this.plugin) {
+                    this.plugin.clear();
+                }
+            },
+
+            loadXGMML: function (xgmml, merge) {
+                if (this.plugin && this.xgmml != xgmml) {
+                    this.setMessage("Loading Data...");
+                    if (merge)
+                        this.plugin.mergeXGMML(xgmml);
+                    else
+                        this.plugin.loadXGMML(xgmml);
+                    this.setMessage("Performing Layout...");
+                    this.plugin.startLayout("dot");
+                    this.xgmml = xgmml;
+                }
+            },
+
+            mergeXGMML: function (xgmml) {
+                if (this.plugin && this.xgmml != xgmml) {
+                    this.plugin.mergeXGMML(xgmml);
+                    this.xgmml = xgmml;
+                }
+            },
+
+            loadDOT: function (dot) {
+                this.load(dot, "dot");
+            },
+
+            load: function (dot, layout) {
+                if (this.plugin && this.xgmml != xgmml) {
+                    this.setMessage("Loading Data...");
+                    this.plugin.loadDOT(dot);
+                    this.setMessage("Performing Layout...");
+                    this.plugin.startLayout(layout);
+                    this.xgmml = xgmml;
+                }
+            },
+
+            setLayout: function (layout) {
+                if (this.plugin) {
+                    this.setMessage("Performing Layout...");
+                    this.plugin.startLayout(layout);
+                }
+            },
+
+            centerOn: function (globalID) {
+                var item = this.plugin.getItem(globalID);
+                this.plugin.centerOnItem(item, true);
+                var items = [item];
+                this.plugin.setSelected(items, true);
+            },
+
+            getVersion: function () {
+                return this.plugin.version;
+            },
+
+            displayProperties: function (item, place) {
+                var props = this.plugin.getProperties(item);
+                if (props.id) {
+                    var table = domConstruct.create("h3", {
+                        innerHTML: props.id,
+                        align: "center"
+                    }, place);
+                    delete props.id;
+                }
+                if (props.count) {
+                    var table = domConstruct.create("table", { border: 1, cellspacing: 0, width: "100%" }, place);
+                    var tr = domConstruct.create("tr", null, table);
+                    var td = domConstruct.create("td", { innerHTML: "Count" }, tr);
+                    var td = domConstruct.create("td", {
+                        align: "right",
+                        innerHTML: props.count
+                    }, tr);
+                    delete props.count;
+                    domConstruct.create("br", null, place);
+                }
+                if (props.max) {
+                    var table = domConstruct.create("table", { border: 1, cellspacing: 0, width: "100%" }, place);
+                    var tr = domConstruct.create("tr", null, table);
+                    domConstruct.create("th", { innerHTML: "    " }, tr);
+                    domConstruct.create("th", { innerHTML: "Skew" }, tr);
+                    domConstruct.create("th", { innerHTML: "Node" }, tr);
+                    domConstruct.create("th", { innerHTML: "Rows" }, tr);
+                    tr = domConstruct.create("tr", null, table);
+                    domConstruct.create("td", { innerHTML: "Max" }, tr);
+                    domConstruct.create("td", { innerHTML: props.maxskew }, tr);
+                    domConstruct.create("td", { innerHTML: props.maxEndpoint }, tr);
+                    domConstruct.create("td", { innerHTML: props.max }, tr);
+                    tr = domConstruct.create("tr", null, table);
+                    domConstruct.create("td", { innerHTML: "Min" }, tr);
+                    domConstruct.create("td", { innerHTML: props.minskew }, tr);
+                    domConstruct.create("td", { innerHTML: props.minEndpoint }, tr);
+                    domConstruct.create("td", { innerHTML: props.min }, tr);
+                    delete props.maxskew;
+                    delete props.maxEndpoint;
+                    delete props.max;
+                    delete props.minskew;
+                    delete props.minEndpoint;
+                    delete props.min;
+                    domConstruct.create("br", null, place);
+                }
+                if (props.slaves) {
+                    var table = domConstruct.create("table", { border: 1, cellspacing: 0, width: "100%" }, place);
+                    var tr = domConstruct.create("tr", null, table);
+                    domConstruct.create("th", { innerHTML: "Slaves" }, tr);
+                    domConstruct.create("th", { innerHTML: "Started" }, tr);
+                    domConstruct.create("th", { innerHTML: "Stopped" }, tr);
+                    tr = domConstruct.create("tr", null, table);
+                    domConstruct.create("td", { innerHTML: props.slaves }, tr);
+                    domConstruct.create("td", { innerHTML: props.started }, tr);
+                    domConstruct.create("td", { innerHTML: props.stopped }, tr);
+                    delete props.slaves;
+                    delete props.started;
+                    delete props.stopped;
+                    domConstruct.create("br", null, place);
+                }
+                var first = true;
+                var table = {};
+                var tr = {};
+                for (key in props) {
+                    if (key[0] == "_")
+                        continue;
+
+                    if (first) {
+                        first = false;
+                        table = domConstruct.create("table", { border: 1, cellspacing: 0, width: "100%" }, place);
+                        tr = domConstruct.create("tr", null, table);
+                        domConstruct.create("th", { innerHTML: "Property" }, tr);
+                        domConstruct.create("th", { innerHTML: "Value" }, tr);
+                    }
+                    tr = domConstruct.create("tr", null, table);
+                    domConstruct.create("td", { innerHTML: key }, tr);
+                    domConstruct.create("td", { innerHTML: props[key] }, tr);
+                }
+                if (first == false) {
+                    domConstruct.create("br", null, place);
+                }
+            },
+
+            isPluginInstalled: function () {
+                if (has("ie")) {
+                    try {
+                        var o = new ActiveXObject("HPCCSystems.HPCCSystemsGraphViewControl.1");
+                        o = null;
+                        return true;
+                    } catch (e) { }
+                    return false;
+                } else {
+                    for (var i = 0, p = navigator.plugins, l = p.length; i < l; i++) {
+                        if (p[i].name.indexOf("HPCCSystemsGraphViewControl") > -1) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            },
+
+            createPlugin: function () {
+                var pluginID = this.id + "Plugin";
+                if (this.plugin == null) {
+                    if (this._isPluginInstalled) {
+                        if (has("ie")) {
+                            this.graphContentPane.domNode.innerHTML = '<object '
+                                                    + 'type="application/x-hpccsystemsgraphviewcontrol" '
+                                                    + 'id="' + pluginID + '" '
+                                                    + 'name="' + pluginID + '" '
+                                                    + 'width="100%" '
+                                                    + 'height="100%">'
+                                                    + '</object>';
+                        } else {
+                            this.graphContentPane.domNode.innerHTML = '<embed type="application/x-hpccsystemsgraphviewcontrol" '
+                                                    + 'id="' + pluginID + '" '
+                                                    + 'name="' + pluginID + '" '
+                                                    + 'width="100%" '
+                                                    + 'height="100%">'
+                                                    + '</embed>';
+                        }
+                        this.plugin = dom.byId(pluginID);
+                        var context = this;
+                        setTimeout(function () {
+                            context.registerEvents();
+                        }, 20);
+                    } else {
+                        domConstruct.create("div", {
+                            innerHTML: "<h4>Graph View</h4>" +
+                                        "<p>To enable graph views, please install the Graph View Control plugin:</p>" +
+                                        "<a href=\"http://graphcontrol.hpccsystems.com/stable/SetupGraphControl.msi\">Internet Explorer + Firefox (32bit)</a><br>" +
+                                        "<a href=\"http://graphcontrol.hpccsystems.com/stable/SetupGraphControl64.msi\">Internet Explorer + Firefox (64bit)</a><br>" +
+                                        "<a href=\"https://github.com/hpcc-systems/GraphControl\">Linux/Other (sources)</a>"
+                        }, this.graphContentPane.domNode);
+                    }
+                }
+            },
+
+            setMessage: function (message) {
+                if (this.plugin) {
+                    return this.plugin.setMessage(message);
+                }
+                return null;
+            },
+
+            getLocalisedXGMML: function (items) {
+                if (this.plugin) {
+                    return this.plugin.getLocalisedXGMML(items);
+                }
+                return null;
+            },
+
+            mergeSVG: function (svg) {
+                if (this.plugin) {
+                    return this.plugin.mergeSVG(svg);
+                }
+                return null;
+            },
+
+            startLayout: function (layout) {
+                if (this.plugin) {
+                    return this.plugin.startLayout(layout);
+                }
+                return null;
+            },
+
+            find: function (findText) {
+                if (this.plugin) {
+                    return this.plugin.find(findText);
+                }
+                return [];
+            },
+
+            centerOnItem: function (item, scaleToFit, widthOnly) {
+                if (this.plugin) {
+                    return this.plugin.centerOnItem(item, scaleToFit, widthOnly);
+                }
+                return null;
+            },
+
+            setSelected: function (items) {
+                if (this.plugin) {
+                    return this.plugin.setSelected(items);
+                }
+                return null;
+            },
+
+            setSelectedAsGlobalID: function (items) {
+                if (this.plugin) {
+                    return this.plugin.setSelectedAsGlobalID(items);
+                }
+                return null;
+            },
+
+            getSelectionAsGlobalID: function () {
+                if (this.plugin) {
+                    return this.plugin.getSelectionAsGlobalID();
+                }
+                return [];
+            },
+
+            getItem: function (globalID) {
+                if (this.plugin) {
+                    return this.plugin.getItem(globalID);
+                }
+                return null;
+            },
+
+            watchSplitter: function (splitter) {
+                if (has("chrome")) {
+                    //  Chrome can ignore splitter events
+                    return;
+                }
+                var context = this;
+                dojo.connect(splitter, "_startDrag", function () {
+                    if (context.plugin) {
+                        dojo.style(context.plugin, "width", "1px");
+                        dojo.style(context.plugin, "height", "1px");
+                    }
+                });
+                dojo.connect(splitter, "_stopDrag", function (evt) {
+                    if (context.plugin) {
+                        dojo.style(context.plugin, "width", "100%");
+                        dojo.style(context.plugin, "height", "100%");
+                    }
+                });
+            },
+
+            watchSelect: function (select) {
+                if (has("chrome") && select) {
+                    //  Only chrome needs to monitor select drop downs.
+                    var context = this;
+                    select.watch("_opened", function () {
+                        if (context.plugin) {
+                            if (select._opened) {
+                                dojo.style(context.plugin, "width", "1px");
+                                dojo.style(context.plugin, "height", "1px");
+                            } else {
+                                dojo.style(context.plugin, "width", "100%");
+                                dojo.style(context.plugin, "height", "100%");
+                            }
+                        }
+                    });
+                }
+            },
+
+            watchStyleChange: function () {
+                if (has("chrome")) {
+                    var context = this;
+                    aspect.around(domClass, "replace", function (origFunc) {
+                        return function (node, addStyle, removeStyle) {
+                            if (node.id == context.id) {
+                                if (addStyle == "dijitHidden") {
+                                    context.hiddenBySelf = true;
+                                    dojo.style(node, "width", "1px");
+                                    dojo.style(node, "height", "1px");
+                                } else if (addStyle == "dijitVisible" && context.hiddenBySelf) {
+                                    context.hiddenBySelf = false;
+                                    dojo.style(node, "width", "100%");
+                                    dojo.style(node, "height", "100%");
+                                } else {
+                                    var deferred = origFunc(node, addStyle, removeStyle);
+                                    //  alternative:  return origFunc.apply(this, arguments);
+                                    return deferred;
+                                }
+                            } else {
+                                var deferred = origFunc(node, addStyle, removeStyle);
+                                //  alternative:  return origFunc.apply(this, arguments);
+                                return deferred;
+                            }
+                        }
+                    });
+                }
+            },
+
+            registerEvents: function () {
+                if (!this.eventsRegistered) {
+                    this.eventsRegistered = true;
+                    var context = this;
+                    this.registerEvent("MouseDoubleClick", function (item) {
+                        context.plugin.centerOnItem(item, true);
+                        context.onDoubleClick(context.plugin.getGlobalID(item));
+                    });
+                    this.registerEvent("LayoutFinished", function () {
+                        context.plugin.centerOnItem(0, true);
+                        context.setMessage('');
+                        context.dot = context.plugin.getDOT();
+                        context.svg = context.plugin.getSVG();
+                        context.onLayoutFinished();
+                    });
+                    this.registerEvent("SelectionChanged", function (items) {
+                        context.onSelectionChanged(items);
+                    });
+                }
+            },
+
+            registerEvent: function (evt, func) {
+                if (this.plugin) {
+                    if (this.plugin.attachEvent) {
+                        return this.plugin.attachEvent("on" + evt, func);
+                    } else {
+                        return this.plugin.addEventListener(evt, func, false);
+                    }
+                }
+                return false;
+            }
+
+        });
+    });

+ 6 - 2
esp/files/scripts/ResultsControl.js

@@ -18,14 +18,18 @@ define([
 	"dojo/_base/declare",
 	"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, Memory, ObjectStore, DataGrid, EnhancedGrid, Pagination, Filter, NestedSorting, registry, ContentPane) {
+], function (declare, Memory, ObjectStore,
+			DataGrid, EnhancedGrid, Pagination, Filter, NestedSorting,
+			registry, ContentPane) {
 	return declare(null, {
 		workunit: null,
 		paneNum: 0,
@@ -166,7 +170,7 @@ define([
 						{ name: "Column", field: "Column" },
 						{ name: "Code", field: "Code" },
 						{ name: "Message", field: "Message", width: "auto" }
-						]
+					]
 				});
 				grid.placeAt(resultNode.containerNode, "last");
 				grid.startup();

+ 111 - 0
esp/files/scripts/ResultsWidget.js

@@ -0,0 +1,111 @@
+/*##############################################################################
+#    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/>.
+############################################################################## */
+require([
+	"dojo/_base/declare",
+	"dojo/_base/xhr",
+	"dojo/dom",
+
+	"dijit/layout/_LayoutWidget",
+	"dijit/_TemplatedMixin",
+	"dijit/_WidgetsInTemplateMixin",
+	"dijit/layout/TabContainer",
+	"dijit/registry",
+
+	"hpcc/ESPBase",
+	"hpcc/ESPWorkunit",
+	"hpcc/ResultsControl",
+	"dojo/text!./templates/ResultsWidget.html"
+], function (declare, xhr, dom,
+				_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, TabContainer, registry,
+				ESPBase, ESPWorkunit, ResultsControl,
+				template) {
+    return declare("ResultsWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "ResultsWidget",
+
+        resultsPane: null,
+        resultsControl: null,
+
+        buildRendering: function (args) {
+            this.inherited(arguments);
+        },
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this._initControls();
+            this.resultsPane.resize();
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+            this.resultsPane.resize();
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        //  Implementation  ---
+        onErrorClick: function (line, col) {
+        },
+
+        _initControls: function () {
+            var context = this;
+            this.resultsPane = registry.byId(this.id + "ResultsPane");
+
+            var context = this;
+            this.resultsControl = new ResultsControl({
+                resultsSheetID: this.id + "ResultsPane",
+                sequence: 0,
+                onErrorClick: function (line, col) {
+                    context.onErrorClick(line, col);
+                }
+            });
+        },
+
+        clear: function () {
+            this.resultsControl.clear();
+        },
+
+        refresh: function (wu) {
+            this.resultsControl.refresh(wu);
+        },
+
+        init: function (wuid, sequence) {
+            if (wuid) {
+                this.wu = new ESPWorkunit({
+                    wuid: wuid
+                });
+                var monitorCount = 4;
+                var context = this;
+                this.wu.monitor(function () {
+                    if (context.wu.isComplete() || ++monitorCount % 5 == 0) {
+                        context.wu.getInfo({
+                            onGetResults: function (results) {
+                                context.refresh(context.wu);
+                            }
+                        });
+                    }
+                });
+            }
+        }
+    });
+});

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

@@ -23,7 +23,7 @@ define([
 	return declare(null, {
 		id: null,
 		samplesURL: null,
-		select: null,
+		selectControl: null,
 
 		onNewSelection: function (eclText) {
 		},
@@ -35,7 +35,7 @@ define([
 			});
 
 			var context = this;
-			this.select = new dijit.form.Select({
+			this.selectControl = new dijit.form.Select({
 				name: this.id,
 				store: sampleStore,
 				value: "default.ecl",
@@ -57,7 +57,7 @@ define([
 				}
 			}, this.id);
 			try {
-				this.select.startup();
+				this.selectControl.startup();
 			} catch (e) {
 			}
 		}

+ 88 - 0
esp/files/scripts/SampleSelectWidget.js

@@ -0,0 +1,88 @@
+/*##############################################################################
+#    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/>.
+############################################################################## */
+require([
+	"dojo/_base/declare",
+	"dojo/_base/xhr",
+	"dojo/dom",
+
+	"dijit/layout/_LayoutWidget",
+	"dijit/_TemplatedMixin",
+	"dijit/_WidgetsInTemplateMixin",
+	"dijit/form/Select",
+	"dijit/registry",
+
+	"hpcc/ESPBase",
+	"dojo/text!./templates/SampleSelectWidget.html"
+], function (declare, xhr, dom,
+					_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, Select, registry,
+					ESPBase, template) {
+    return declare("SampleSelectWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "SampleSelectWidget",
+
+        selectControl: null,
+        _value: "",
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this._initControls();
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        //  Implementation  ---
+        _initControls: function () {
+            var first = true;
+            var context = this;
+            this.selectControl = registry.byId(this.id + "SampleSelect");
+            this.selectControl.maxHeight = 480;
+            this.selectControl.onChange = function () {
+                if (first) {
+                    first = false;
+                    context.selectControl.set("value", "default.ecl");
+                    return;
+                }
+                var filename = this.getValue();
+                xhr.get({
+                    url: "ecl/" + filename,
+                    handleAs: "text",
+                    load: function (eclText) {
+                        context.onNewSelection(eclText);
+                    },
+                    error: function () {
+                    }
+                });
+            };
+        },
+
+        load: function () {
+            var sampleStore = new dojo.data.ItemFileReadStore({
+                url: "ecl/ECLPlaygroundSamples.json"
+            });
+            this.selectControl.setStore(sampleStore);
+        },
+
+        onNewSelection: function (eclText) {
+        }
+    });
+});

+ 119 - 0
esp/files/scripts/TargetSelectWidget.js

@@ -0,0 +1,119 @@
+/*##############################################################################
+#    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/>.
+############################################################################## */
+require([
+	"dojo/_base/declare",
+	"dojo/_base/xhr",
+	"dojo/dom",
+
+	"dijit/layout/_LayoutWidget",
+	"dijit/_TemplatedMixin",
+	"dijit/_WidgetsInTemplateMixin",
+	"dijit/form/Select",
+	"dijit/registry",
+
+	"hpcc/ESPBase",
+	"dojo/text!./templates/TargetSelectWidget.html"
+], function (declare, xhr, dom,
+					_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin, Select, registry,
+					ESPBase, template) {
+    return declare("TargetSelectWidget", [_LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
+        templateString: template,
+        baseClass: "TargetSelectWidget",
+
+        targetSelectControl: null,
+        _value: "",
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this._initControls();
+        },
+
+        resize: function (args) {
+            this.inherited(arguments);
+        },
+
+        layout: function (args) {
+            this.inherited(arguments);
+        },
+
+        //  Implementation  ---
+        _initControls: function () {
+            var context = this;
+            this.targetSelectControl = registry.byId(this.id + "TargetSelect");
+            this.targetSelectControl.onChange = function () {
+                context.onChange(this.get("value"));
+            };
+            this.loadTargets();
+        },
+
+        onChange: function (target) {
+            this._value = target;
+        },
+
+        setValue: function (target) {
+            if (target && this._value != target) {
+                this._value = target;
+                this.targetSelectControl.set("value", target);
+            }
+        },
+
+        getValue: function () {
+            return this._value;
+        },
+
+        loadTargets: function () {
+            var base = new ESPBase({
+            });
+            var request = {
+                rawxml_: true
+            };
+            var context = this;
+            xhr.post({
+                url: base.getBaseURL("WsTopology") + "/TpTargetClusterQuery",
+                handleAs: "xml",
+                content: request,
+                load: function (xmlDom) {
+                    var targetData = base.getValues(xmlDom, "TpTargetCluster");
+
+                    context.targetSelectControl.options = [];
+                    var has_hthor = false;
+                    for (var i = 0; i < targetData.length; ++i) {
+                        context.targetSelectControl.options.push({
+                            label: targetData[i].Name,
+                            value: targetData[i].Name
+                        });
+                        if (targetData[i].Name == "hthor") {
+                            has_hthor = true;
+                        }
+                    }
+
+                    if (context._value == "") {
+                        if (has_hthor) {
+                            context.setValue("hthor");
+                        } else {
+                            context._value = context.targetSelectControl.options[0].value;
+                        }
+                    } else {
+                        context.targetSelectControl.set("value", context._value);
+                    }
+                },
+                error: function () {
+                }
+            });
+        }
+    });
+});

+ 4 - 4
esp/files/scripts/WsWorkunits.js

@@ -64,12 +64,12 @@ define([
 					return data.total;
 				})
 			});
-	
+
 			return QueryResults(parsedResults);
 		}
 	});
 
-	var WUResult =  declare(ESPBase, {
+	var WUResult = declare(ESPBase, {
 		idProperty: "myInjectedRowNum",
 		wuid: "",
 		sequence: 0,
@@ -97,7 +97,7 @@ define([
 					url: this.getBaseURL("WsWorkunits") + "/WUResult",
 					handleAs: "xml",
 					content: request,
-					load: function(domXml) {
+					load: function (domXml) {
 						var rows = context.getValues(domXml, "Row");
 						for (var i = 0; i < rows.length; ++i) {
 							rows[i].myInjectedRowNum = options.start + i + 1;
@@ -116,7 +116,7 @@ define([
 					}
 				});
 			} else {
-				setTimeout(function() {
+				setTimeout(function () {
 					context.queryWhenComplete(query, options, deferredResults);
 				}, 100);
 			}

+ 35 - 0
esp/files/templates/ECLPlaygroundWidget.html

@@ -0,0 +1,35 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}TopPane" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.layout.ContentPane">
+            <div id="${id}Title" style="display: inline-block; vertical-align: middle; font-weight: bold; font-size: x-large">
+                ECL Playground
+            </div>
+            <div style="float: right; display: inline-block; vertical-align: middle">
+                <div id="${id}SampleSelect" style="display: inline-block; vertical-align: middle" data-dojo-type="SampleSelectWidget">
+                </div>
+            </div>
+        </div>
+        <div id="${id}Tabs" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
+            <div id="${id}EclContent">
+                <textarea id="${id}EclCode">
+				</textarea>
+            </div>
+        </div>
+        <div id="${id}Graphs" style="width: 240px;" data-dojo-props="minSize:120, region: 'right', splitter:true" data-dojo-type="GraphWidget">
+        </div>
+        <div id="${id}Results" style="height: 240px; border-style: none; border-width: 0px" data-dojo-props="minSize:120, region: 'bottom', splitter:true" data-dojo-type="ResultsWidget">
+        </div>
+        <div id="${id}SubmitPane" class="edgePanel" data-dojo-props="region: 'bottom'" data-dojo-type="dijit.layout.ContentPane">
+            <div style="display: inline-block; vertical-align: middle">
+                <button id="${id}SubmitBtn" data-dojo-attach-event="onClick:_onSubmit" data-dojo-type="dijit.form.Button">Submit</button>
+                <div id="${id}TargetSelect" style="display: inline-block; vertical-align: middle" data-dojo-type="TargetSelectWidget">
+                </div>
+            </div>
+            <div style="float: right; display: inline-block; vertical-align: middle">
+                <div id="${id}Status">
+                    ...
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 38 - 0
esp/files/templates/GraphPageWidget.html

@@ -0,0 +1,38 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}MainGraphWidget" data-dojo-props="region: 'center'" data-dojo-type="GraphWidget">
+            <div id="${id}GraphSelect" data-dojo-type="dijit.form.Select">
+            </div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}FindField" style="width: 120px" data-dojo-props="placeHolder:'find'" data-dojo-type="dijit.form.TextBox">Find</div>
+            <div id="${id}Find" data-dojo-attach-event="onClick:_onFind" data-dojo-type="dijit.form.Button">F</div>
+            <div id="${id}Previous" data-dojo-attach-event="onClick:_onFindPrevious" data-dojo-type="dijit.form.Button">&lt;</div>
+            <div id="${id}Next" data-dojo-attach-event="onClick:_onFindNext" data-dojo-type="dijit.form.Button">&gt;</div>
+            <span data-dojo-type="dijit.ToolbarSeparator"></span>
+            <div id="${id}Layout" data-dojo-attach-event="onClick:_onLayout" data-dojo-type="dijit.form.Button">Layout</div>
+        </div>
+        <div id="${id}RightBorderContainer" style="width: 33%; padding: 0px; overflow: hidden" data-dojo-props="region: 'right', splitter:true, minSize: 120" data-dojo-type="dijit.layout.BorderContainer">
+            <div id="${id}OverviewTabContainer" data-dojo-props="region: 'center', tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
+                <div id="${id}MiniGraphWidget" title="Overview" data-dojo-type="GraphWidget">
+                </div>
+                <div id="${id}Timings" title="Timings" style="padding: 0px; overflow: hidden" data-dojo-type="dijit.layout.ContentPane">
+                    <table id="${id}TimingsGrid" data-dojo-type="dojox.grid.DataGrid">
+                        <thead>
+                            <tr>
+                                <th field="Name" width="auto">Component</th>
+                                <th field="Value" width="auto">Time (ms)</th>
+                            </tr>
+                        </thead>
+                    </table>
+                </div>
+            </div>
+            <div id="${id}LocalTabContainer" class="edgePanel" style="height: 66%" data-dojo-props="region: 'bottom', splitter:true, minSize: 120, tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
+                <div id="${id}LocalGraphWidget" title="Local" data-dojo-type="GraphWidget">
+                    <div id="${id}LocalSync" data-dojo-attach-event="onClick:_onLocalSync" data-dojo-type="dijit.form.Button">Recalculate</div>
+                </div>
+                <div id="${id}Properties" title="Properties" data-dojo-type="dijit.layout.ContentPane">
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 15 - 0
esp/files/templates/GraphWidget.html

@@ -0,0 +1,15 @@
+<div class="${baseClass}">
+    <div id="${id}BorderContainer" class="${baseClass}BorderContainer" style="width: 100%; height: 100%; padding: 0px; overflow: hidden" data-dojo-props="splitter: false, gutters:false" data-dojo-type="dijit.layout.BorderContainer">
+        <div id="${id}ToolbarContentPane" class="${baseClass}ToolbarContentPane" style="padding: 0px; overflow: hidden" data-dojo-props="region: 'top'" data-dojo-type="dijit.layout.ContentPane">
+            <div class="topPanel" data-dojo-type="dijit.Toolbar">
+                <b>Zoom:</b>
+                <div data-dojo-attach-event="onClick:_onClickZoomAll" data-dojo-type="dijit.form.Button">All</div>
+                <div data-dojo-attach-event="onClick:_onClickZoomWidth" data-dojo-type="dijit.form.Button">Width</div>
+                <span data-dojo-type="dijit.ToolbarSeparator"></span>
+                <span data-dojo-attach-point="containerNode"></span>
+            </div>
+        </div>
+        <div id="${id}GraphContentPane" class="${baseClass}GraphContentPane" style="padding: 0px; overflow: hidden; background-color: #707070" data-dojo-props="region: 'center'" data-dojo-type="dijit.layout.ContentPane">
+        </div>
+    </div>
+</div>

+ 4 - 0
esp/files/templates/ResultsWidget.html

@@ -0,0 +1,4 @@
+<div class="${baseClass}">
+    <div id="${id}ResultsPane" style="width: 100%; height: 100%" data-dojo-props="tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
+    </div>
+</div>

+ 5 - 0
esp/files/templates/SampleSelectWidget.html

@@ -0,0 +1,5 @@
+<div class="${baseClass}">
+    <label id="${id}SampleSelectLabel" for="${id}SampleSelect">Sample:</label>
+    <div id="${id}SampleSelect" data-dojo-type="dijit.form.Select">
+    </div>
+</div>

+ 5 - 0
esp/files/templates/TargetSelectWidget.html

@@ -0,0 +1,5 @@
+<div class="${baseClass}">
+    <label id="${id}TargetSelectLabel" for="${id}TargetSelect">Target:</label>
+    <div id="${id}TargetSelect" data-dojo-type="dijit.form.Select">
+    </div>
+</div>