TRACE-120. UI: Track search history via URL hash (May Ang via iwasakims)
diff --git a/htrace-core/src/web/app/setup.js b/htrace-core/src/web/app/setup.js
index adb100c..beb06db 100644
--- a/htrace-core/src/web/app/setup.js
+++ b/htrace-core/src/web/app/setup.js
@@ -28,7 +28,7 @@
 var Router = Backbone.Marionette.AppRouter.extend({
   "routes": {
     "": "init",
-    "!/search(/:query)": "search",
+    "!/search(?:query)": "search",
     "!/spans/:id": "span",
     "!/swimlane/:id": "swimlane",
     "!/swimlane/:id:?:lim": "swimlane"
@@ -37,7 +37,6 @@
   "initialize": function() {
     // Collection
     this.spansCollection = new app.Spans();
-    this.spansCollection.fetch();
   },
 
   "init": function() {
@@ -46,9 +45,37 @@
 
   "search": function(query) {
     app.root.app.show(new app.SearchView());
+
+    var predicates;
+
+    this.spansCollection.switchMode("infinite", {
+      fetch: false,
+      resetState: true
+    });
+
+    if (query) {
+      predicates = _(query.split(";"))
+      .map(function(predicate) {
+        return _(predicate.split('&'))
+          .reduce(function(mem, op) {
+            var op = op.split('=');
+            mem[op[0]] = op[1];
+            return mem;
+          }, {});
+      });
+      this.spansCollection.fullCollection.reset();
+      this.spansCollection.setPredicates(predicates);
+    }
+    else {
+      this.spansCollection.fullCollection.reset();
+      this.spansCollection.setPredicates([{"op":"cn","field":"description","val":""}]);
+    }
+    this.spansCollection.fetch();
+
     app.root.app.currentView.controls.show(
       new app.SearchControlsView({
-        "collection": this.spansCollection
+        "collection": this.spansCollection,
+        "predicates": predicates
       }));
     app.root.app.currentView.main.show(
       new Backgrid.Grid({
diff --git a/htrace-core/src/web/app/views/search/field.js b/htrace-core/src/web/app/views/search/field.js
index 34b0110..c9f048a 100644
--- a/htrace-core/src/web/app/views/search/field.js
+++ b/htrace-core/src/web/app/views/search/field.js
@@ -35,6 +35,7 @@
   'render': function() {
     this.$el.html(this.template({ field: this.field }));
     this.showSearchField();
+    if (this.options.value) this.setValue();
     return this;
   },
 
@@ -106,5 +107,18 @@
       default:
         return '';
     }
+  },
+
+  'setValue': function() {
+    switch (this.field) {
+      case 'begin':
+      case 'end':
+        this.$('select.op').val(this.options.op);
+        this.$('input.start-end-date-time').val(moment.unix(this.options.value).format('YYYY-MM-DD HH:mm'));
+      case 'duration':
+        this.$("input.duration").val(this.options.value);
+      case 'description':
+        this.$('input.description').val(this.options.value);
+    }
   }
 });
diff --git a/htrace-core/src/web/app/views/search/search.js b/htrace-core/src/web/app/views/search/search.js
index 3b74b01..b9acee5 100644
--- a/htrace-core/src/web/app/views/search/search.js
+++ b/htrace-core/src/web/app/views/search/search.js
@@ -33,7 +33,8 @@
     "click button.search": "search",
   },
 
-  "initialize": function() {
+  "initialize": function(options) {
+    this.options = options;
     this.predicates = [];
     this.searchFields = [];
     this.searchFields.push(new app.SearchFieldView({
@@ -47,17 +48,29 @@
   "render": function() {
     this.$el.html(this.template());
     this.$el.find('.search-fields').append(this.searchFields[0].render().$el);
+
+    _(this.options.predicates).each(function(pred) {
+      if (pred.field === 'description') {
+        this.$el.find('input.description').val(pred.val);
+      } else {
+        this.addSearchField(pred);
+      }
+    }.bind(this));
+
     return this;
   },
 
   "addSearchField": function(e) {
-    var target = $(e.target);
-    $('button.field').text(target.text());
-    var newSearchField = new app.SearchFieldView({
+    var target = e.target ? $(e.target) : e;
+    if (e.target) $('button.field').text(target.text());
+    var searchOptions = {
       predicates: this.predicates,
       manager: this,
-      field: target.data('field')
-    });
+      field: target.data ? target.data('field') : target.field,
+    };
+    if (!e.target) _.extend(searchOptions, { value: target.val, op: target.op})
+
+    var newSearchField = new app.SearchFieldView(searchOptions);
     this.$el.find('.search-fields').append(newSearchField.render().$el);
     this.searchFields.push(newSearchField);
   },
@@ -74,6 +87,11 @@
       return predicate.val;
     });
 
+    this.searchParams = _(this.predicates).map(function(predicate) {
+      return $.param(predicate);
+    }).join(';');
+    Backbone.history.navigate('!/search?' + this.searchParams, { trigger: false });
+
     this.collection.switchMode("infinite", {
       fetch: false,
       resetState: true