New subsetting interface.
+ Fixes and improvements.
+ Added default value for subsetting variables.
Change-Id: I3b4681bfa4dc85e877465cec29d00166069d91b3
diff --git a/client/catalog-dev/css/style.css b/client/catalog-dev/css/style.css
index 31c86b8..ee3e460 100644
--- a/client/catalog-dev/css/style.css
+++ b/client/catalog-dev/css/style.css
@@ -207,10 +207,9 @@
position: relative;
}
-.absBotRight {
- position: absolute;
- bottom: 0;
- right: 0;
+.floatRight {
+ float: right;
+ margin-right: 15px;
}
.fade {
@@ -239,3 +238,25 @@
word-wrap: normal;
overflow: auto;
}
+
+#requestForm button {
+ margin-bottom: 5px;
+}
+
+#templates {
+ display: none;
+}
+
+#subsetVariables > .row:not(:last-child) {
+ margin-bottom: 5px;
+}
+
+#subsetVariables {
+ transition: opacity 1s, margin 1s, padding 1s;
+}
+
+#subsetVariables:empty {
+ opacity: 0;
+ margin: 0;
+ padding: 0;
+}
diff --git a/client/catalog-dev/index.html b/client/catalog-dev/index.html
index 52e7c6e..1c1e03c 100644
--- a/client/catalog-dev/index.html
+++ b/client/catalog-dev/index.html
@@ -33,6 +33,53 @@
</head>
<body id="body">
+
+ <div id="templates">
+ <div class="row" id="timeTemplate">
+ <button class="col-xs-1 close" type="button">×</button>
+ <div class="col-xs-4">
+ <input type="text" class="form-control variable" value="time">
+ </div>
+ <div class="col-xs-7 values">
+ <input type="datetime" class="form-control" name="start" placeholder="Start">
+ <input type="datetime" class="form-control" name="end" placeholder="End">
+ </div>
+ </div>
+ <div class="row" id="locationTemplate">
+ <button class="col-xs-1 close" type="button">×</button>
+ <div class="col-xs-4">
+ <input type="text" class="form-control variable" value="coord">
+ </div>
+ <div class="col-xs-7 values">
+ <div class="input-group">
+ <input type="number" class="form-control" name="startLat" placeholder="Start Latitude">
+ <span class="input-group-addon">° North</span>
+ </div>
+ <div class="input-group">
+ <input type="number" class="form-control" name="startLong" placeholder="Start Longitude">
+ <span class="input-group-addon">° East</span>
+ </div>
+ <div class="input-group">
+ <input type="number" class="form-control" name="endLat" placeholder="End Latitude">
+ <span class="input-group-addon">° North</span>
+ </div>
+ <div class="input-group values">
+ <input type="number" class="form-control" name="endLong" placeholder="End Longitude">
+ <span class="input-group-addon">° East</span>
+ </div>
+ </div>
+ </div>
+ <div class="row" id="customTemplate">
+ <button class="col-xs-1 close" type="button">×</button>
+ <div class="col-xs-4">
+ <input type="text" class="form-control variable" placeholder="Variable Name">
+ </div>
+ <div class="col-xs-7 values">
+ <input type="text" class="form-control" name="value" placeholder="Custom Restriction">
+ </div>
+ </div>
+ </div>
+
<header>
<div class="navbar navbar-inverse navbar-static-top container-fluid">
<div class="navbar-header">
@@ -50,14 +97,18 @@
<div class="row">
- <div class="col-sm-12 col-md-12">
+ <div class="col-sm-12 col-md-12">
<div id="alerts"></div>
<div class="panel panel-info tab-pane fade in active" id="filterSearch">
<div class="panel-heading">Filter Based Search</div>
<div class="panel-body">
- <div class="col-sm-3 col-md-2 sidebar">
+ <div>
+ <div id="filters"></div>
+ <button id="searchButton" class="btn btn-primary right-fix">Search</button>
+ </div>
+ <div>
<div class="panel panel-primary">
<div class="panel-heading">
<span>Filter Categories</span>
@@ -67,10 +118,6 @@
</div>
</div>
</div>
- <div class="col-sm-9 col-md-10">
- <div id="filters"></div>
- <button id="searchButton" class="btn btn-primary right-fix">Search</button>
- </div>
</div>
</div>
@@ -80,9 +127,7 @@
<form class="form-inline" id="searchBar">
<div class="form-group">
<div class="input-group">
- <input id="search" placeholder="Enter a path (Ex: /CMIP5)"
- type="text" class="form-control"
- title="This bar is for doing a tab completion like path completion. Press tab, enter, or click AutoComplete to begin your search.">
+ <input id="search" placeholder="Enter a path (Ex: /CMIP5)" type="text" class="form-control">
<div class="input-group-btn">
<button id="autoCompleteSearch" type="submit" class="btn btn-primary">Search</button>
</div>
@@ -183,6 +228,19 @@
</button>
<ul id="requestDest" class="dropdown-menu"></ul>
</div>
+ <div class="panel panel-default" id="subsetting">
+ <div class="panel-heading">
+ <a data-toggle="collapse" href="#subsetMenu">Subsetting</a>
+ </div>
+ <div class="panel-collapse collapse" id="subsetMenu">
+ <div class="panel-body">
+ <div id="subsetVariables" class="well"></div>
+ <button type="button" class="btn btn-default" id="subsetAddVariableBtn">Add Variable</button>
+ <button type="button" class="btn btn-default" id="subsetAddTimeVariable">Add Time Variable</button>
+ <button type="button" class="btn btn-default" id="subsetAddLocVariable">Add Location Variable</button>
+ </div>
+ </div>
+ </div>
<!-- Disabled For Demo
<div class="form-group">
<label>Authentication Key</label>
@@ -191,9 +249,11 @@
</div>
<div id="requestDrop" class="well">You can also drop your key here instead of using the input above.</div>
-->
- <div class="absBotRight">
- <button type="submit" class="btn btn-primary">Submit</button>
- <button id="requestCancel" type="button" class="btn btn-default">Cancel</button>
+ <div class="row">
+ <div class="floatRight">
+ <button type="submit" class="btn btn-primary">Submit</button>
+ <button id="requestCancel" type="button" class="btn btn-default">Cancel</button>
+ </div>
</div>
</form>
</div>
diff --git a/client/catalog-dev/js/catalog.js b/client/catalog-dev/js/catalog.js
index 158aad9..d8937cd 100644
--- a/client/catalog-dev/js/catalog.js
+++ b/client/catalog-dev/js/catalog.js
@@ -251,6 +251,14 @@
e.preventDefault();
});
+ this.resultTable.on('click', '.subsetButton', function(){
+ var metaData = $(this).siblings('pre').text();
+ var exp = /netcdf ([\w-]+)/;
+ var match = exp.exec(metaData);
+ var filename = match[0].replace(/netcdf /,'') + '.nc';
+ scope.request(null, filename);
+ });
+
}
Atmos.prototype.clearResults = function(){
@@ -393,6 +401,8 @@
this.resultMenu.find('.previous').removeClass('disabled');
}
+ $.scrollTo("#results", 500, {interrupt: true});
+
}
Atmos.prototype.getResults = function(index){
@@ -401,8 +411,6 @@
$('#results').removeClass('hidden').slideDown();
}
- $.scrollTo("#results", 500, {interrupt: true});
-
if ((this.results.length === this.resultCount) || (this.resultsPerPage * (index + 1) < this.results.length)){
//console.log("We already have index", index);
this.page = index;
@@ -524,6 +532,7 @@
* Requests all of the names represented by the buttons in the elements list.
*
* @param elements {Array<jQuery>} A list of the table row elements
+ * @param subsetFileName {String} If present then do a subsetting request instead.
*/
Atmos.prototype.request = function(){
@@ -532,30 +541,51 @@
var certificateName;
var keyAdded = false;
- return function(elements){
+ return function(elements, subsetFilename){
var names = [];
- var destination = $('#requestDest .active').text();
- $(elements).find('>*:nth-child(2)').each(function(){
+ $(elements).find('.metaDataLink').each(function(){
var name = $(this).text();
names.push(name);
- });//.append('<span class="badge">Requested!</span>')
- //Disabling the checkbox doesn't make sense anymore with the ability to request to multiple destinations.
- //$(elements).find('.resultSelector').prop('disabled', true).prop('checked', false);
+ });
+
+ var subset = false;
+
+ if (!subsetFilename){
+ $('#subsetting').hide();
+ } else {
+ $('#subsetting').show();
+ subset = true;
+ }
var scope = this;
this.requestForm.on('submit', function(e){ //This will be registered for the next submit from the form.
e.preventDefault();
+ $('#request .alert').remove();
+
+ var variables = [];
+ if (subset){
+ $('#subsetVariables .row').each(function(){
+ var t = $(this);
+ var values = {};
+ t.find('.values input').each(function(){
+ var t = $(this);
+ values[t.attr('name')] = t.val();
+ });
+ variables.push({variable: t.find('.variable').val(), values: values});
+ });
+ }
+
//Form checking
var dest = scope.requestForm.find('#requestDest .active');
if (dest.length !== 1){
- $('#requestForm').append($('<div class="alert alert-warning">A destination is required!' + closeButton + '<div>'));
+ var alert = $('<div class="alert alert-warning">A destination is required!' + closeButton + '<div>');
+ $('#request > .panel-body').append(alert);
return;
}
- $('#request').modal('hide')//Initial params are ok. We can close the form.
- .remove('.alert') //Remove any alerts
+ $('#request').modal('hide');//Initial params are ok. We can close the form.
scope.cleanRequestForm();
@@ -600,8 +630,14 @@
//This function will exist until the page exits but will likely only be used once.
var data = new Data(interest.getName());
- var content = JSON.stringify(names);
- data.setContent(content);
+ var content;
+ if (subset){
+ content = JSON.stringify({name: subsetFilename, subset: variables});
+ } else {
+ content = JSON.stringify(names);
+ }
+ //Blob breaks the data! Don't use it
+ data.setContent(content); //TODO Packetize this.
keyChain.sign(data, certificateName);
try {
@@ -616,7 +652,6 @@
}, function(prefix){ //On fail
scope.createAlert("Failed to register the retrieval URI! See console for details.", "alert-danger");
console.error("Failed to register URI:", prefix.toUri(), prefix);
-
}, function(prefix, registeredPrefixId){ //On success
var name = new Name(dest.text());
name.append(prefix);
@@ -717,8 +752,13 @@
/**
* This function retrieves all segments in order until it knows it has reached the last one.
* It then returns the final joined result.
+ *
+ * @param prefix {String|Name} The ndn name we are retrieving.
+ * @param callback {function(String)} if successful, will call the callback with a string of data.
+ * @param failure {function(Interest)} if unsuccessful, will call failure with the last failed interest.
+ * @param stop {boolean} stop if no finalBlock.
*/
- Atmos.prototype.getAll = function(prefix, callback, failure){
+ Atmos.prototype.getAll = function(prefix, callback, failure, stop){
var scope = this;
var d = [];
@@ -726,7 +766,7 @@
var count = 3;
var retry = function(interest){
if (count === 0){
- console.log("Failed to 'getAll' after 3 attempts", interest);
+ console.log("Failed to 'getAll' after 3 attempts", interest.toUri(), interest);
failure(interest);
} else {
count--;
@@ -751,8 +791,11 @@
d.push(data.getContent().toString());
- if (data.getMetaInfo().getFinalBlockId().value.length !== 0 &&
- interest.getName().get(-1).toSegment() == data.getMetaInfo().getFinalBlockId().toSegment()){
+ var hasFinalBlock = data.getMetaInfo().getFinalBlockId().value.length === 0;
+ var finalBlockStop = hasFinalBlock && stop;
+
+ if (finalBlockStop ||
+ (!hasFinalBlock && interest.getName().get(-1).toSegment() == data.getMetaInfo().getFinalBlockId().toSegment())){
callback(d.join(""));
} else {
request(interest.getName().get(-1).toSegment() + 1);
@@ -768,6 +811,9 @@
$('#requestDest').prev().removeClass('btn-success').addClass('btn-default');
$('#requestDropText').text('Destination');
$('#requestDest .active').removeClass('active');
+ $('#subsetMenu').attr('class', 'collapse');
+ $('#subsetVariables').empty();
+ $('#request .alert').alert('close').remove();
}
Atmos.prototype.setupRequestForm = function(){
@@ -796,49 +842,23 @@
$('#requestDest').prev().removeClass('btn-default').addClass('btn-success');
});
- //This code will remain unused until users must use their own keys instead of the demo key.
-// var scope = this;
+ var addVariable = function(selector){
+ var ele = $(selector).clone().attr('id','');
+ ele.find('.close').click(function(){
+ ele.remove();
+ });
+ $('#subsetVariables').append(ele);
+ };
-// var warning = '<div class="alert alert-warning">' + closeButton + '<div>';
-
-// var handleFile = function(e){
-// var t = $(this);
-// if (e.target.files.length > 1){
-// var el = $(warning);
-// t.append(el.append("We are looking for a single file, we will try the first only!"));
-// } else if (e.target.files.length === 0) {
-// var el = $(warning.replace("alert-warning", "alert-danger"));
-// t.append(el.append("No file was supplied!"));
-// return;
-// }
-
-// var reader = new FileReader();
-// reader.onload = function(e){
-// var key;
-// try {
-// key = JSON.parse(e.target.result);
-// } catch (e) {
-// console.error("Could not parse the key! (", key, ")");
-// var el = $(warning.replace("alert-warning", "alert-danger"));
-// t.append(el.append("Failed to parse the key file, is it a valid json key?"));
-// }
-
-// if (!key.DEFAULT_RSA_PUBLIC_KEY_DER || !key.DEFAULT_RSA_PRIVATE_KEY_DER) {
-// console.warn("Invalid key", key);
-// var el = $(warning.replace("alert-warning", "alert-danger"));
-// t.append(el.append("Failed to parse the key file, it is missing required attributes."));
-// }
-
-
-// };
-
-// }
-
-// this.requestForm.find('#requestDrop').on('dragover', function(e){
-// e.dataTransfer.dropEffect = 'copy';
-// }).on('drop', handleFile);
-
-// this.requestForm.find('input[type=file]').change(handleFile);
+ $('#subsetAddVariableBtn').click(function(){
+ addVariable('#customTemplate');
+ });
+ $('#subsetAddTimeVariable').click(function(){
+ addVariable('#timeTemplate');
+ });
+ $('#subsetAddLocVariable').click(function(){
+ addVariable('#locationTemplate');
+ });
}
@@ -849,8 +869,12 @@
return function(element) {
var name = $(element).text();
+ ga('send', 'event', 'request', 'metaData');
+
+ var subsetButton = '<button class="btn btn-default subsetButton" type="button">Subset</button>';
+
if (cache[name]) {
- return ['<pre class="metaData">', cache[name], '</pre>'].join('');
+ return [subsetButton, '<pre class="metaData">', cache[name], '</pre>'].join('');
}
var prefix = new Name(name).append("metadata");
@@ -860,7 +884,10 @@
this.getAll(prefix, function(data){
var el = $('<pre class="metaData"></pre>');
el.text(data);
- $('#' + id).remove('span').append(el);
+ var container = $('<div></div>');
+ container.append($(subsetButton));
+ container.append(el);
+ $('#' + id).empty().append(container);
cache[name] = data;
}, function(interest){
$('#' + id).text("The metadata is unavailable for this name.");