Reworked filters to be far simpler that before.

Filter state is stored in the dom instead of global variables. No time
is lost because these dom objects were retrieved or removed regardless.

Change-Id: I5a525c5ea1f8740d25ba5cad8f81a8a148c5e651
diff --git a/client/query/query.html b/client/query/query.html
index 828b37e..1f2af4a 100644
--- a/client/query/query.html
+++ b/client/query/query.html
@@ -4,14 +4,25 @@
 <head>
 <title>Atmospheric Query and Retrieval Tool</title>
 
+<script>
+  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+  ga('create', 'UA-64984905-1', 'auto');
+  ga('send', 'pageview');
+
+</script>
+
 <!-- Styles -->
 <link rel="stylesheet"
-  href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.4/yeti/bootstrap.min.css">
+  href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.5/yeti/bootstrap.min.css">
 <link rel="stylesheet" href="query2.css">
 
 <!-- Scripts -->
-<script
-  src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
+<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.min.js"></script>
 <script src="query.js"></script>
 
@@ -43,7 +54,7 @@
 
         <div class="panel panel-default">
           <div class="panel-body">
-            <div class="currentSelections"></div>
+            <div id="filters"></div>
             <div class="autocomplete">
               <div class="ui-widget">
                 <form class="form-inline" id="searchBar">
diff --git a/client/query/query.js b/client/query/query.js
index 5241edf..3de2220 100644
--- a/client/query/query.js
+++ b/client/query/query.js
@@ -18,10 +18,19 @@
 });
 
 /*
-  Atmos
-  Version 2
+  
 */
 
+/**
+ * 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){
   "use strict";
   //Internal variables.
@@ -34,9 +43,12 @@
   this.state = {};
   this.currentViewIndex = 0;
 
+  this.catalog = catalog;
+
   this.face = new Face(config);
   this.categories = $('#side-menu');
   this.resultTable = $('#resultTable');
+  this.filters = $('#filters');
 
   var scope = this;
 
@@ -68,7 +80,7 @@
             var item = $('<li><a href="#">' + name + '</a></li>');
             sub.append(item);
             item.click(function(){
-              scope.submitCatalogSearch(name);
+              scope.addFilter(name);
             });
           });
 
@@ -89,6 +101,7 @@
 
   $('#searchBar').submit(function(e){
     e.preventDefault();
+    console.log("Search started!", $('#search'));
   })
 
 }
@@ -145,47 +158,28 @@
   var scope = this;
 
   this.face.expressInterest(queryInterest,
-    function(){ //FIXME
-      scope.onQueryData.apply(scope, arguments); //TODO
-    }, function(){
-      scope.onQueryTimeout.apply(scope, arguments);
+    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.log("Sumbit Catalog Search: " + field);
-  // @todo: this logic isn't quite right
-  var remove = false;
-  var scope = this;
-  $.each(scope.selectedSearch, function (search, f) {
-    if (field == f) {
-      delete scope.selectedSearch[field];
-      remove = true;
-    }
-  });
-  if (!remove) {
-    $.each(scope.searchMenuOptions, function (search, fields) {
-      $.each(fields, function (index, f) {
-        if (f == field) {
-          scope.selectedSearch[search] = field;
-        }
-      });
-    });
-  }
-  this.query(scope.catalog, scope.selectedSearch, function(){
-    scope.onData.apply(scope, arguments); //Unknown arguments. FIXME (Works but could be improved for readability)
-  }, 1);
-  scope.populateCurrentSelections();
-  return false;//?? Is this used?
+  console.warn("Use of deprecated function submitCatalogSearch! (Use applyFilters/addFilters as appropriate)");
 }
 
 Atmos.prototype.expressNextInterest = function() {
   // @todo pipelines
-  var nextName = new Name(state["results"]);
-  nextName.appendSegment(state["nextSegment"]);
+  var nextName = new Name(this.state["results"]);
+  nextName.appendSegment(this.state["nextSegment"]);
 
   var nextInterest = new Interest(nextName);
   nextInterest.setInterestLifetimeMilliseconds(10000);
@@ -193,11 +187,11 @@
   var scope = this;
 
   this.face.expressInterest(nextInterest,
-      function(){
-        scope.onQueryResultsData.apply(scope, arguments); //FIXME
+      function(interest, data){
+        scope.onQueryResultsData(interest, data);
       },
-      function(){
-        scope.onQueryResultsTimeout.apply(scope, arguments); //FIXME
+      function(interest){
+        scope.onQueryResultsTimeout(interest);
       });
 
   this.state["nextSegment"] ++;
@@ -423,7 +417,55 @@
   */
 }
 
-Atmos.prototype.populateCurrentSelections = function() { //TODO
+/**
+ * Adds a filter to the list of filters.
+ * There is no need to remove the filter, it is done via ui clicks.
+ * 
+ * @param {string} filter
+ */
+Atmos.prototype.addFilter = function(name){
+  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(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().map(function(obj, index){
+      return $(obj).text();
+  }, []);
+  console.log('Collected filters:', filters);
+  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();
 
@@ -450,4 +492,5 @@
   });
 
   currentSelection.append("</p>");
+  */
 }
diff --git a/client/query/query2.css b/client/query/query2.css
index a39b328..026c7b8 100644
--- a/client/query/query2.css
+++ b/client/query/query2.css
@@ -25,3 +25,47 @@
   margin-left: 15px;
   border-left: gray 3px solid;
 }
+
+#filters {
+  margin-bottom: 10px;
+  visibility: visible;
+  opacity: 1;
+  transition: visibility 1s ease-out, opacity 1s ease-out, margin-bottom 1s ease-out;
+}
+
+#filters .label {
+  /* display: none; */
+  font-size: inherit;
+  transition: background-color 1s, 
+  
+  border-color 2s;
+}
+
+#filters .label:hover {
+  /* font-size: 0; */
+  background-color: red;
+  border-color: red;
+}
+
+#filters .label:hover::before {
+  content: "Remove ";
+}
+
+#filters .label:hover::after {
+  content: "?";
+}
+
+#filters .label:not(:last-child) {
+  margin-right: 5px;
+}
+
+#filters:empty {
+  visibility: hidden;
+  opacity: 0;
+  margin-bottom: 0;
+}
+
+/* Magic filters text that won't get picked up in jquery.text */
+#filters:not(:empty)::before {
+  content: "Filters: ";
+}