climate-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jo...@apache.org
Subject svn commit: r1480068 [3/3] - in /incubator/climate/trunk/rcmet/src/main: python/rcmes/services/ python/rcmes/storage/ ui/app/ ui/app/css/ ui/app/font/ ui/app/js/ ui/app/partials/ ui/scripts/ ui/test/unit/
Date Tue, 07 May 2013 20:50:18 GMT
Modified: incubator/climate/trunk/rcmet/src/main/ui/app/index.html
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/app/index.html?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/app/index.html (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/app/index.html Tue May  7 20:50:17 2013
@@ -9,6 +9,7 @@
   <link rel="stylesheet" href="css/jasny-bootstrap.min.css"/>
   <link rel="stylesheet" href="css/jasny-bootstrap-responsive.min.css"/>
   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" />
+  <link rel="stylesheet" href="css/font-awesome.min.css"/>
   <!--[if lte IE 8]>
     <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.ie.css" />
   <![endif]-->
@@ -28,7 +29,34 @@
 	</div>
   </bootstrap-modal>
 
-  <h1>RCMET Evaluation UI</h1>
+  <bootstrap-modal modal-id="evaluationResults">
+  <div class="modal-header">
+  	<button class="close">&times;</button>
+  	<h3>Evaluation Results</h3>
+  </div>
+  <div class="modal-body">
+	  <p>
+	  Comparison: <a href="http://localhost:8082/static/evalResults/{{evalResults.comparisonPath}}">
+		  {{evalResults.comparisonPath}}</a>
+	  </p>
+	  <p>
+	  Model: <a href="http://localhost:8082/static/evalResults/{{evalResults.modelPath}}">
+		  {{evalResults.modelPath}}</a>
+	  </p>
+	  <p>
+	  Observation: <a href="http://localhost:8082/static/evalResults/{{evalResults.obsPath}}">
+		  {{evalResults.obsPath}}</a>
+	  </p>
+	  <p><img src="http://localhost:8082/static/evalResults/{{evalResults.comparisonPath}}" alt="" /></p>
+	  <p><img src="http://localhost:8082/static/evalResults/{{evalResults.modelPath}}" alt="" /></p>
+	  <p><img src="http://localhost:8082/static/evalResults/{{evalResults.obsPath}}" alt="" /></p>
+  </div>
+  <div class="modal-footer">
+	  <a href="#" class="btn btn-primary close">Close</a>
+  </div>
+  </bootstrap-modal>
+
+  <h1 id="rcmetHeader">RCMET Evaluation UI</h1>
   <div ng-controller="WorldMapCtrl">
     <sap id="map"></sap>
   </div>
@@ -38,54 +66,112 @@
       <div class="span1 offset2 text-center">Start Date:</div>
       <div class="span2">
         <form>
-          <input type="text" class="span2" ng-model="startDateInput"/>
+		  <input ng-model="displayStart" type="text" class="span2" />
         </form>
       </div>
       <div class="span1 text-center">End Date:</div>
       <div class="span2">
         <form>
-          <input type="text" class="span2" ng-model="endDateInput"/>
+		  <input ng-model="displayEnd" type="text" class="span2" />
         </form>
       </div>
       <div class="span1">
-        <button ng-click="runEvaluation()" ng-disabled="shouldDisableControls()" class="btn btn-primary">Evaluate</button>
+		<button ng-click="runEvaluation()" ng-disabled="shouldDisableEvaluate()" class="btn btn-block btn-primary">
+		  <div ng-hide="runningEval">Evaluate</div>
+		  <div ng-show="runningEval"><i class="icon-spinner icon-spin"></i></div>
+		</button>
       </div>
       <div class="span2">
-        <button class="btn btn-success" bootstrap-modal-open="datasetSelect">Select a Dataset</button>
+        <button class="btn btn-block btn-success" bootstrap-modal-open="datasetSelect">Select a Dataset</button>
       </div>
+	  <div class="span1">
+		  <button class="btn btn-block btn-primary" ng-disabled="shouldDisableResultsView()" bootstrap-modal-open="evaluationResults">Results</button>
+	  </div>
     </div>
     <div class="row">
       <div class="span1 offset2 text-center">North:</div>
       <div class="span2">
         <form action="">
-          <input type="text" class="span2" ng-model="northInput"/>
+		  <input ng-model="displayLatMax" type="text" class="span2" />
         </form>
       </div>
       <div class="span1 text-center">East:</div>
       <div class="span2">
         <form>
-          <input type="text" class="span2" ng-model="eastInput"/>
+		  <input ng-model="displayLonMax" type="text" class="span2" />
         </form>
       </div>
       <div class="span1">
-        <button ng-click="updateParameters()" ng-disabled="shouldDisableControls()" class="btn btn-info">Update</button>
+        <button ng-click="updateParameters()" ng-disabled="shouldDisableControls()" class="btn btn-block btn-info">Update</button>
       </div>
+	  <div class="span2">
+		<button ng-click="clearDatasets()" ng-disabled="shouldDisableClearButton()" class="btn btn-block btn-warning">Clear Datasets</button>
+	  </div>
     </div>
     <div class="row">
       <div class="span1 offset2 text-center">South:</div>
       <div class="span2">
         <form action="">
-          <input type="text" class="span2" ng-model="southInput"/>
+		  <input ng-model="displayLatMin" type="text" class="span2" />
         </form>
       </div>
       <div class="span1 text-center">West:</div>
       <div class="span2">
         <form>
-          <input type="text" class="span2" ng-model="westInput"/>
+		  <input ng-model="displayLonMin" type="text" class="span2" />
         </form>
       </div>
     </div>
   </div>
+  <!--Dataset display-->
+  <div ng-controller="DatasetDisplayCtrl">
+    <div ng-repeat="dataset in datasets">
+	  <div class="row displayRow">
+  	    <div class="span10 offset2"><hr /></div>
+	  </div>
+	  <div class="row displayRow">
+		<div class="span4 offset2">
+		  <input class="span6 text-center" type="text" value="{{dataset.name}}" />
+		</div>
+	  </div>
+	  <!--Time Values!-->
+      <div class="row displayRow">
+        <div class="span1 offset2 text-center">Start Date:</div>
+		<div class="span2">
+		  <input class="span2 text-center" type="text" value="{{dataset.timeVals.start}}" readonly/>
+		</div>
+        <div class="span1 text-center">End Date:</div>
+		<div class="span2">
+		  <input class="span2 text-center" type="text" value="{{dataset.timeVals.end}}" readonly/>
+		</div>
+	  </div>
+	  <!--Lat/Long Values!-->
+      <div class="row displayRow">
+        <div class="span1 offset2 text-center">North:</div>
+		<div class="span2">
+		  <input class="span2 text-center" type="text" value="{{dataset.latlonVals.latMax}}" readonly/>
+		</div>
+        <div class="span1 text-center">East:</div>
+		<div class="span2">
+		  <input class="span2 text-center" type="text" value="{{dataset.latlonVals.lonMax}}" readonly/>
+		</div>
+		<div class="span2">
+		  <a class="btn btn-block" ng-click="removeDataset($index)">Remove Dataset</a>
+		</div>
+    <div class="span1" style="background-color: {{fillColors[$index]}}; height: 28px; width: 28px;"></div>
+	  </div>
+      <div class="row displayRow">
+        <div class="span1 offset2 text-center">South:</div>
+		<div class="span2">
+		  <input class="span2 text-center" type="text" value="{{dataset.latlonVals.latMin}}" readonly/>
+		</div>
+        <div class="span1 text-center">West:</div>
+		<div class="span2">
+		  <input class="span2 text-center" type="text" value="{{dataset.latlonVals.lonMin}}" readonly/>
+		</div>
+	  </div>
+    </div>
+  </div>
 
   <!-- In production use:
   <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>

Modified: incubator/climate/trunk/rcmet/src/main/ui/app/js/app.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/app/js/app.js?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/app/js/app.js (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/app/js/app.js Tue May  7 20:50:17 2013
@@ -2,8 +2,14 @@
 
 angular.module('rcmes', []).
 	config(['$routeProvider', function($routeProvider) {
-	$routeProvider.
-		when('/obs', {templateUrl: 'partials/selectObservation.html',   controller: ObservationSelectCtrl}).
-		when('/rcmed', {templateUrl: 'partials/selectRcmed.html', controller: ObservationSelectCtrl}).
-		otherwise({redirectTo: '/obs'});
-}]);
+		$routeProvider.
+			when('/obs', {templateUrl: 'partials/selectObservation.html',   controller: ObservationSelectCtrl}).
+			when('/rcmed', {templateUrl: 'partials/selectRcmed.html', controller: RcmedSelectionCtrl}).
+			otherwise({redirectTo: '/obs'});
+	}]).
+	run(function($rootScope) {
+		$rootScope.datasets = [];
+		$rootScope.evalResults = ""; 
+		$rootScope.fillColors = ['#ff0000', '#00c90d', '#cd0074', '#f3fd00'];
+		$rootScope.surroundColors = ['#a60000', '#008209', '#8f004b', '#93a400']
+	});

Modified: incubator/climate/trunk/rcmet/src/main/ui/app/js/controllers.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/app/js/controllers.js?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/app/js/controllers.js (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/app/js/controllers.js Tue May  7 20:50:17 2013
@@ -5,36 +5,489 @@ function WorldMapCtrl($scope) {
 }
 
 // Controller for dataset parameter selection/modification
-function ParameterSelectCtrl($scope) {
-	$scope.numberOfDatasets = 0;
+function ParameterSelectCtrl($rootScope, $scope, $http, $timeout) {
+	// The min/max lat/lon values from the selected datasets
+	$scope.latMin = -90;
+	$scope.latMax = 90;
+	$scope.lonMin = -180;
+	$scope.lonMax = 180;
+	$scope.start = "1980-01-01 00:00:00";
+	$scope.end = "2030-01-01 00:00:00";
+
+	// The min/max lat/lon values entered by the user
+	$scope.enteredLatMin = "";
+	$scope.enteredLatMax = "";
+	$scope.enteredLonMin = "";
+	$scope.enteredLonMax = "";
+	$scope.enteredStart = "";
+	$scope.enteredEnd = "";
+
+	// The min/max lat/loon values that are displayed
+	$scope.displayLatMin = "";
+	$scope.displayLatMax = "";
+	$scope.displayLonMin = "";
+	$scope.displayLonMax = "";
+	$scope.displayStart = "";
+	$scope.displayEnd = "";
+
+	$scope.runningEval = false;
+	
+	var updateDisplayValues = function() {
+		// Update the displayed lat/lon values. We give precedence to users entered values assuming
+		// they're valid given the current set of datasets selected.
+		//
+		// If the user has entered a value for latMin
+		if ($scope.enteredLatMin != "") {
+			// If it's not a valid value...
+			if ($scope.enteredLatMin < $scope.latMin) {
+				// Reset enteredLatMin to the "unmodified" state and display the correct value.
+				$scope.displayLatMin = $scope.latMin;
+			} else {
+				$scope.displayLatMin = $scope.enteredLatMin;
+			}
+		// Otherwise, just display the value.
+		} else { 
+			$scope.displayLatMin = $scope.latMin;
+		}
+		// Update latMax
+		if ($scope.enteredLatMin != "") {
+			if ($scope.enteredLatMax > $scope.latMax) {
+				$scope.displayLatMax = $scope.latMax;
+			} else {
+				$scope.displayLatMax = $scope.enteredLatMax;
+			}
+		} else { 
+			$scope.displayLatMax = $scope.latMax;
+		}
+		// Update lonMin
+		if ($scope.enteredLonMin != "") {
+			if ($scope.enteredLonMin < $scope.lonMin) {
+				$scope.displayLonMin = $scope.lonMin;
+			} else {
+				$scope.displayLonMin = $scope.enteredLonMin;
+			}
+		} else { 
+			$scope.displayLonMin = $scope.lonMin;
+		}
+		// Update lonMax
+		if ($scope.enteredLonMax != "") {
+			if ($scope.enteredLonMax > $scope.lonMax) {
+				$scope.displayLonMax = $scope.lonMax;
+			} else {
+				$scope.displayLonMax = $scope.enteredLonMax;
+			}
+		} else { 
+			$scope.displayLonMax = $scope.lonMax;
+		}
+		// Update Start time
+		if ($scope.enteredStart != "") {
+			if ($scope.enteredStart < $scope.start) {
+				$scope.displayStart = $scope.start;
+			} else {
+				$scope.displayStart = $scope.enteredStart;
+			}
+		} else {
+			$scope.displayStart = $scope.start;
+		}
+		// Update End time
+		if ($scope.enteredEnd != "") {
+			if ($scope.enteredEnd > $scope.end) {
+				$scope.displayEnd = $scope.end;
+			} else {
+				$scope.displayEnd = $scope.enteredEnd;
+			}
+		} else {
+			$scope.displayEnd = $scope.end;
+		}
+	}
 
 	$scope.shouldDisableControls = function() {
-		return ($scope.numberOfDatasets < 2);
+		return ($rootScope.datasets.length < 2);
 	}
 
-	$scope.runEvaluation = function() {
+	$scope.shouldDisableEvaluate = function() {
+		return ($scope.shouldDisableControls() || $scope.runningEval);
+	}
+
+	$scope.shouldDisableClearButton = function() {
+		return ($rootScope.datasets.length === 0)
 	}
 
-	$scope.addDataset = function() {
+	$scope.shouldDisableResultsView = function() {
+		var res = false;
+
+		if ($rootScope.evalResults == "")
+			res = true;
+
+		return res;
+	}
+
+	$scope.clearDatasets = function() {
+		$rootScope.datasets = [];
+	}
+
+	$scope.runEvaluation = function() {
+		$scope.runningEval = true;
+
+		// TODO
+		// Currently this has the 1 model, 1 observation format hard coded in. This shouldn't
+		// be the long-term case! This needs to be changed!!!!!!!!
+		var obsIndex = -1,
+			modelIndex = -1;
+		for (var i = 0; i < $rootScope.datasets.length; i++) {
+			if ($rootScope.datasets[i]['isObs'] == 1)
+				obsIndex = i;
+			else
+				modelIndex = i;
+		}
+
+		// You might wonder why this is using a jQuery ajax call instead of a built
+		// in $http.post call. The reason would be that it wasn't working with the 
+		// $http.post call but it is with this. So...there you go! This should be
+		// changed eventually!!
+		$.ajax({
+			type: "POST",
+			url: "http://localhost:8082/rcmes/run/", 
+			data: { 
+				"obsDatasetId"     : $rootScope.datasets[obsIndex]['id'],
+				"obsParameterId"   : $rootScope.datasets[obsIndex]['param'],
+				"startTime"        : $scope.displayStart,
+				"endTime"          : $scope.displayEnd,
+				"latMin"           : $scope.displayLatMin,
+				"latMax"           : $scope.displayLatMax,
+				"lonMin"           : $scope.displayLonMin,
+				"lonMax"           : $scope.displayLonMax,
+				"filelist"         : $rootScope.datasets[modelIndex]['id'],
+				"modelVarName"     : $rootScope.datasets[modelIndex]['param'],
+				"modelTimeVarName" : $rootScope.datasets[modelIndex]['time'],
+				"modelLatVarName"  : $rootScope.datasets[modelIndex]['lat'],
+				"modelLonVarName"  : $rootScope.datasets[modelIndex]['lon'],
+				"regridOption"     : "model",
+				"timeRegridOption" : "monthly",
+				"metricOption"     : "bias",
+			},
+			success: function(data) {
+				var comp = data['comparisonPath'].split('/');
+				var model = data['modelPath'].split('/');
+				var obs = data['obsPath'].split('/');
+
+				$rootScope.evalResults = {};
+				$rootScope.evalResults.comparisonPath = comp[comp.length - 1];
+				$rootScope.evalResults.modelPath = model[model.length - 1];
+				$rootScope.evalResults.obsPath = obs[obs.length - 1];
+
+				$scope.runningEval = false;
+
+				$timeout(function() {
+					$('#evaluationResults').trigger('modalOpen', true, true);
+				}, 100);
+			},
+			error: function(xhr, status, error) {
+				$scope.runningEval = false;
+			},
+		});
 	}
 
 	$scope.updateParameters = function() {
+		// Save the user input, even if it isn't valid.
+		$scope.enteredLatMin = $scope.displayLatMin;
+		$scope.enteredLatMax = $scope.displayLatMax;
+		$scope.enteredLonMin = $scope.displayLonMin;
+		$scope.enteredLonMax = $scope.displayLonMax;
+		$scope.enteredStart = $scope.displayStart;
+		$scope.enteredEnd = $scope.displayEnd;
+
+		// Check if the user values are valid and update the display values.
+		updateDisplayValues();
+		// Update the map
+		$scope.updateMap();
+	}
+
+	$scope.updateMap = function() {
+ 		
+ 		// Clear Group of layers from map if it exists
+ 		if ("rectangleGroup" in $rootScope) {
+ 			$rootScope.rectangleGroup.clearLayers();
+ 		}
+
+		// Don't process if we don't have any datasets added!!
+		if ($scope.datasets.length == 0)
+			return;
+ 		
+ 		if ("map" in $rootScope) {
+ 			// Create Group to add all rectangles to map
+ 			$rootScope.rectangleGroup = L.layerGroup();
+ 			
+ 			// Loop through datasets and add rectangles to Group 
+			var i = 0;
+ 			angular.forEach($scope.datasets, function(dataset) {
+ 				
+ 				// Get bounds from dataset 
+ 				var maplatlon = dataset.latlonVals;
+ 				var bounds = [[maplatlon.latMax, maplatlon.lonMin], 
+ 				              [maplatlon.latMin, maplatlon.lonMax]];
+ 	
+ 				var polygon = L.rectangle(bounds,{
+					//color: $rootScope.surroundColors[i],
+					stroke: false,
+					fillColor: $rootScope.fillColors[i],
+ 				    fillOpacity: 0.3
+ 				});
+ 				
+ 				// Add layer to Group
+ 				$rootScope.rectangleGroup.addLayer(polygon);
+				i++;
+ 			});
+
+			// Draw user selected region
+			if ($scope.displayLatMin != "" && $scope.displayLatMax != "" && $scope.displayLonMin != "" && $scope.displayLonMax != "") {
+				var bounds = [[$scope.displayLatMax, $scope.displayLonMin],
+							  [$scope.displayLatMin, $scope.displayLonMax]];
+				var polygon = L.rectangle(bounds, {
+					//color: '#1921b1',
+					color: '#000000',
+					opacity: 1.0,
+					fill: false,
+				});
+
+				$rootScope.rectangleGroup.addLayer(polygon);
+			}
+ 			
+ 			// Add rectangle Group to map
+ 			$rootScope.rectangleGroup.addTo($rootScope.map);
+
+ 		}
+ 	};
+
+	$scope.$watch('datasets', 
+		function() { 
+			var numDatasets = $rootScope.datasets.length;
+
+ 			if (numDatasets) {
+ 				var latMin = -90,
+ 					latMax = 90,
+ 					lonMin = -180,
+ 					lonMax = 180,
+ 					start = "1980-01-01 00:00:00",
+ 					end = "2030-01-01 00:00:00";
+ 			
+ 				// Get the valid lat/lon range in the selected datasets.
+ 				for (var i = 0; i < numDatasets; i++) {
+ 					var curDataset = $rootScope.datasets[i];
+ 	
+ 					latMin = (curDataset['latlonVals']['latMin'] > latMin) ? curDataset['latlonVals']['latMin'] : latMin;
+ 					latMax = (curDataset['latlonVals']['latMax'] < latMax) ? curDataset['latlonVals']['latMax'] : latMax;
+ 					lonMin = (curDataset['latlonVals']['lonMin'] > lonMin) ? curDataset['latlonVals']['lonMin'] : lonMin;
+ 					lonMax = (curDataset['latlonVals']['lonMax'] < lonMax) ? curDataset['latlonVals']['lonMax'] : lonMax;
+ 					start = (curDataset['timeVals']['start'] > start) ? curDataset['timeVals']['start'] : start;
+ 					end = (curDataset['timeVals']['end'] < end) ? curDataset['timeVals']['end'] : end;
+				}
+			}
+
+			$scope.latMin = latMin;
+			$scope.latMax = latMax;
+			$scope.lonMin = lonMin;
+			$scope.lonMax = lonMax;
+			$scope.start = start;
+			$scope.end = end;
+
+			updateDisplayValues();
+			$scope.updateMap();
+		}, true);
+}
+
+// Controller for dataset display
+function DatasetDisplayCtrl($rootScope, $scope) {
+	$scope.removeDataset = function($index) {
+		$rootScope.datasets.splice($index, 1);
 	}
 }
 
 // Controller for observation selection in modal
-function ObservationSelectCtrl($scope) {
+function ObservationSelectCtrl($rootScope, $scope, $http) {
 	$scope.params = ["Please select a file above"];
 	$scope.lats = ["Please select a file above"];
 	$scope.lons = ["Please select a file above"];
 	$scope.times = ["Please select a file above"];
+	$scope.latLonVals = [];
+	$scope.timeVals = [];
+	$scope.localSelectForm = {};
 
 	$scope.uploadLocalFile = function() {
-		// Need to make a request to the bottle services
-		// Then upload the local bindings
+		// TODO: Need to try to validate the input a bit. At least make sure we're not
+		// pointing at a directory perhaps?
+		
+		// TODO: Two-way binding with ng-model isn't being used here because it fails to update
+		// properly with the auto-complete that we're using on the input box. So we're doing
+		// it the wrong way temporarily...
+		var input = $('#observationFileInput').val();
+
+		// TODO: We're not really handling the case where there is a failure here at all. 
+		// Should check for fails and allow the user to make changes.
+		//
+	    // Get model variables
+		$http.jsonp('http://localhost:8082/list/vars/"' + input + '"?callback=JSON_CALLBACK').
+			success(function(data) {
+				if ("FAIL" in data) {
+					$scope.params = ["Unable to find variable(s)"];
+					return;
+				}
+
+				$scope.params = data['variables'];
+			}).
+			error(function(data) {
+				$scope.params = ["Unable to find variable(s)"];
+			});		
+
+		// Get Lat and Lon variables
+		$http.jsonp('http://localhost:8082/list/latlon/"' + input + '"?callback=JSON_CALLBACK').
+			success(function(data) {
+				if (data["success"] == 0) {
+					$scope.lats = ["Unable to find variable(s)"];
+					$scope.lons = ["Unable to find variable(s)"];
+					return;
+				}
+
+				$scope.lats = [data["latname"]];
+				$scope.lons = [data["lonname"]];
+				var tmpMinsMaxs = [data["latMin"], data["latMax"], data["lonMin"], data["lonMax"]];
+				$scope.latLonVals = tmpMinsMaxs.map(parseFloat);
+			}).
+			error(function(data) {
+				$scope.lats = ["Unable to find variable(s)"];
+				$scope.lons = ["Unable to find variable(s)"];
+			});		
+
+		// Get Time variables
+		$http.jsonp('http://localhost:8082/list/time/"' + input + '"?callback=JSON_CALLBACK').
+			success(function(data) {
+				if (data["success"] == 0) {
+					$scope.times = ["Unable to find variable(s)"];
+					return;
+				}
+
+				if (data["timename"] instanceof Array) {
+					$scope.times = data["timename"];
+				} else {
+					$scope.times = [data["timename"]];
+				}
+
+				$scope.timeVals = [data["start_time"], data["end_time"]];
+			}).
+			error(function(data) {
+				$scope.times = ["Unable to find variable(s)"];
+			});		
 	};
 
 	$scope.addDataSet = function() {
-		// Need to add the dataset information to the correct model for the main div
+		// TODO: Need to verify that all the variables selected are correct!!!
+		// TODO: We shouldn't allow different parameters to match the same variables!!
+
+		var newDataset = {};
+		var input = $('#observationFileInput').val();
+
+		newDataset['isObs'] = 0;
+		// Save the model path. Note that the path is effectively the "id" for the model.
+		newDataset['id'] = input;
+		// Grab the file name later for display purposes.
+		var splitFilePath = input.split('/');
+		newDataset['name'] = splitFilePath[splitFilePath.length - 1];
+		// Save the model parameter variable. We save it twice for consistency and display convenience.
+		newDataset['param'] = $('#paramSelect').val();
+		newDataset['paramName'] = newDataset['param'];
+		// Save the lat/lon information
+		newDataset['lat'] = $('#latsSelect').val();
+		newDataset['lon'] = $('#lonsSelect').val();
+		newDataset['latlonVals'] = {"latMin": $scope.latLonVals[0], "latMax": $scope.latLonVals[1],
+									"lonMin": $scope.latLonVals[2], "lonMax": $scope.latLonVals[3]};
+		// Get the time information
+		newDataset['time'] = $('#timesSelect').val();
+		newDataset['timeVals'] = {"start": $scope.timeVals[0], "end": $scope.timeVals[1]};
+
+		$rootScope.datasets.push(newDataset);
+
+		// Reset all the fields!!
+		$scope.params = ["Please select a file above"];
+		$scope.lats = ["Please select a file above"];
+		$scope.lons = ["Please select a file above"];
+		$scope.times = ["Please select a file above"];
+		$scope.latLonVals = [];
+		$scope.timeVals = [];
+
+		// Clear the input box
+		$('#observationFileInput').val("");
 	}
 }
+
+function RcmedSelectionCtrl($rootScope, $scope, $http) {
+	var getObservations = function() {
+		$http.jsonp('http://localhost:8082/getObsDatasets?callback=JSON_CALLBACK').
+			success(function(data) {
+				$scope.availableObs = data;
+			}).
+			error(function(data) {
+				$scope.availableObs = ["Unable to query RCMED"]
+			});
+	};
+
+	var getObservationTimeRange = function(datasetID) {
+		var times = {
+			'1' : {'start' : '1989-01-01 00:00:00','end' : '2009-12-31 00:00:00'},	// ERA-Interim
+			'2' : {'start' : '2002-08-31 00:00:00','end' : '2010-01-01 00:00:00'},	// AIRS
+			'3' : {'start' : '1998-01-01 00:00:00','end' : '2010-01-01 00:00:00'},	// TRMM
+			'4' : {'start' : '1948-01-01 00:00:00','end' : '2010-01-01 00:00:00'},	// URD
+			'5' : {'start' : '2000-02-24 00:00:00','end' : '2010-05-30 00:00:00'},	// MODIS
+			'6' : {'start' : '1901-01-01 00:00:00','end' : '2006-12-01 00:00:00'}   // CRU
+		};
+
+		return ((datasetID in times) ? times[datasetID] : false);
+	};
+
+	$scope.dataSelectUpdated = function() {
+		var urlString = 'http://localhost:8082/getDatasetParam?dataset=' + 
+							$scope.datasetSelection["shortname"] + 
+							"&callback=JSON_CALLBACK";
+		$http.jsonp(urlString).
+			success(function(data) {
+				$scope.retrievedObsParams = data;
+			});
+	};
+
+	$scope.addObservation = function() {
+		// This is a horrible hack for temporarily getting a valid time range
+		// for the selected observation. Eventually we need to handle this more
+		// elegantly than indexing into an array...
+		var timeRange = getObservationTimeRange($scope.datasetSelection["dataset_id"]);
+
+		var newDataset = {};
+
+		newDataset['isObs'] = 1;
+		// Save the dataset id (the important part) and name (for display purposes)
+		newDataset['id'] = $scope.datasetSelection['dataset_id'];
+		newDataset['name'] = $scope.datasetSelection['longname'];
+		// Save the parameter id (the important part) and name (for display purposes)
+		newDataset['param'] = $scope.parameterSelection['parameter_id'];
+		newDataset['paramName'] = $scope.parameterSelection['longname'];
+		// Save the (fake) lat/lon information. Our datasets cover the entire globe (I think...)
+		newDataset['latlonVals'] = {"latMin": -90, "latMax": 90, "lonMin": -180, "lonMax": 180};
+		// Set some defaults for lat/lon variable names. This just helps us display stuff later.
+		newDataset['lat'] = "N/A";
+		newDataset['lon'] = "N/A";
+		// Save time range information. If we don't have saved data for this observation then
+		// we set the values to extreme values so they'll be ignored when calculating overlaps.
+		newDataset['timeVals'] = {"start": (timeRange) ? timeRange['start'] : "1901-01-01 00:00:00",
+								  "end": (timeRange) ? timeRange['end'] : "2050-01-01 00:00:00"};
+		// Set a default for the time variable names for display convenience.
+		newDataset['time'] = "N/A";
+
+		$rootScope.datasets.push(newDataset);
+
+		// Clear the user selections by requery-ing RCMED. This is really hacky, but it works for now...
+		$scope.availableObs = [];
+		$scope.retrievedObsParams = [];
+		getObservations();
+	};
+
+	// Grab the available observations from RCMED
+	getObservations();
+}

Modified: incubator/climate/trunk/rcmet/src/main/ui/app/js/directives.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/app/js/directives.js?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/app/js/directives.js (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/app/js/directives.js Tue May  7 20:50:17 2013
@@ -2,20 +2,21 @@
 
 // Directive for dealing with the Leaflet map
 angular.module('rcmes').
-directive('sap', function() {
+directive('sap', function($rootScope) {
 	return {
 		restrict: 'E',
 		replace: true,
 		template: '<div></div>',
 		link: function(scope, element, attrs) {
-			var map = L.map(attrs.id, {
-				center: [40, -86],
+			$rootScope.map = L.map(attrs.id, {
+				//center: [40, -86],
+				center: [40, 0],
 				zoom: 2
 			});
 			//create a CloudMade tile layer and add it to the map
 			L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
-				maxZoom: 4, minZoom: 2
-			}).addTo(map);
+				maxZoom: 4, minZoom: 2,
+			}).addTo($rootScope.map);
 		}
 	};
 }).
@@ -57,6 +58,11 @@ directive('bootstrapModal', function($ti
 
 			// Display the modal
 			modal.css({display: 'block'});
+			// If we want to animate the modals later the open and close functions can be changed
+			// to effectively be a simple function call. This is arguably the better way to handle
+			// this anyway! Then again, switching to http://angular-ui.github.io/bootstrap/ is probably
+			// even better...
+			//modal.modal('show');
 		};
 
 		closeModal = function(event) {
@@ -71,6 +77,9 @@ directive('bootstrapModal', function($ti
 
 			// Hide the modal
 			$('#' + attrs.modalId).css({display: 'none'});
+			// See above about animation
+			//$('#' + attrs.modalId).modal('hide');
+			
 		};
 
 		// We need to bind the close and open modal events so outside elements can trigger the modal.
@@ -91,6 +100,8 @@ directive('bootstrapModal', function($ti
 			modalId: '@' 
 		},
 		template: '<div id="{{modalId}}" class="modal hide"><div ng-transclude></div></div>',
+		// We can't have fade if we use the display: {block,none} method for showing the modal
+		//template: '<div id="{{modalId}}" class="modal hide fade"><div ng-transclude></div></div>',
 		transclude: true
 	};
 }).
@@ -110,4 +121,286 @@ directive('bootstrapModalOpen', function
 			});
 		}
 	};
+}).
+// Setup a text input that the user will use to input a path to a local file.
+directive('predictiveFileBrowserInput', function() {
+	var link = function($scope, $elem, $attrs) {
+		$scope.autocomplete = [];
+		
+		/*
+		 * We need a place to dump our auto-completion options
+		 */
+		$($elem).parent().append('<ul id="autoCompletePath"><ul>');
+
+		// Handle user clicks on auto-complete path options
+		$(document).on('click', '#autoCompletePath li span', function(e) {
+			// Set the input text box's value to that of the user selected path
+			var val = $(e.target).text();
+			$($elem).val(val);
+
+			// If the user selected a directory, find more results..
+			if (val[val.length - 1] == '/') {
+				$scope.fetchFiles($($elem).val());
+			// Otherwise, remove the auto-complete options...
+			} else {
+				$('#autoCompletePath li').remove();
+			}
+		});
+
+		/*
+		 * Handle key-down events on the input box
+		 *
+		 * We need to ignore <TAB> presses here so we can auto-complete with <TAB>.
+		 * If we don't ignore here then <TAB> will move the user to the next field
+		 * in the form and our common-prefix-fill won't work later.
+		 */
+		$($elem).keydown(function(e) {
+			var code = e.keyCode || e.which;
+			var BACKSPACE = 8,
+				TAB = 9;
+
+			if (code == TAB)
+				return false;
+		});
+
+		/*
+		 * Handle key-up events on the input box
+		 */
+		$($elem).keyup(function(e) {
+			var code = e.keyCode || e.which;
+			var BACKSPACE = 8,
+				TAB = 9,
+				FORWARD_SLASH = 191;
+
+			if (code === FORWARD_SLASH) {
+				// Fetch new directory information from the server.
+				$scope.fetchFiles($(this).val());
+			} else if (code === TAB) {
+				// Attempt to auto-fill for the user.
+				$scope.handleTabPress();
+			} else if (code == BACKSPACE) {
+				// Need to properly handle the removal of directory information
+				// and the displaying of auto-complete options
+				$scope.handleBackSpace();
+			} else {
+				// Filter auto-complete options based on user input..
+				$scope.handleMiscKeyPress();
+			}
+
+			// This is being used so we can handle backspacing. The user might hold
+			// down the backspace key or select a section of text and delete. This allows
+			// us to compare the result to its prior state, which makes handling
+			// backspaces easier.
+			$scope.lastInputContents = $elem.val();
+		});
+
+		/*
+		 * Grab additional path information from the web-server
+		 *
+		 * Params:
+		 *		path - The path to get a directory listing of.
+		 */
+		// TODO Make this use $HTTP
+		$scope.fetchFiles = function(path) {
+			$.get('http://localhost:8082/getDirInfo/' + path, {},
+				 function(data) {
+					 $scope.setNewData(data);
+					 $scope.updateAutoComplete();
+				 }, 'json');
+		};
+
+		/*
+		 * Grab additional path information from the web-server and filter the
+		 * results based on the current input text.
+		 *
+		 * Params:
+		 *		path - The path to get a directory listing of.
+		 *
+		 * This is needed to handle deletion of selected text. It is possible that
+		 * the user will select text and delete only part of a word. The results
+		 * need to be filtered based on this partial input.
+		 */
+		// TODO Why isn't this using $http?!?!?! Because I copy and pasted!!!!
+		$scope.fetchFilesAndFilter = function(path) {
+			$.get('http://localhost:8082/getDirInfo/' + path, {},
+				 function(data) {
+					 $scope.setNewData(data);
+					 $scope.filterResults();
+					 $scope.updateAutoComplete();
+				 }, 'json');
+		};
+
+		/*
+		 * Handle directory data from the server.
+		 *
+		 * We store the entire directory information along with the remaining
+		 * possible options given the users current input. This lets us avoid
+		 * unnecessary calls to the server for directory information every time
+		 * the user deletes something.
+		 */
+		$scope.setNewData = function(data) {
+			$scope.autocomplete = data.sort();
+			$scope.possibleCompletes = $scope.autocomplete;
+		};
+
+		/* 
+		 * Handle <TAB> presses.
+		 *
+		 * Attempt to auto-complete options when the user presses <TAB>.
+		 */
+		$scope.handleTabPress = function() {
+			// If there's only one option available there's no points in trying to
+			// find a common prefix! Just set the value!
+			if ($scope.possibleCompletes.length === 1) {
+				$elem.val($scope.possibleCompletes[0]);
+
+				// Make sure more options are displayed if a directory was selected.
+				$scope.checkForMoreOptions();
+				$scope.updateAutoComplete();
+				return;
+			}
+
+			// Find the greatest common prefix amongst the remaining choices and set
+			// the input text to it.
+			var prefix = $scope.getLargestCommonPrefix($scope.possibleCompletes);
+			$elem.val(prefix);
+			$scope.updateAutoComplete();
+		};
+
+		/*
+		 * Handle Backspacing and option displaying.
+		 *
+		 * The auto-complete options needs to be displayed correctly when the user
+		 * removes directory information.
+		 */
+		$scope.handleBackSpace = function() {
+			var curInputVal = $elem.val();
+
+			// If the user deletes everything in the input box all we need to do
+			// is make sure that the auto-complete options aren't displayed.
+			if (curInputVal.length === 0) {
+				$('#autoCompletePath li').remove();
+				return;
+			}
+
+			// Figure out how much text the user removed from the input box.
+			var lengthDiff = $scope.lastInputContents.length - curInputVal.length;
+			// Grab the removed text.
+			var removedText = $scope.lastInputContents.substr(-lengthDiff);
+
+			// If the user deleted over a directory we need to fetch information on the
+			// previous directory for auto-completion.
+			if (removedText.indexOf('/') !== -1) {
+				var lastSlashIndex = curInputVal.lastIndexOf('/');
+
+				// If the remaining path still contains a directory...
+				if (lastSlashIndex !== -1) {
+					// Grab the section of the path that points to a valid directory,
+					// fetch the listing, and update the results.
+					var pathToSearch = curInputVal.slice(0, lastSlashIndex + 1);
+					$scope.fetchFilesAndFilter(pathToSearch);
+				} else {
+					// Delete the old auto-complete information in the case where the user
+					// completely removed path information.
+					$('#autoCompletePath li').remove();
+				}
+			} else {
+				// Otherwise, we just need to filter results based on the remaining input.
+				$scope.filterResults();
+				$scope.updateAutoComplete();
+			}
+		};
+
+		/* 
+		 * Handle all other key presses in the input box
+		 *
+		 * Filter the auto-complete options as the user types to ensure that only options
+		 * which are possible given the current input text are still displayed.
+		 */
+		$scope.handleMiscKeyPress = function() {
+			// Safely exit when there are no options available.
+			if ($scope.autocomplete === [])
+				return;
+
+			// Otherwise, filter the results.
+			$scope.filterResults();
+			$scope.updateAutoComplete();
+		};
+
+		/* 
+		 * When a path is auto-completed with <TAB> we need to check to see if it points
+		 * to a directory. If it does, we still need to fetch results!
+		 */
+		$scope.checkForMoreOptions = function() {
+			var path = $elem.val();
+			if (path[path.length - 1] === '/') {
+				$scope.fetchFiles(path);
+			}
+		};
+
+		/* 
+		 * Calculate the greatest common prefix of the passed options.
+		 *
+		 * Params:
+		 *		Options - An array of strings in which the greatest common prefix
+		 *				  should be found
+		 *
+		 * Returns:
+		 *		The greatest common prefix of the strings.
+		 *
+		 *
+		 * Note - For us, there will always be a prefix of at least '/' since this can't
+		 * possible be called without the users entering a starting directory. As a result,
+		 * we don't explicitly handle the case where there is 0 length common prefix.
+		 */
+		$scope.getLargestCommonPrefix = function(options) {
+			var index = 1;
+			var shortestString = options.reduce(function(a, b) { return a.length < b.length ? a : b; });
+			var longestString = options.reduce(function(a, b) { return a.length > b.length ? a : b; });
+			var	substringToCheck = shortestString[0];
+
+			while (longestString.indexOf(substringToCheck) !== -1) {
+				substringToCheck = shortestString.slice(0, ++index);
+			}
+
+			return longestString.slice(0, index - 1);
+		};
+
+		/* 
+		 * Filter the auto-complete options based on the current input.
+		 */
+		$scope.filterResults = function() {
+			$scope.possibleCompletes = $scope.autocomplete.filter(function(item, index, array) {
+				return (~item.indexOf($($elem).val()));
+			});
+
+			$scope.possibleCompletes.sort();
+		};
+
+		/*
+		 * Update the display of auto-complete options.
+		 */
+		$scope.updateAutoComplete = function() {
+			// Remove all the existing options
+			$('#autoCompletePath li').remove();
+
+			// We don't need to show anything if the user has completely selected
+			// the only existing option available.
+			if ($scope.possibleCompletes.length === 1) {
+				if ($scope.possibleCompletes[0] === $elem.val()) {
+					return;
+				}
+			}
+
+			// Display all the possible completes
+			$.each($scope.possibleCompletes, function(i, v) {
+				$('#autoCompletePath').append($('<li>').html($('<span>').text(v)));
+			});
+		};
+	};
+
+	return {
+		link: link,
+		restrict: 'A'
+	};
 });

Modified: incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectObservation.html
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectObservation.html?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectObservation.html (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectObservation.html Tue May  7 20:50:17 2013
@@ -1,31 +1,25 @@
 <ul class="nav nav-pills">
 	<li class="active"><a href="#/obs">Local File</a></li>
 	<li><a href="#/rcmed">RCMED</a></li>
-	<li class="disabled"><a href="#/egs">EGS</a></li>
+	<li class="disabled"><a href="#/esg">ESG</a></li>
 </ul>
 
-<div class="fileupload fileupload-new" data-provides="fileupload">
-	<div class="input-append">
-		<div class="uneditable-input span3">
-			<i class="icon-file fileupload-exists"></i> 
-			<span class="fileupload-preview"></span>
-		</div>
-		<span class="btn btn-file">
-			<span class="fileupload-new">Select file</span>
-			<span class="fileupload-exists">Change</span>
-			<input type="file" />
-		</span>
-		<a href="#" class="btn fileupload-exists" data-dismiss="fileupload">Remove</a>
-		<button class="btn fileupload-exists" ng-click="uploadLocalFile()">Upload File</button>
-	</div>
-</div>
+<form class="form-inline" autocomplete="off">
+	<input id="observationFileInput" predictive-file-browser-input ng-model="filePathInput" type="text" class="input-xlarge" autocomplete="off" />
+	<button class="btn" ng-click="uploadLocalFile()">Upload File</button>
+</form>
 
 <div class="row">
 	<div class="span2 text-center">
 		Parameter Option
 	</div>
 	<div class="span3">
-		<select>
+		<!--
+			This is not really the correct way to handle this. The commented code should be used, but
+			it's not working. So...
+		-->
+		<!--<select ng-model="paramSelect" ng-options="val for val in params"></select>-->
+		<select id="paramSelect">
 			<option ng-repeat="param in params">{{param}}</option>
 		</select>
 	</div>
@@ -33,7 +27,7 @@
 		Latitude Variable
 	</div>
 	<div class="span3">
-		<select>
+		<select id="latsSelect">
 			<option ng-repeat="lat in lats">{{lat}}</option>
 		</select>
 	</div>
@@ -41,7 +35,7 @@
 		Longitude Variable
 	</div>
 	<div class="span3">
-		<select>
+		<select id="lonsSelect">
 			<option ng-repeat="lon in lons">{{lon}}</option>
 		</select>
 	</div>
@@ -49,7 +43,7 @@
 		Date/Time Variable
 	</div>
 	<div class="span3">
-		<select>
+		<select id="timesSelect">
 			<option ng-repeat="time in times">{{time}}</option>
 		</select>
 	</div>

Modified: incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectRcmed.html
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectRcmed.html?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectRcmed.html (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/app/partials/selectRcmed.html Tue May  7 20:50:17 2013
@@ -1,7 +1,28 @@
 <ul class="nav nav-pills">
 	<li><a href="#/obs">Local File</a></li>
 	<li class="active"><a href="#/rcmed">RCMED</a></li>
-	<li class="disabled"><a href="#/egs">EGS</a></li>
+	<li class="disabled"><a href="#/esg">ESG</a></li>
 </ul>
 
-<h3>This is temporary placeholder</h3>
+<div class="row">
+	<div class="span5">
+		Select the RCMED dataset that you would like use.
+	</div>
+</div>
+<div class="row">
+	<div class="span4">
+		<select ng-change="dataSelectUpdated()" class="span5" ng-model="datasetSelection" ng-options="obs as obs.longname for obs in availableObs"></select>
+	</div>
+</div>
+<div class="row">
+	<div class="span5">
+		Select the dataset parameter that you would like to test.
+	</div>
+</div>
+<div class="row">
+	<div class="span4">
+		<select class="span3" ng-model="parameterSelection" ng-options="param as param.shortname for param in retrievedObsParams"></select>
+	</div>
+</div>
+
+<button class="btn" ng-click="addObservation()">Add Observation</button>

Added: incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular-e2e.conf.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular-e2e.conf.js?rev=1480068&view=auto
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular-e2e.conf.js (added)
+++ incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular-e2e.conf.js Tue May  7 20:50:17 2013
@@ -0,0 +1,22 @@
+basePath = '../';
+
+files = [
+  ANGULAR_SCENARIO,
+  ANGULAR_SCENARIO_ADAPTER,
+  'test/e2e/**/*.js'
+];
+
+autoWatch = false;
+
+browsers = ['Chrome'];
+
+singleRun = true;
+
+proxies = {
+  '/': 'http://localhost:8000/'
+};
+
+junitReporter = {
+  outputFile: 'test_out/e2e.xml',
+  suite: 'e2e'
+};

Propchange: incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular-e2e.conf.js
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular.conf.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular.conf.js?rev=1480068&view=auto
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular.conf.js (added)
+++ incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular.conf.js Tue May  7 20:50:17 2013
@@ -0,0 +1,23 @@
+basePath = '../';
+
+files = [
+  JASMINE,
+  JASMINE_ADAPTER,
+  'app/js/jquery-1.9.1.min.js',
+  'app/js/bootstrap.js',
+  'app/lib/angular/angular.js',
+  'app/lib/angular/angular-*.js',
+  'test/lib/angular/angular-mocks.js',
+  'app/js/leaflet.js',
+  'app/js/**/*.js',
+  'test/unit/**/*.js'
+];
+
+autoWatch = true;
+
+browsers = ['Chrome'];
+
+junitReporter = {
+  outputFile: 'test_out/unit.xml',
+  suite: 'unit'
+};

Propchange: incubator/climate/trunk/rcmet/src/main/ui/scripts/testacular.conf.js
------------------------------------------------------------------------------
    svn:executable = *

Modified: incubator/climate/trunk/rcmet/src/main/ui/scripts/web-server.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/scripts/web-server.js?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/scripts/web-server.js (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/scripts/web-server.js Tue May  7 20:50:17 2013
@@ -93,6 +93,8 @@ StaticServlet.prototype.handleRequest = 
   var parts = path.split('/');
   if (parts[parts.length-1].charAt(0) === '.')
     return self.sendForbidden_(req, res, path);
+  if (~path.indexOf("dirlist"))
+    return self.getDirList_(req, res, path);
   fs.stat(path, function(err, stat) {
     if (err)
       return self.sendMissing_(req, res, path);
@@ -164,6 +166,55 @@ StaticServlet.prototype.sendRedirect_ = 
   util.puts('301 Moved Permanently: ' + redirectUrl);
 };
 
+StaticServlet.prototype.getDirList_ = function(req, res, path) {
+  res.writeHead(200, {
+	'Content-Type': 'json'
+  });
+
+  // Grab the passed path value
+  var pathQuery = url.parse(req.url, true).query.path
+  // Using the supplied path, grab directory information
+  var dirList = fs.readdirSync(pathQuery);
+
+  // Filter out any hidden files or current/previous directory references
+  dirList = dirList.filter(function(item, index, array) {
+	return (item[0] !== ".");
+  });
+  
+  // Generate the full path names for all the items found when 'ls'-ing 
+  // the passed directory.
+  dirList = dirList.map(function(item, index, array) {
+    var temp = item; 
+
+	// Make sure the path is joined properly. Sometimes there will be a trailing
+	// '/' in the path and sometimes there won't. Don't want to end up with '//'.
+    if (pathQuery[pathQuery.length - 1] === "/") {
+      temp = pathQuery + item;
+    } else {
+  	  temp = pathQuery + "/" + item;
+    }
+  
+	// We want the directories that are found to have a trailing '/'. Let's make sure
+	// that we do that!
+    var ret = temp;
+    if (fs.existsSync(temp + "/")) {
+     ret = temp + "/";
+    }  
+
+    return ret;
+  });
+  
+  // Sort all the results alphabetically ignoring case.
+  dirList = dirList.sort(function(a, b) {
+    if (a.toLowerCase() < b.toLowerCase()) return -1;
+    if (a.toLowerCase() > b.toLowerCase()) return 1;
+    return 0;
+  });
+
+  res.write(JSON.stringify(dirList));
+  res.end();
+}
+
 StaticServlet.prototype.sendFile_ = function(req, res, path) {
   var self = this;
   var file = fs.createReadStream(path);

Modified: incubator/climate/trunk/rcmet/src/main/ui/test/unit/directivesSpec.js
URL: http://svn.apache.org/viewvc/incubator/climate/trunk/rcmet/src/main/ui/test/unit/directivesSpec.js?rev=1480068&r1=1480067&r2=1480068&view=diff
==============================================================================
--- incubator/climate/trunk/rcmet/src/main/ui/test/unit/directivesSpec.js (original)
+++ incubator/climate/trunk/rcmet/src/main/ui/test/unit/directivesSpec.js Tue May  7 20:50:17 2013
@@ -1,9 +1,5 @@
 'use strict';
 
-/* jasmine specs for directives go here */
-
-// This should be testing the Leaflet map directive...
-//*
 describe('directives', function() {
 	beforeEach(module('rcmes'));
 
@@ -17,8 +13,9 @@ describe('directives', function() {
 			})
 		});
 	});
-
 	//*/
+	
+	// Testing the Bootstrap Modal directive
 	describe('bootstrap-modal directive', function() {
 		it('should create a div element of the correct form', function() {
 			inject(function($compile, $rootScope) {
@@ -37,4 +34,3 @@ describe('directives', function() {
 		});
 	});
 });
-//*/



Mime
View raw message