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">&times;</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">&times;</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">&deg; North</span>
+        </div>
+        <div class="input-group">
+          <input type="number" class="form-control" name="startLong" placeholder="Start Longitude">
+          <span class="input-group-addon">&deg; East</span>
+        </div>
+        <div class="input-group">
+          <input type="number" class="form-control" name="endLat" placeholder="End Latitude">
+          <span class="input-group-addon">&deg; North</span>
+        </div>
+        <div class="input-group values">
+          <input type="number" class="form-control" name="endLong" placeholder="End Longitude">
+          <span class="input-group-addon">&deg; East</span>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="customTemplate">
+      <button class="col-xs-1 close" type="button">&times;</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.");