Lots of work on autocomplete and more refactoring code.

Autocomplete is nearly done, there is a new script to handle
autocomplete as a jquery plugin (currently uses bootstrap). The main
query.js code has been refactored to be inside an anonymous function to
permit use of "use strict" on all the code. Minimal functionality
changes other than a schema update.

Future: The actual retrieval code will need to be rewritten for the
new paging. This will actually make the code more efficient and easier
to read.

Change-Id: I1abf71469f56390ae7a3db9c4fb8889a756a40e0
diff --git a/client/query/autocomplete.js b/client/query/autocomplete.js
new file mode 100644
index 0000000..6edd6d5
--- /dev/null
+++ b/client/query/autocomplete.js
@@ -0,0 +1,136 @@
+/*
+ * The following code is a jquery extention written to add autocomplete functionality to bootstrap input groups.
+ * 
+ * Usage:
+ * 
+ * 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.
+ * 
+ */
+(function(){
+  "use strict";
+  if (!jQuery){
+    throw new Error("jQuery is required and must be loaded before this script.")
+  }
+  jQuery.fn.extend({
+    /**
+     * @param {Array<String>|getSuggestions}
+     */
+    autoComplete: function(suggestions) {
+
+      var element = $('<div></div>');
+      element.addClass('list-group')
+      .css({
+        'border-top-left-radius': 0,
+        'border-top-right-radius': 0,
+        'width': this.width(),
+        'position': 'absolute',
+        'top': this.parent().height(),
+        'display': 'none',
+        'max-height': '500px',
+        'overflow-y': 'auto'
+      });
+
+      this.focus(function(){
+        element.slideDown();
+      }).blur(function(){
+        element.slideUp();
+      }).before(element);
+
+      var getSuggestions = function(current, callback){
+        callback(suggestions.reduce(function(prev, suggestion){
+          if (current.toLowerCase().indexOf(suggestion.substr(0, current.length).toLowerCase()) === 0){
+            prev.push(suggestion);
+          }
+          return prev;
+        }, []));
+      }
+
+      var setAutoComplete = function(list){
+        element.empty();
+
+        element.html(list.reduce(function(prev, current){
+          return [prev, '<a href="#" class="list-group-item">', current, '</a>'].join("");
+        }, ""));
+
+      }
+
+      if (suggestions instanceof Function){
+        getSuggestions = suggestions;
+      }
+
+      var input = this;
+
+      element.bind('click', 'a', function(){
+        input.val($(this).text());
+      });
+
+      this.keydown(function(e){
+        switch(e.which){
+          case 38: //up
+          var active = element.find('.active');
+          if (active.length === 0){
+            element.find(':first-child').addClass('active');
+          } else {
+            if (!active.is(':first-child')){
+              active.removeClass('active').prev().addClass('active');
+            }
+          }
+          e.preventDefault();
+          break;
+
+          case 40: //down
+          var active = element.find('.active');
+          if (active.length === 0){
+            element.find(':first-child').addClass('active');
+          } else {
+            if (!active.is(':last-child')){
+              active.removeClass('active').next().addClass('active');
+            }
+          }
+          e.preventDefault();
+          break;
+
+          case 13: //Enter
+          var active = element.find('.active');
+          if (active.length === 1){
+            $(this).val(active.text());
+            e.preventDefault();
+          }
+          break;
+
+          case 9: //Tab
+          getSuggestions(input.val(), setAutoComplete);
+          e.preventDefault(); //Don't print tab
+          break;
+        }
+
+      });
+
+      return this;
+
+    }
+  });
+})();
+
+/**
+ * @callback getSuggestions
+ * @param {string} current - The current value of the input field.
+ * @param {function}
+ */
\ No newline at end of file
diff --git a/client/query/query.html b/client/query/query.html
index 1ee69df..860f449 100644
--- a/client/query/query.html
+++ b/client/query/query.html
@@ -24,6 +24,7 @@
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js"></script>
 <script src="../ndn-js/build/ndn.js"></script>
+<script src="autocomplete.js"></script>
 <script src="query.js"></script>
 
 </head>
@@ -55,21 +56,17 @@
         <div class="panel panel-default">
           <div class="panel-body">
             <div id="filters"></div>
-            <div class="autocomplete">
-              <div class="ui-widget">
-                <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">
-                      <div class="input-group-btn">
-                        <button id="searchButton" type="submit" class="btn btn-primary">Search</button>
-                      </div>
-                    </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">
+                  <div class="input-group-btn">
+                    <button id="searchButton" type="submit" class="btn btn-primary">Search</button>
                   </div>
-                </form>
+                </div>
               </div>
-            </div>
+            </form>
           </div>
         </div>
 
diff --git a/client/query/query.js b/client/query/query.js
index efca0dd..e20f8f6 100644
--- a/client/query/query.js
+++ b/client/query/query.js
@@ -9,491 +9,506 @@
   new Atmos(catalog, config);
 });
 
-/**
- * Atmos
- * @version 2.0
- * 
- * Configures an Atmos object. This manages the atmos interface.
- * 
- * @constructor 
- * @param {string} catalog - NDN path
- * @param {Object} config - Object of configuration options for a Face. 
- */
-function Atmos(catalog, config){
+var Atmos = (function(){
   "use strict";
-  //Internal variables.
-  this.results = []
-  this.resultCount = 0;
-  this.page = 1;
-  this.totalPages = 1;
-  this.selectedSearch = {};
-  this.dropdown = [];
-  this.state = {};
-  this.currentViewIndex = 0;
+  /**
+   * Atmos
+   * @version 2.0
+   * 
+   * Configures an Atmos object. This manages the atmos interface.
+   * 
+   * @constructor 
+   * @param {string} catalog - NDN path
+   * @param {Object} config - Object of configuration options for a Face. 
+   */
+  function Atmos(catalog, config){
 
-  this.catalog = catalog;
+    //Internal variables.
+    this.results = [];
+      this.resultCount = 0;
+    this.page = 1;
+    this.totalPages = 1;
+    this.selectedSearch = {};
+    this.dropdown = [];
+    this.state = {};
+    this.currentViewIndex = 0;
 
-  this.face = new Face(config);
-  this.categories = $('#side-menu');
-  this.resultTable = $('#resultTable');
-  this.filters = $('#filters');
+    this.catalog = catalog;
 
-  var scope = this;
+    this.face = new Face(config);
+    this.categories = $('#side-menu');
+    this.resultTable = $('#resultTable');
+    this.filters = $('#filters');
 
-  this.resultTable.on('click', '.interest-button', function(){
-    var button = $(this);
+    var scope = this;
 
-    if (button.is(':disabled')){
-      console.warn("Attempt to request again!");
-    }
+    this.resultTable.on('click', '.interest-button', function(){
+      var button = $(this);
 
-    var name = button.parent().prev().text();
-    var interest = new Interest(new Name('/retrieve' + name));
-    scope.face.expressInterest(interest, function(){}, function(){});
-
-    button.text("Requested!")
-    .removeClass('btn-primary')
-    .addClass('btn-default')
-    .addClass('disabled')
-    .prop('disabled', true);
-
-  });
-
-  $.getJSON("search_catagories.json").done(function (data) {
-    $.each(data, function (pageSection, contents) {
-      if (pageSection == "SearchCatagories") {
-        $.each(contents, function (search, searchOptions) {
-          var e = $('<li><a href="#">' + search.replace(/\_/g, " ") + '</a><ul class="subnav nav nav-pills nav-stacked"></ul></li>');
-
-          var sub = e.find('ul.subnav');
-          $.each(searchOptions, function(index, name){
-            var item = $('<li><a href="#">' + name + '</a></li>');
-            sub.append(item);
-            item.click(function(){
-              scope.addFilter(name, search);
-            });
-          });
-
-          //Toggle the menus. (Only respond when the immediate tab is clicked.)
-          e.find('> a').click(function(){
-            scope.categories.find('.subnav').slideUp();
-            var t = $(this).siblings('.subnav');
-            if ( !t.is(':visible')){
-              t.slideDown().triggerHandler('focus'); //Cancel other animations and slide down.
-            }
-          });
-
-          scope.categories.append(e);
-        });
+      if (button.is(':disabled')){
+        console.warn("Attempt to request again!");
       }
+
+      var name = button.parent().prev().text();
+      var interest = new Interest(new Name('/retrieve' + name));
+      scope.face.expressInterest(interest, function(){}, function(){});
+
+      button.text("Requested!")
+        .removeClass('btn-primary')
+        .addClass('btn-default')
+        .addClass('disabled')
+        .prop('disabled', true);
+
     });
-  });
 
-  $('#searchBar').submit(function(e){
-    e.preventDefault();
-    console.log("Search started!", $('#search'));
-  })
+    $.getJSON("search_catagories.json").done(function (data) {
+      $.each(data, function (pageSection, contents) {
+        if (pageSection == "SearchCatagories") {
+          $.each(contents, function (search, searchOptions) {
+            var e = $('<li><a href="#">' + search.replace(/\_/g, " ") + '</a><ul class="subnav nav nav-pills nav-stacked"></ul></li>');
 
-}
+            var sub = e.find('ul.subnav');
+            $.each(searchOptions, function(index, name){
+              var item = $('<li><a href="#">' + name + '</a></li>');
+              sub.append(item);
+              item.click(function(){
+                scope.addFilter(name, search);
+              });
+            });
 
-Atmos.prototype.onData = function(data) {
-  var payloadStr = data.content.toString().split("\n")[0];
+            //Toggle the menus. (Only respond when the immediate tab is clicked.)
+            e.find('> a').click(function(){
+              scope.categories.find('.subnav').slideUp();
+              var t = $(this).siblings('.subnav');
+              if ( !t.is(':visible')){
+                t.slideDown().triggerHandler('focus'); //Cancel other animations and slide down.
+              }
+            });
 
-  if (!payloadStr || payloadStr.length === 0){
-    this.populateResults();
-    return; //No results were returned.
+            scope.categories.append(e);
+          });
+        }
+      });
+    });
+
+    $('#searchBar').submit(function(e){
+      e.preventDefault();
+      console.log("Search started!", $('#search'));
+    })
+
   }
 
-  var queryResults = JSON.parse(payloadStr);
+  Atmos.prototype.onData = function(data) {
+    console.log("Recieved data", data);
 
-  var scope = this;
+    var payloadStr = data.content.toString().split("\n")[0];
 
-  $.each(queryResults, function (queryResult, field) {
-
-    if (queryResult == "next") {
-      scope.populateAutocomplete(field);
+    if (!payloadStr || payloadStr.length === 0){
+      this.populateResults();
+      return; //No results were returned.
     }
 
-    if (queryResult == "results" && field == null){
-      return; //Sometimes the results are null. (We should skip this.)
-    }
+    var queryResults = JSON.parse(payloadStr);
 
-    $.each(field, function (entryCount, name) {
-      scope.results.push(name);
+    var scope = this;
+
+    $.each(queryResults, function (queryResult, field) {
+
+      if (queryResult == "next") {
+        scope.populateAutocomplete(field);
+      }
+
+      if (queryResult == "results" && field == null){
+        return; //Sometimes the results are null. (We should skip this.)
+      }
+
+      $.each(field, function (entryCount, name) {
+        scope.results.push(name);
+      });
     });
-  });
 
-  // Calculating the current page and the view
-  this.populateResults();
+    // Calculating the current page and the view
+    this.populateResults();
 
-}
+  }
 
-Atmos.prototype.query = function(prefix, parameters, callback, pipeline) {
-  this.results = [];
-  this.dropdown = [];
-  this.resultTable.empty();
+  Atmos.prototype.query = function(prefix, parameters, callback, pipeline) {
+    this.results = [];
+    this.dropdown = [];
+    this.resultTable.empty();
 
-  var queryPrefix = new Name(prefix);
-  queryPrefix.append("query");
+    var queryPrefix = new Name(prefix);
+    queryPrefix.append("query");
 
-  var jsonString = JSON.stringify(parameters);
-  queryPrefix.append(jsonString);
+    var jsonString = JSON.stringify(parameters);
+    queryPrefix.append(jsonString);
 
-  this.state = {
+    this.state = {
       prefix: new Name(prefix),
       userOnData: callback,
       outstanding: {},
       nextSegment: 0,
       parameters: jsonString
-  };
+    };
 
-  /*if (state.hasOwnProperty("version")) {
-                console.log("state already has version");
-            }*/
+    /*if (state.hasOwnProperty("version")) {
+      console.log("state already has version");
+      }*/
 
-  var queryInterest = new Interest(queryPrefix);
-  queryInterest.setInterestLifetimeMilliseconds(10000);
+    var queryInterest = new Interest(queryPrefix);
+    queryInterest.setInterestLifetimeMilliseconds(10000);
 
-  var scope = this;
-
-  this.face.expressInterest(queryInterest,
-    function(interest, data){
-      scope.onQueryData(interest, data);
-    }, function(interest){
-      scope.onQueryTimeout(interest);
-    }
-  );
-
-  this.state["outstanding"][queryInterest.getName().toUri()] = 0;
-}
-
-/**
- * @deprecated
- * Use applyFilters/addFilters as appropriate.
- */
-Atmos.prototype.submitCatalogSearch = function(field) {
-  console.warn("Use of deprecated function submitCatalogSearch! (Use applyFilters/addFilters as appropriate)");
-}
-
-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"]);
-
-  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,
+
+    this.face.expressInterest(queryInterest,
         function(interest, data){
           scope.onQueryData(interest, data);
+        }, function(interest){
+          scope.onQueryTimeout(interest);
+        }
+        );
+
+    this.state["outstanding"][queryInterest.getName().toUri()] = 0;
+  }
+
+  /**
+   * @deprecated
+   * Use applyFilters/addFilters as appropriate.
+   */
+  Atmos.prototype.submitCatalogSearch = function(field) {
+    console.warn("Use of deprecated function submitCatalogSearch! (Use applyFilters/addFilters as appropriate)");
+  }
+
+  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.onQueryTimeout(interest);
+          scope.onQueryResultsTimeout(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];
-      }
+    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.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;
-  }
+  Atmos.prototype.populateResults = function() {
 
-  this.totalPages = Math.ceil(this.results.length / 20);
+    //TODO Check only for page changes and result length
 
-  //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(' ... ');
+    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 {
-        currentPage.append(' ' + i);
-      }
-    } else { // Need to skip ahead
-      if (i == this.page + 6) {
-        currentPage.append(' ... ');
+      } 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(' <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;
+    currentPage.append('  ' + this.results.length + ' results');
   }
 
-  nextPage--; // Need to adjust for starting at 0
+  Atmos.prototype.getPage = function(clickedPage) {
+    console.log(clickedPage);
 
-  if (nextPage < 0 ) {
-    nextPage = 0;
-    console.log("0 enabled");
-  } else if (nextPage > this.totalPages - 1) {
-    nextPage = this.totalPages - 1;
-    console.log("total enabled");
-  }
+    var nextPage = clickedPage;
+    if (clickedPage === "<") {
+      nextPage = this.page - 5;
+    } else if (clickedPage === ">") {
+      console.log("> enabled");
 
-  this.populateResults(nextPage * 20);
-  return false;
-}
-
-
-Atmos.prototype.submitAutoComplete = function() {
-  /* FIXME TODO
-  if (autocompleteText.value.length > 0) {
-    var selection = autocompleteText.value;
-    $.each(dropdown, function (i, dropdownEntry) {
-      if (dropdownEntry.substr(0, dropdownEntry.length - 1) == selection) {
-        selection = dropdownEntry;
-      }
-    });
-
-    selectedSearch["?"] = selection;
-    query(catalog, selectedSearch, onData, 1);
-    delete selectedSearch["?"];
-  }
-  */
-}
-
-Atmos.prototype.populateAutocomplete = function(fields) {
-  /* FIXME TODO
-  var isAutocompleteFullName = (autocompleteText.value.charAt(autocompleteText.value.length - 1) === "/");
-  var autocompleteFullName = autocompleteText.value;
-  for (var i = 0; i < fields.length; ++i) {
-    var fieldFullName = fields[i];
-    var entry = autocompleteFullName;
-    var skipahead = "";
-
-    if (isAutocompleteFullName) {
-      skipahead = fieldFullName.substr(autocompleteText.value.length, fieldFullName.length);
-    } else {
-      if (fieldFullName.charAt(autocompleteText.value.length) === "/") {
-        entry += "/";
-        skipahead = fieldFullName.substr(autocompleteText.value.length + 1, fieldFullName.length);
-      } else {
-        skipahead = fieldFullName.substr(autocompleteText.value.length, fieldFullName.length);
-      }
-    }
-    if (skipahead.indexOf("/") != -1) {
-      entry += skipahead.substr(0, skipahead.indexOf("/") + 1);
-    } else {
-      entry += skipahead;
+      nextPage = this.page + 5;
     }
 
-    var added = false;
-    for (var j = 0; j < dropdown.length && !added; ++j) {
-      if (dropdown[j] === entry) {
-        added = true;
-      } else if (dropdown[j] > entry) {
-        dropdown.splice(j, 0, entry);
-        added = true;
-      }
-    }
-    if (!added) {
-      dropdown.push(entry);
+    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;
   }
-  $("#autocompleteText").autocomplete({
-    source: dropdown
-  });
-  */
-}
 
-/**
- * Adds a filter to the list of filters.
- * If a filter is already added, it will be removed instead.
- * 
- * @param {string} filter
- */
-Atmos.prototype.addFilter = function(name, category){
-  var existing = this.filters.find('span:contains(' + name + ')');
-  if (existing.length){
-    //If the category is clicked twice, then we delete it.
-    existing.remove();
-    this.applyFilters();
 
-  } else {
+  Atmos.prototype.submitAutoComplete = function() {
+    console.log("Submitted autoComplete");
+    /* FIXME TODO
+       if (autocompleteText.value.length > 0) {
+       var selection = autocompleteText.value;
+       $.each(dropdown, function (i, dropdownEntry) {
+       if (dropdownEntry.substr(0, dropdownEntry.length - 1) == selection) {
+       selection = dropdownEntry;
+       }
+       });
 
-    var filter = $('<span class="label label-default"></span>');
-    filter.text(category + ':' + name);
+       selectedSearch["?"] = selection;
+       query(catalog, selectedSearch, onData, 1);
+       delete selectedSearch["?"];
+       }
+       */
+  }
 
-    this.filters.append(filter);
+  Atmos.prototype.populateAutocomplete = function(fields) {
+    console.log(fields);
+    /* FIXME TODO
+       var isAutocompleteFullName = (autocompleteText.value.charAt(autocompleteText.value.length - 1) === "/");
+       var autocompleteFullName = autocompleteText.value;
+       for (var i = 0; i < fields.length; ++i) {
+       var fieldFullName = fields[i];
+       var entry = autocompleteFullName;
+       var skipahead = "";
 
-    this.applyFilters();
+       if (isAutocompleteFullName) {
+       skipahead = fieldFullName.substr(autocompleteText.value.length, fieldFullName.length);
+       } else {
+       if (fieldFullName.charAt(autocompleteText.value.length) === "/") {
+       entry += "/";
+       skipahead = fieldFullName.substr(autocompleteText.value.length + 1, fieldFullName.length);
+       } else {
+       skipahead = fieldFullName.substr(autocompleteText.value.length, fieldFullName.length);
+       }
+       }
+       if (skipahead.indexOf("/") != -1) {
+       entry += skipahead.substr(0, skipahead.indexOf("/") + 1);
+       } else {
+       entry += skipahead;
+       }
 
-    var scope = this;
-    filter.click(function(){
-      $(this).remove();
-      scope.applyFilters();
-    });
+       var added = false;
+       for (var j = 0; j < dropdown.length && !added; ++j) {
+       if (dropdown[j] === entry) {
+       added = true;
+       } else if (dropdown[j] > entry) {
+       dropdown.splice(j, 0, entry);
+       added = true;
+       }
+       }
+       if (!added) {
+       dropdown.push(entry);
+       }
+
+       }
+       $("#autocompleteText").autocomplete({
+       source: dropdown
+       });
+       */
+  }
+
+  /**
+   * Adds a filter to the list of filters.
+   * If a filter is already added, it will be removed instead.
+   * 
+   * @param {string} filter
+   */
+  Atmos.prototype.addFilter = function(name, category){
+    var existing = this.filters.find('span:contains(' + name + ')');
+        if (existing.length){
+          //If the category is clicked twice, then we delete it.
+          existing.remove();
+          this.applyFilters();
+
+        } else {
+
+          var filter = $('<span class="label label-default"></span>');
+          filter.text(category + ':' + name);
+
+          this.filters.append(filter);
+
+          this.applyFilters();
+
+          var scope = this;
+          filter.click(function(){
+            $(this).remove();
+            scope.applyFilters();
+          });
+
+        }
 
   }
-  
-}
 
-Atmos.prototype.applyFilters = function(){
-  var filters = this.filters.children().toArray().reduce(function(prev, current){
+  Atmos.prototype.applyFilters = function(){
+    var filters = this.filters.children().toArray().reduce(function(prev, current){
       var data = $(current).text().split(/:/);
       prev[data[0]] = data[1];
       return prev;
-  }, {});
-  console.log('Collected filters:', filters);
-  var scope = this;
-  this.query(this.catalog, filters, function(data){
-    scope.onData(data);
-  }, 1);
-}
+    }, {});
+    console.log('Collected filters:', filters);
+    var scope = this;
+    this.query(this.catalog, filters, function(data){
+      scope.onData(data);
+    }, 1);
+  }
 
-/**
- * @deprecated
- * Use addFilter instead.
- */
-Atmos.prototype.populateCurrentSelections = function() {
-  console.warn("Use of deprecated function populateCurrentSelections! (Use addFilter instead)");
-  /*
-  var currentSelection = $(".currentSelections");
-  currentSelection.empty();
+  /**
+   * @deprecated
+   * Use addFilter instead.
+   */
+  Atmos.prototype.populateCurrentSelections = function() {
+    console.warn("Use of deprecated function populateCurrentSelections! (Use addFilter instead)");
+    /*
+       var currentSelection = $(".currentSelections");
+       currentSelection.empty();
 
-  currentSelection.append("<p>Filtering on:");
+       currentSelection.append("<p>Filtering on:");
 
-  var scope = this;
+       var scope = this;
 
-  $.each(this.selectedSearch, function (searchMenuCatagory, selection) {
-    var e = $('<a href="#">[X] ' + searchMenuCatagory + ":" + selection + '</a>');
-    e.onclick(function(){
-      var searchFilter = $(this).text();
+       $.each(this.selectedSearch, function (searchMenuCatagory, selection) {
+       var e = $('<a href="#">[X] ' + searchMenuCatagory + ":" + selection + '</a>');
+       e.onclick(function(){
+       var searchFilter = $(this).text();
 
-      var search = "";
-      for (var j = 0; j < searchFilter.length; ++j) {
-        search += searchFilter[j] + " ";
-      }
-      console.log("Split values: '" + search + "'");
+       var search = "";
+       for (var j = 0; j < searchFilter.length; ++j) {
+       search += searchFilter[j] + " ";
+       }
+       console.log("Split values: '" + search + "'");
 
-      delete this.selectedSearch[searchFilter[0]];
-      this.query(catalog, selectedSearch, onData, 1);
-      populateCurrentSelections();
-    });
-    currentSelection.append(e);
-  });
+       delete this.selectedSearch[searchFilter[0]];
+       this.query(catalog, selectedSearch, onData, 1);
+       populateCurrentSelections();
+       });
+       currentSelection.append(e);
+       });
 
-  currentSelection.append("</p>");
-  */
-}
+       currentSelection.append("</p>");
+       */
+  }
+
+  return Atmos;
+
+})();
+
+
diff --git a/client/query/query2.css b/client/query/query2.css
index 644a07a..f36df8b 100644
--- a/client/query/query2.css
+++ b/client/query/query2.css
@@ -26,6 +26,11 @@
   border-left: gray 3px solid;
 }
 
+.autoComplete .list-group {
+  margin-bottom: 0;
+  border: none;
+}
+
 #filters {
   margin-bottom: 10px;
   visibility: visible;