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/README.md b/client/README.md
index 2e20aad..88e5cd4 100644
--- a/client/README.md
+++ b/client/README.md
@@ -6,23 +6,51 @@
 Setup
 -----
 
-To simply run the client code, you will need the following things setup:
+###To simply run the client code:
+
+You will need the following things setup:
 
 * A NDN backend running somewhere (The default config is pointed at a test backend)
 * NDN-JS
   + Run `git submodule init ndn-js` in the client directory.
   + Then run `git submodule update`
-* Configure the config.json
+* Configure the config.json in catalog-dev
   + If it doesn't exist, you will need to copy it from the config-example.json
-  + config.json is intentionally left out of the git to prevent overwriting it.
+  + config.json is intentionally left out of the git to prevent overwriting your changes.
+
+_Note: The dev code runs from catalog-dev and is not gauranteed to run across all browsers. Only the deployment code is prepared in such a way that we can promise the code will work. (Internet explorer may work but is not officially supported by either option)_
+
+###To run the deployment code:
+
+You will additionally require npm and node installed (npm is packaged with the default node installation).
+
+Run the following:
+```
+npm install -g gulp
+npm install
+gulp
+```
+
+The site will now be available in the catalog directory.
+
+If you run into issues of requiring sudo access you may need to [configure the npm prefix](http://competa.com/blog/2014/12/how-to-run-npm-without-sudo/) to point your global package repo somewhere else. Should you find yourself in a situation that you don't mind using sudo, feel free to simply just run with sudo.
+
+##Serving the site:
+
+To serve the site, point a webserver at the same directory this README file is in. Then give users the url: http://<your domain>/catalog or /catalog-dev depending on if you are running deployment code. 
+
+HTTPS is not supported and will break the code as it is unless the ndn backend is running a valid certificate as well, this is due to a security rule in most browsers that restricts the ws protocol from running in https tabs/frames. All content in the https frame MUST be secure. (Aka run wss in https) (HTTPS is not officially supported)
 
 config.json
 -----------
 
-catalogPrefix - This is where you are doing your catalog queries. This should be the root of where we are querying in ndn.
+###Global
+* CatalogPrefix - Where should the catalog attach in the URI scheme? (Usually the root of a catalog)
+* FaceConfig - A valid NDN node location running the websocket for NDN-JS.
 
-faceConfig - Configure where you have your backend running and on which port it is listening. To setup a new backend, please read the readme at the root of the repo.
-
+###Retrieval
+* DemoKey - The public and private portion of an RSA in Base64. This key must be valid in the NDN Network for it to work.
+* Destinations - A list of retrieval URIs. These must be running the retrieval code or retrieval will fail.
 
 Changing the theme
 ------------------
diff --git a/client/catalog-dev/background.jpg b/client/catalog-dev/background.jpg
deleted file mode 100644
index 7196c30..0000000
--- a/client/catalog-dev/background.jpg
+++ /dev/null
Binary files differ
diff --git a/client/catalog-dev/config-example.json b/client/catalog-dev/config-example.json
index 5a0455b..9c37d97 100644
--- a/client/catalog-dev/config-example.json
+++ b/client/catalog-dev/config-example.json
@@ -2,7 +2,7 @@
 	"global": {
 		"catalogPrefix": "/catalog",
 		"faceConfig": {
-			"host": "atmos-csu.research-lan.colostate.edu",
+			"host": "atmos-nwsc.ucar.edu",
 			"port": 9696
 		}
 	},
@@ -12,7 +12,7 @@
 			"priv": "MIIEpQIBAAKCAQEAuAmnWYKE7E8G+hyy4TiTU7t91KyIGvglEeT6HWEkW4LKzXLO22a1jVS9+yP96I6vp7N5vpS1t7oXtgWuzkO+O85u6gfbvwp+67zJe2I89eHO4dmNnP4fx/j7WcCUCyzZfbyW67h5IoouoBIdQge2Xdvh9rFdex9UUhyjEZv5676zlcqlhz8xGBrJmQHsqpD9ijY1XhKBvoSIoQ0ZKkpmwVk8QYM9PbjUqzSQBj4aYXS+BPV6aRudVvyDt2DBXp2FNP0CGrosCXKnSl4Yv8BYp0k0RmFZDuJuntLb/XIvPEfMX5li7g3zHzAlIJIVSwT+FRkd3H5cECFSIZFUYIuSQQIDAQABAoIBAQCKBftzfxavn6lM5T8m+GZN0vzRBsBg8Z/jpsYKSLOayiHNKYCIPaSFpXuCIYEo6/JDJLB2xVLvwupLgkGSwm2mrvCyJkihI38Cz6iQF6I+iia9bYrupgwxzsK7klm1c+J9kXXivYxj4hyLwmoc/mnARMtYV7cTQvDbUEzgRQmPykWKBv6Y0SL1WprfiRfKIMwSqQk91ffj6whKxBLAuUdseVBmo/ivLPq0a+wDrcvaJAxSB4eIwCHzAugkRA/NoK0vG3mra0lK5jvQrcNIuffxNAnresDVDTnYRc42etjePLAhlpeK/4sjYE/wPdeP8yzLHUg/hsSpAPIjLXJNZqUBAoGBANxPmUQNf1lGHo/nLY3dVMD3+kYNnTUD8XwS81qdg8/dNyF8t+7DOdJ1j7Itb+zGA1XXAGfTm6JoUG+eKKR2OSuyZcxygpOgzxAFanXKhTWZsKbG70xNmX0sOAEhtTGsgFTEGEv977MwIlFa6n2bsp3Luj/AGmvNsOYvBDPXOklxAoGBANXZyXAaE7M5JALusLuEFxLGvWVz6TRdQ//c+FWvKrnh+nFlTlAPpDvlaPJJca8ViNevxJ2UhGtbENXAqgwTYpnAi/yQD4dATViIveK6Pn4t12mpPAlkMbbMTR8jtp5l1oHchcwe8QuEOKuTX5+STpNGlWs+tsMb12mhCpc3eO3RAoGAMxjDE2WOA8afkACuMBkFbzwUb+r4azNe7sf2aS3fRHaqMroabuYYoxdhHJItQ10pqN8U2P/bOO+4uCqWgo5o9BmMQr7MSjEh1TVsW6V8/9GFhyjcl3XoA4Ad/SU0QTEhEofomrdqwMSJMRVFDZzu8Gov6FlFx3sNbFW7Q8rHWgECgYEAq/TVz3iIgsLdvCXmosHSM9zvCpcr3FlqhmFOpseVmaamVWxajnIlY6xSuRBpg5nTUWwas4Nq/1BYtyiXE+K6lFuJtOq6Mc145EoANkIAYkHGR0Y36m1QtGaPVQzImZHV7NJAHCR9Ov90+jIk4BErca1+FKB3IWhPzLYb6ABJEyECgYEAthhzWSxPkqyiLl+2vnhdR3EEkvDX6MV6hGu4tDAf2A1Y0GSApyEaSAA31hlxu5EgneLD7Ns2HMpIfQMydB5lcwKQc9g/tVI1eRzuk6Myi+2JmPEM2BLyiX8yI+xnZlKDiZleQitCS4RQGz5HbXT70aYQIGxuvkQ/uf68jdrL6o8="
 		},
 		"destinations": [
-			"/retrieve/demo"
+			"/retrieve/ucar"
 		]
 	}
 }
diff --git a/client/catalog-dev/css/style.css b/client/catalog-dev/css/style.css
index 94ea89f..17ba979 100644
--- a/client/catalog-dev/css/style.css
+++ b/client/catalog-dev/css/style.css
@@ -142,6 +142,10 @@
   cursor: pointer;
 }
 
+.treeExplorer .treeExplorerNode .nodeContent {
+  display: inline-block;
+}
+
 .treeExplorer .treeExplorerNode > .nodeChildren {
   display: none;
 }
@@ -168,6 +172,14 @@
   content: "\e022"
 }
 
+.treeExplorer .treeSearch{
+  display: none;
+}
+
+.treeExplorer .nodeContent:hover > .treeSearch {
+  display: inline-block;
+}
+
 #popup {
   top: 0;
   left: 0;
diff --git a/client/catalog-dev/index.html b/client/catalog-dev/index.html
index 590fcfb..6f43aa0 100644
--- a/client/catalog-dev/index.html
+++ b/client/catalog-dev/index.html
@@ -22,6 +22,7 @@
 
 <!-- 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-scrollTo/2.1.0/jquery.scrollTo.min.js"></script>
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
 <script src="../ndn-js/build/ndn.min.js"></script>
 <script src="js/autocomplete.js"></script>
@@ -168,7 +169,7 @@
           <p>Select a destination and press submit if you are sure you want to download the selected data to the selected destination.</p>
           <div class="dropdown">
             <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
-              Destination
+              <span id="requestDropText">Destination<span>
               <span class="caret"></span>
             </button>
             <ul id="requestDest" class="dropdown-menu"></ul>
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.
diff --git a/client/catalog-dev/js/treeExplorer.js b/client/catalog-dev/js/treeExplorer.js
index 5037629..6b67804 100644
--- a/client/catalog-dev/js/treeExplorer.js
+++ b/client/catalog-dev/js/treeExplorer.js
@@ -27,7 +27,8 @@
           var el = $('<div class="treeExplorerNode"></div>');
           if (current.match(/\/$/)){
             el.attr('id', path + current);
-            el.append(['<a href="#' , path , current , '">' , current , '</a>'].join(""));
+            el.append(['<div class="nodeContent"><a href="#', path, current, '">', current,
+              '</a>&nbsp;<button class="treeSearch btn btn-success btn-xs">Search from here</button></div>'].join(""));
           } else {
             el.addClass('file');
             el.text(current);
@@ -36,8 +37,8 @@
         });
       }
 
-      tree.on('click', '.treeExplorerNode > a', function(){
-        var node = $(this).parent();
+      tree.on('click', '.treeExplorerNode > .nodeContent > a', function(){
+        var node = $(this).parent().parent();
 
         if (node.hasClass('open')){ //Are we open already?
           node.removeClass('open');
diff --git a/client/gulpfile.js b/client/gulpfile.js
index cc79d11..585a791 100644
--- a/client/gulpfile.js
+++ b/client/gulpfile.js
@@ -1,3 +1,4 @@
+//Includes
 var gulp = require('gulp');
 var minifyHTML = require('gulp-minify-html');
 var minifyCSS = require('gulp-minify-css');
@@ -5,9 +6,14 @@
 var closure = require('gulp-closure-compiler-service');
 var clean = require('gulp-clean');
 
+//Globs
+var cssGlob  = ['./catalog-dev/css/style.css', './catalog-dev/css/cubeLoader.css'];
+var jsGlob   = './catalog-dev/js/*.js';
+var htmlGlob = './catalog-dev/index.html';
+
 gulp.task('minify-html', function() {
 
-  return gulp.src(['./catalog-dev/index.html'])
+  return gulp.src(htmlGlob)
     .pipe(minifyHTML())
     .pipe(gulp.dest('./catalog/'));
 
@@ -15,21 +21,21 @@
 
 gulp.task('minify-js', function() {
 
-  return gulp.src('./catalog-dev/js/*.js')
+  return gulp.src(jsGlob, {base: 'catalog-dev'})
     .pipe(sourcemaps.init())
     .pipe(closure())
-    .pipe(sourcemaps.write('../../catalog-dev/js'))
-    .pipe(gulp.dest('./catalog/js'));
+    .pipe(sourcemaps.write({sourceRoot: '/catalog-dev', includeContent: false}))
+    .pipe(gulp.dest('./catalog'));
 
 });
 
 gulp.task('minify-css', function() {
 
-  return gulp.src(['./catalog-dev/css/style.css', './catalog-dev/css/cubeLoader.css'])
+  return gulp.src(cssGlob, {base: 'catalog-dev'})
     .pipe(sourcemaps.init())
     .pipe(minifyCSS())
-    .pipe(sourcemaps.write('../../catalog-dev/css'))
-    .pipe(gulp.dest('./catalog/css'));
+    .pipe(sourcemaps.write({sourceRoot: '/catalog-dev', includeContent: false}))
+    .pipe(gulp.dest('./catalog'));
 
 });
 
@@ -50,5 +56,14 @@
 
 });
 
+gulp.task('watch', ['default'], function(){
+
+  gulp.watch(cssGlob, ['minify-css']);
+  gulp.watch(jsGlob, ['minify-js']);
+  gulp.watch(htmlGlob, ['minify-html']);
+  gulp.watch(['./catalog-dev/config.json', './catalog-dev/css/*.min.css'], ['copy']);
+
+});
+
 gulp.task('default', ['minify-html', 'minify-css', 'minify-js', 'copy']);