Completed the search function on ndn-atmos.

Additionally cleaned up the submodules.
All old code has been removed.

Change-Id: If653f3e4b8d18f3c71052c123bc7d1593e50b5d6
diff --git a/.gitmodules b/.gitmodules
index 91eb1ee..6b09fa1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,13 @@
 [submodule "client/ndn-js"]
 	path = client/ndn-js
 	url = https://github.com/named-data/ndn-js.git
+	ignore = dirty
 [submodule "ndn-cxx"]
 	path = ndn-cxx
 	url = https://github.com/named-data/ndn-cxx.git
+	ignnore = dirty
 [submodule "ChronoSync"]
 	path = ChronoSync
 	url = https://github.com/named-data/ChronoSync.git
+	ignore = dirty
+
diff --git a/ChronoSync b/ChronoSync
index ea488fd..edaef7c 160000
--- a/ChronoSync
+++ b/ChronoSync
@@ -1 +1 @@
-Subproject commit ea488fdeea0458fa32e194ef23ff842d0f932d55
+Subproject commit edaef7c4123f6e250a741912b2747b0b7e233b8a
diff --git a/client/query/autocomplete.js b/client/query/autocomplete.js
index 6edd6d5..e918390 100644
--- a/client/query/autocomplete.js
+++ b/client/query/autocomplete.js
@@ -5,23 +5,10 @@
  * 
  * Then simply call $('.someClass').autoComplete(getSuggestions) on it to enable auto completion.
  * 
- * Also add
- * ```
- * .autoComplete {
- *  position: absolute;
- *  top: 38px;
- *  width: 229px;
- * }
- * 
- * .autoComplete .list-group {
- *  margin-bottom: 0;
- *  border: none;
- * }
- * ```
- * to an included css file.
- * 
  * getSuggestions returns by calling its callback parameter with an array of valid strings.
  * 
+ * Autocomplete can be manually triggered by triggering the autoComplete event.
+ * 
  */
 (function(){
   "use strict";
@@ -123,6 +110,10 @@
 
       });
 
+      this.on('autoComplete', function(){
+        getSuggestions(input.val(), setAutoComplete);
+      });
+
       return this;
 
     }
diff --git a/client/query/query.html b/client/query/query.html
index 860f449..bcd98d0 100644
--- a/client/query/query.html
+++ b/client/query/query.html
@@ -44,7 +44,10 @@
 
       <div class="col-sm-3 col-md-2 sidebar">
         <div class="panel panel-default">
-          <div class="panel-heading">Filter Categories</div>
+          <div class="panel-heading flex flex-row">
+            <span class="vertical-fix width-fix">Filter Categories</span>
+            <button id="searchButton" class="btn btn-primary right-fix">Search</button>
+          </div>
           <div class="panel-body">
             <ul id="side-menu" class="nav nav-pills nav-stacked"></ul>
           </div>
@@ -58,11 +61,12 @@
             <div id="filters"></div>
             <form class="form-inline" id="searchBar">
               <div class="form-group">
-                <label for="searchBar">Search:&nbsp;</label>
                 <div class="input-group">
-                  <input id="search" placeholder="/cmip" type="text" class="form-control" data-toggle="tooltip" data-placement="top" title="Press Tab for autocomplete">
+                  <input id="search" placeholder="Enter a path" value="/"
+                   type="text" class="form-control" data-toggle="tooltip" data-placement="top"
+                   title="This bar is for doing a tab completion like path completion. Press tab, enter, or click AutoComplete to begin your search.">
                   <div class="input-group-btn">
-                    <button id="searchButton" type="submit" class="btn btn-primary">Search</button>
+                    <button id="autoCompleteButton" type="submit" class="btn btn-default">AutoComplete</button>
                   </div>
                 </div>
               </div>
@@ -70,15 +74,27 @@
           </div>
         </div>
 
-        <div class="panel panel-default">
+        <div class="panel panel-primary">
           <div class="panel-heading">Results</div>
           <div class="panel-body">
             <nav>
-              <ul id="resultPages" class="pagination"></ul>
-              <span id="resultTotalPages"></span>
+              <ul class="pager">
+                <li class="previous disabled"><a href="#">&larr; Previous</a></li>
+                <li><span class="totalResults">0 Results</span></li>
+                <li class="next disabled"><a href="#">Next &rarr;</a></li>
+              </ul>
             </nav>
             <table id="resultTable" class="table"></table>
           </div>
+          <div class="panel-footer">
+            <nav>
+              <ul class="pager">
+                <li class="previous disabled"><a href="#">&larr; Previous</a></li>
+                <li><span class="totalResults">0 Results</span></li>
+                <li class="next disabled"><a href="#">Next &rarr;</a></li>
+              </ul>
+            </nav>
+          </div>
         </div>
 
       </div>
diff --git a/client/query/query.js b/client/query/query.js
index 258afd8..0546cd7 100644
--- a/client/query/query.js
+++ b/client/query/query.js
@@ -26,8 +26,10 @@
     //Internal variables.
     this.results = [];
     this.resultCount = 0;
-    this.page = 1;
-    this.itemsPerPage = 25;
+    this.name = null;
+    this.page = 0;
+    this.lastPage = -1;
+    //this.itemsPerPage = 25; //TODO
 
     this.catalog = catalog;
 
@@ -37,6 +39,8 @@
     this.filters = $('#filters');
     this.searchInput = $('#search');
     this.searchBar = $('#searchBar');
+    this.searchButton = $('#searchButton');
+    this.pagers = $('.pager');
 
     var scope = this;
 
@@ -109,33 +113,44 @@
       });
     });
 
+    this.searchInput.autoComplete(function(field, callback){
+      scope.autoComplete(field, callback);
+    });
+
     this.searchBar.submit(function(e){
       e.preventDefault();
+      scope.searchInput.trigger('autoComplete');
+    });
+
+    this.searchButton.click(function(){
+      console.log("Search Button Pressed");
       scope.search();
     });
 
-    this.searchInput.autoComplete(function(field, callback){
-      scope.autoComplete(field, callback);
+    this.pagers.find('.next').click(function(){
+      if (!$(this).hasClass('disabled')){
+        scope.getResults(scope.page + 1);
+      }
+    });
+    this.pagers.find('.previous').click(function(){
+      if (!$(this).hasClass('disabled')){
+        scope.getResults(scope.page - 1);
+      }
     });
 
   }
 
   Atmos.prototype.search = function(){
-    var filters = this.getFilters();
 
-    if (this.searchInput.val().length === 0 && !filters.hasOwnProperty()){
-      if (!this.searchBar.hasClass('has-error')){
-        this.searchBar.addClass('has-error').append('<span class="help-block">A filter or search value is required!</span>');
-      }
-      return;
-    } else {
-      this.searchBar.removeClass('has-error').find('.help-block').fadeOut(function(){$(this).remove()});
-    }
+    var filters = this.getFilters();
 
     console.log("Search started!", this.searchInput.val(), filters);
 
     console.log("Initiating query"); 
 
+    this.results = []; //Drop any old results.
+    this.resultTable.empty();
+
     var scope = this;
 
     this.query(this.catalog, filters, 
@@ -146,29 +161,10 @@
 
       var ack = data.getName();
 
-      var name = new Name(scope.catalog).append("query-results").append(parameters)
-      .append(ack.get(ack.getComponentCount() - 3)).append(ack.getComponent(ack.getComponentCount() - 2))
+      scope.name = new Name(scope.catalog).append("query-results").append(parameters)
+      .append(ack.get(ack.getComponentCount() - 3)).append(ack.getComponent(ack.getComponentCount() - 2));
 
-      var first = new Name(name).appendSegment(0);
-
-      console.log("Next request:", first.toUri());
-
-      scope.face.expressInterest(new Interest(first).setInterestLifetimeMilliseconds(10000),
-        function(interest, data){ //Response
-  
-          var content = JSON.parse(data.getContent().toString().replace(/[\n\0]/g,""));
-
-          var results = scope.results[0] = content.results;
-          scope.resultCount = content.resultCount;
-
-          console.log("Got results:", results);
-
-        },
-        function(interest){ //Timeout
-          console.error("Failed to retrieve results: timeout");
-        }
-      );
-      
+      scope.getResults(0);
 
     }, function(interest){ //Timeout function
       console.error("Request failed! Timeout");
@@ -179,6 +175,15 @@
   Atmos.prototype.autoComplete = function(field, callback){
     console.log("Autocomplete triggered");
 
+    if (this.searchInput.val().length === 0 && !filters.hasOwnProperty()){
+      if (!this.searchBar.hasClass('has-error')){
+        this.searchBar.addClass('has-error').append('<span class="help-block">A filter or search value is required!</span>');
+      }
+      return;
+    } else {
+      this.searchBar.removeClass('has-error').find('.help-block').fadeOut(function(){$(this).remove()});
+    }
+
     var filters = this.getFilters();
 
     filters["?"] = this.searchInput.val();
@@ -192,39 +197,87 @@
 
   }
 
-//   Atmos.prototype.onData = function(data) {
-//     console.log("Recieved data", data);
+  Atmos.prototype.showResults = function(resultIndex) {
 
-//     var payloadStr = data.content.toString().split("\n")[0];
+    var results = $('<tr><td>' + this.results[resultIndex].join('</td><td><button class="interest-button btn btn-primary btn-sm">Retrieve</button></td></tr><tr><td>') +
+    '</td><td><button class="interest-button btn btn-primary btn-sm">Retrieve</button></td></tr>'); //Fastest way to generate the table.
 
-//     if (!payloadStr || payloadStr.length === 0){
-//       this.populateResults();
-//       return; //No results were returned.
-//     }
+    this.resultTable.empty().append(results);
 
-//     var queryResults = JSON.parse(payloadStr);
+    this.pagers.find('.totalResults').text(this.results[resultIndex].length + " of " + this.resultCount + " results");
 
-//     var scope = this;
+    if (resultIndex === this.lastPage) {
+      this.pagers.find('.next').addClass('disabled');
+    }
 
-//     $.each(queryResults, function (queryResult, field) {
+    if (resultIndex === 0){
+      this.pagers.find('.next').removeClass('disabled');
+      this.pagers.find('.previous').addClass('disabled');
+    } else if (resultIndex === 1) {
+      this.pagers.find('.previous').removeClass('disabled');
+    }
 
-//       if (queryResult == "next") {
-//         scope.populateAutocomplete(field);
-//       }
+  }
 
-//       if (queryResult == "results" && field == null){
-//         return; //Sometimes the results are null. (We should skip this.)
-//       }
+  Atmos.prototype.getResults = function(index){
 
-//       $.each(field, function (entryCount, name) {
-//         scope.results.push(name);
-//       });
-//     });http://stackoverflow.com/questions/26334964/json-parse-unexpected-token-error
+    if (this.results[index]){
+      //console.log("We already have index", index);
+      this.page = index;
+      this.showResults(index);
+      return;
+    }
 
-//     // Calculating the current page and the view
-//     this.populateResults();
+    if (this.name === null) {
+      console.error("This shouldn't be reached! We are getting results before a search has occured!");
+      throw new Error("Illegal State");
+    }
 
-//   }
+    var first = new Name(this.name).appendSegment(index);
+
+    console.log("Requesting data index: (", index, ") at ", first);
+
+    var scope = this;
+
+    this.face.expressInterest(new Interest(first).setInterestLifetimeMilliseconds(5000),
+      function(interest, data){ //Response
+
+        if (data.getContent().length === 0){
+          console.log("Empty response.");
+          return;
+        }
+
+        if (data.getName().get(-1).equals(data.getMetaInfo().getFinalBlockId())) { //Final page.
+          scope.lastPage = index;
+          //The next buttons will be disabled by showResults.
+        }
+
+        var content = JSON.parse(data.getContent().toString().replace(/[\n\0]/g,""));
+
+        var results = scope.results[index] = content.results;
+
+        scope.resultCount = content.resultCount;
+
+        scope.pagers.find('.totalResults').text(scope.resultCount + " Results");
+
+        //console.log("Got results:", results);
+
+        scope.page = index;
+
+        if (!results){
+          console.log("No results were found!");
+          return;
+        }
+
+        scope.showResults(index);
+
+      },
+      function(interest){ //Timeout
+        console.error("Failed to retrieve results: timeout");
+      }
+    );
+
+  }
 
   Atmos.prototype.query = function(prefix, parameters, callback, timeout) {
 
@@ -241,189 +294,6 @@
 
   }
 
-//   Atmos.prototype.expressNextInterest = function() {
-//     // @todo pipelines
-//     var nextName = new Name(this.state["results"]);
-//     nextName.appendSegment(this.state["nextSegment"]);
-
-//     var nextInterest = new Interest(nextName);
-//     nextInterest.setInterestLifetimeMilliseconds(10000);
-
-//     var scope = this;
-
-//     this.face.expressInterest(nextInterest,
-//         function(interest, data){
-//           scope.onQueryResultsData(interest, data);
-//         },
-//         function(interest){
-//           scope.onQueryResultsTimeout(interest);
-//         });
-
-//     this.state["nextSegment"] ++;
-//     this.state["outstanding"][nextName.toUri()] = 0;
-//   }
-
-//   Atmos.prototype.onQueryData = function(interest, data) {
-//     var name = data.getName();
-
-//     delete this.state["outstanding"][interest.getName().toUri()];
-
-//     this.state["version"] = name.get(this.state["prefix"].size() + 2).toVersion();
-
-//     this.state["results"] = new Name(this.state["prefix"]).append("query-results").append(this.state['parameters'])
-//     .appendVersion(this.state["version"]).append(name.getComponent(name.getComponentCount() - 2));
-
-//     console.log("Requested URI", this.state.results.toUri());
-
-//     this.expressNextInterest();
-//   }
-
-//   Atmos.prototype.onQueryResultsData = function(interest, data) {
-//     var name = data.getName();
-//     delete this.state["outstanding"][interest.getName().toUri()];
-//     if (!name.get(-1).equals(data.getMetaInfo().getFinalBlockId())) {
-//       this.expressNextInterest();
-//     } //else {
-//     //alert("found final block");
-//     //}
-
-//     this.state["userOnData"](data);
-//   }
-
-//   Atmos.prototype.onQueryTimeout = function(interest) {
-//     var uri = interest.getName().toUri();
-//     if (this.state["outstanding"][uri] < 1) {
-//       this.state["outstanding"][uri] ++;
-//       var scope = this;
-//       this.face.expressInterest(interest,
-//           function(interest, data){
-//             scope.onQueryData(interest, data);
-//           },
-//           function(interest){
-//             scope.onQueryTimeout(interest);
-//           });
-//     } else {
-//       delete this.state["outstanding"][uri];
-
-//       // We modify the autocomplete box here because we need to know
-//       // we have all of the entries first. Fairly hacky.
-//       /* TODO FIXME
-//          var autocompleteFullName = this.autocompleteText.value;
-//          for (var i = 0; i < dropdown.length; ++i) {
-//          if (this.dropdown[i].substr(0, dropdown[i].length - 1).toUpperCase === this.autocompleteText.value.toUpperCase || dropdown.length == 1) {
-//          autocompleteText.value = dropdown[i];
-//          }
-//          }
-//          */
-//     }
-//   }
-
-//   Atmos.prototype.onQueryResultsTimeout = function(interest) {
-//     var uri = interest.getName().toUri();
-//     if (this.state["outstanding"][uri] < 1) {
-//       this.state["outstanding"][uri] ++;
-//       var scope = this;
-//       this.face.expressInterest(interest,
-//           function(){
-//             scope.onQueryResultsData.apply(scope, arguments);
-//           },
-//           function(){
-//             scope.onQueryResultsTimeout.apply(scope, arguments);
-//           });
-//     } else {
-//       delete this.state["outstanding"][uri];
-//       // We modify the autocomplete box here because we need to know
-//       // we have all of the entries first. Fairly hacky.
-//       /* TODO FIXME
-//          var autocompleteFullName = autocompleteText.value;
-//          for (var i = 0; i < dropdown.length; ++i) {
-//          if (dropdown[i].substr(0, dropdown[i].length - 1).toUpperCase === autocompleteText.value.toUpperCase || dropdown.length == 1) {
-//          autocompleteText.value = dropdown[i];
-//          }
-//          }
-//          */
-//     }
-//   }
-//   **/
-
-//   Atmos.prototype.populateResults = function() {
-
-//     //TODO Check only for page changes and result length
-
-//     this.resultTable.empty();
-
-//     for (var i = startIndex; i < startIndex + 20 && i < this.results.length; ++i) {
-//       this.resultTable.append('<tr><td>' + this.results[i]
-//           + '</td><td><button class="interest-button btn btn-primary btn-xs">Retrieve</button></td></tr>');
-//     }
-
-//     if (this.results.length <= 20) {
-//       this.page = 1;
-//     } else {
-//       this.page = startIndex / 20 + 1;
-//     }
-
-//     this.totalPages = Math.ceil(this.results.length / 20);
-
-//     //TODO Fix the page to fit the theme.
-//     var currentPage = $(".page");
-//     currentPage.empty();
-//     if (this.page != 1) {
-//       currentPage.append('<a href="#" onclick="getPage(this.id);" id="<"><</a>');
-//     }
-//     // This section of code creates the paging for the results.
-//     // To prevent it from having a 1000+ pages, it will only show the 5 pages before/after
-//     // the current page and the total pages (expect users not to really jump around a lot).
-//     for (var i = 1; i <= this.totalPages; ++i) {
-//       if (i == 1 || i == this.totalPages      // Min or max
-//           || (i <= this.page && i + 5 >= this.page)    // in our current page range
-//           || (i >= this.page && i - 5 <= this.page)) { // in our current page range
-//         if (i != this.page) {
-//           currentPage.append(' <a href="#" onclick="getPage(' + i + ');">' + i + '</a>');
-//             if (i == 1 && this.page > i + 5) {
-//               currentPage.append(' ... ');
-//             }
-//         } else {
-//           currentPage.append(' ' + i);
-//         }
-//       } else { // Need to skip ahead
-//         if (i == this.page + 6) {
-//           currentPage.append(' ... ');
-
-//           currentPage.append(' <a href="#" onclick="getPage(this.id);" id=">">></a>')
-//             i = this.totalPages - 1;
-//         }
-//       }
-//     }
-//     currentPage.append('  ' + this.results.length + ' results');
-//   }
-
-//   Atmos.prototype.getPage = function(clickedPage) {
-//     console.log(clickedPage);
-
-//     var nextPage = clickedPage;
-//     if (clickedPage === "<") {
-//       nextPage = this.page - 5;
-//     } else if (clickedPage === ">") {
-//       console.log("> enabled");
-
-//       nextPage = this.page + 5;
-//     }
-
-//     nextPage--; // Need to adjust for starting at 0
-
-//     if (nextPage < 0 ) {
-//       nextPage = 0;
-//       console.log("0 enabled");
-//     } else if (nextPage > this.totalPages - 1) {
-//       nextPage = this.totalPages - 1;
-//       console.log("total enabled");
-//     }
-
-//     this.populateResults(nextPage * 20);
-//     return false;
-//   }
-
   /**
    * This function returns a map of all the categories active filters.
    * @return {Object<string, string>}
diff --git a/client/query/query2.css b/client/query/query2.css
index cb94019..2119d4c 100644
--- a/client/query/query2.css
+++ b/client/query/query2.css
@@ -31,6 +31,35 @@
   border: none;
 }
 
+.flex {
+  display: flex;
+}
+
+.flex-row {
+  flex-display: row;
+}
+
+.vertical-fix {
+  line-height: 38px; /* Height of the button */
+  vertical-align: middle;
+}
+
+.width-fix {
+  flex: 1 auto;
+}
+
+.right-fix {
+  flex: auto;
+}
+
+.pager {
+  /* margin: 0; */
+}
+
+#searchBar {
+  display: inline-block;
+}
+
 #filters {
   margin-bottom: 10px;
   visibility: visible;