Added TreeSearch and lots of cleaning up/improvements.

* Added watch for gulp.
* Updated README
* Various bug fixes and improvements.

Cleaning up and debugging. Gulp can now watch.

Change-Id: Ice679a326ff693fa030caf429c3c29ba94ae00cd
diff --git a/client/catalog-dev/js/catalog.js b/client/catalog-dev/js/catalog.js
index d013fda..446a7c5 100644
--- a/client/catalog-dev/js/catalog.js
+++ b/client/catalog-dev/js/catalog.js
@@ -158,14 +158,40 @@
     $('#treeSearch div').treeExplorer(function(path, callback){
       console.log("Tree Explorer request", path);
       ga('send', 'event', 'tree', 'request');
-      scope.autoComplete(path, function(list){
+      scope.autoComplete(path, function(data){
+        var list = data.next;
+        var last = (data.lastComponent === true);
         console.log("Autocomplete response", list);
         callback(list.map(function(element){
-          return (path == "/"?"/":"") + element + "/";
+          return (path == "/"?"/":"") + element + (!last?"/":"");
         }));
       })
     });
 
+    $('#treeSearch').on('click', '.treeSearch', function(){
+      var t = $(this);
+
+      scope.clearResults();
+
+      var path = t.parent().parent().attr('id');
+
+      console.log("Stringing tree search:", path);
+
+      scope.query(scope.catalog, {'??': path},
+      function(interest, data){ //Success
+        console.log("Tree search response", interest, data);
+
+        scope.name = data.getContent().toString().replace(/[\n\0]+/g,'');
+
+        scope.getResults(0);
+      },
+      function(interest){ //Failure
+        console.warn("Request failed! Timeout", interest);
+        scope.createAlert("Request timed out.\""+ interest.getName().toUri() + "\" See console for details.");
+      });
+
+    });
+
     this.setupRequestForm();
 
   }
@@ -245,7 +271,7 @@
       function(interest, data){
 
         if (data.getContent().length !== 0){
-          callback(JSON.parse(data.getContent().toString().replace(/[\n\0]/g, "")).next);
+          callback(JSON.parse(data.getContent().toString().replace(/[\n\0]/g, "")));
         } else {
           callback([]);
         }
@@ -308,6 +334,8 @@
       $('#results').removeClass('hidden').slideDown();
     }
 
+    $.scrollTo("#results", 700);
+
     if ((this.results.length === this.resultCount) || (this.resultsPerPage * (index + 1) < this.results.length)){
       //console.log("We already have index", index);
       this.page = index;
@@ -452,7 +480,8 @@
 
         $('#request').modal('hide')//Initial params are ok. We can close the form.
         .remove('.alert') //Remove any alerts
-        .find('.active').removeClass('active'); //Disable the active destination
+
+        scope.cleanRequestForm();
 
         $(this).off(e); //Don't fire this again, the request must be regenerated
 
@@ -500,12 +529,13 @@
           scope.face.expressInterest(interest,
             function(interest, data){ //Success
               console.log("Request for", prefix.toUri(), "succeeded.", interest, data);
+              scope.createAlert("Data retrieval has initiated.", "alert-success");
             }, function(interest){ //Failure
-              console.error("Failure to request", prefix.toUri(), interest);
-              scope.createAlert("Failed to request " + prefix.toUri() + ". This means that the retrieve failed! See console for more details.");
+              console.error("Request for", prefix.toUri(), "timed out.", interest);
+              scope.createAlert("Request for " + prefix.toUri() + " timed out. This means that the retrieve failed! See console for more details.");
             }
           );
-        }, 10000); //Wait 10 seconds
+        }, 5000); //Wait 5 seconds
 
         scope.face.registerPrefix(retrievePrefix,
           function(prefix, interest, face, interestFilterId, filter){ //On Interest
@@ -519,7 +549,6 @@
             try {
               face.putData(data);
               console.log("Responded for", interest.getName().toUri(), data);
-              scope.createAlert("Data retrieval has initiated.", "alert-success");
             } catch (e) {
               console.error("Failed to respond to", interest.getName().toUri(), data);
               scope.createAlert("Data retrieval failed.");
@@ -644,10 +673,20 @@
 
   }
 
+  Atmos.prototype.cleanRequestForm = function(){
+    $('#requestDest').prev().removeClass('btn-success').addClass('btn-default');
+    $('#requestDropText').text('Destination');
+    $('#requestDest .active').removeClass('active');
+  }
+
   Atmos.prototype.setupRequestForm = function(){
+
+    var scope = this;
+
     this.requestForm.find('#requestCancel').click(function(){
       $('#request').unbind('submit') //Removes all event handlers.
       .modal('hide'); //Hides the form.
+      scope.cleanRequestForm();
     });
 
     var dests = $(this.config['retrieval']['destinations'].reduce(function(prev, current){
@@ -660,7 +699,10 @@
     this.requestForm.find('#requestDest').append(dests)
     .on('click', 'a', function(e){
       $('#requestDest .active').removeClass('active');
-      $(this).parent().addClass('active');
+      var t = $(this);
+      t.parent().addClass('active');
+      $('#requestDropText').text(t.text());
+      $('#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.