Added new loading modal window. Phase 1.

Phase 2 will include metadata, currently it needs to be rewritten to allow it.
Also removed some redundant loading animations.

Change-Id: I1dce75460a0d61d14ec1de43b250b1cb27801c65
diff --git a/client/catalog/css/cubeLoader.css b/client/catalog/css/cubeLoader.css
deleted file mode 100644
index 008b846..0000000
--- a/client/catalog/css/cubeLoader.css
+++ /dev/null
@@ -1,61 +0,0 @@
-.sk-cube-grid {
-  width: 40px;
-  height: 40px;
-  margin: 100px auto;
-}
-
-.sk-cube-grid .sk-cube {
-  width: 33%;
-  height: 33%;
-  background-color: #333;
-  float: left;
-  -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
-          animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 
-}
-.sk-cube-grid .sk-cube1 {
-  -webkit-animation-delay: 0.2s;
-          animation-delay: 0.2s; }
-.sk-cube-grid .sk-cube2 {
-  -webkit-animation-delay: 0.3s;
-          animation-delay: 0.3s; }
-.sk-cube-grid .sk-cube3 {
-  -webkit-animation-delay: 0.4s;
-          animation-delay: 0.4s; }
-.sk-cube-grid .sk-cube4 {
-  -webkit-animation-delay: 0.1s;
-          animation-delay: 0.1s; }
-.sk-cube-grid .sk-cube5 {
-  -webkit-animation-delay: 0.2s;
-          animation-delay: 0.2s; }
-.sk-cube-grid .sk-cube6 {
-  -webkit-animation-delay: 0.3s;
-          animation-delay: 0.3s; }
-.sk-cube-grid .sk-cube7 {
-  -webkit-animation-delay: 0s;
-          animation-delay: 0s; }
-.sk-cube-grid .sk-cube8 {
-  -webkit-animation-delay: 0.1s;
-          animation-delay: 0.1s; }
-.sk-cube-grid .sk-cube9 {
-  -webkit-animation-delay: 0.2s;
-          animation-delay: 0.2s; }
-
-@-webkit-keyframes sk-cubeGridScaleDelay {
-  0%, 70%, 100% {
-    -webkit-transform: scale3D(1, 1, 1);
-            transform: scale3D(1, 1, 1);
-  } 35% {
-    -webkit-transform: scale3D(0, 0, 1);
-            transform: scale3D(0, 0, 1); 
-  }
-}
-
-@keyframes sk-cubeGridScaleDelay {
-  0%, 70%, 100% {
-    -webkit-transform: scale3D(1, 1, 1);
-            transform: scale3D(1, 1, 1);
-  } 35% {
-    -webkit-transform: scale3D(0, 0, 1);
-            transform: scale3D(0, 0, 1);
-  } 
-}
diff --git a/client/catalog/css/style.css b/client/catalog/css/style.css
index 0146b6e..d8be5c1 100644
--- a/client/catalog/css/style.css
+++ b/client/catalog/css/style.css
@@ -8,8 +8,6 @@
   background-attachment: fixed;
 }
 
-body#body {}
-
 .sidebar {
   height: 100%;
   max-height: 100%;
@@ -170,7 +168,7 @@
   content: "[=]"
 }
 
-#popup {
+.popup {
   top: 0;
   left: 0;
   position: fixed;
@@ -183,23 +181,15 @@
   pointer-events: none;
 }
 
-#popup > * {
+.popup > * {
   display: none;
   pointer-events: auto;
 }
 
-.modal-open, #request {
+.modal-open, #request, #loading {
   padding-right: 0 !important;
 }
 
-.sk-cube-grid {
-  display: none;
-}
-
-table:empty ~ .sk-cube-grid {
-  display: block;
-}
-
 #requestForm {
   max-width: 500px;
   /* min-height: 300px; */
@@ -267,3 +257,11 @@
 #direct-download-list:empty:before {
   content: "No names in the selection are available for direct download...";
 }
+
+#loading.loading #cancel-text {
+  display: none;
+}
+
+#loading.cancelled #loading-text {
+  display: none;
+}
diff --git a/client/catalog/index.html b/client/catalog/index.html
index 78c0e73..774b28e 100644
--- a/client/catalog/index.html
+++ b/client/catalog/index.html
@@ -24,31 +24,22 @@
 
 <!-- Styles -->
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css">
-<link rel="stylesheet" href="css/cubeLoader.css">
 <link rel="stylesheet" href="css/theme.min.css">
 <link rel="stylesheet" href="css/style.css">
 
 <!-- Scripts -->
-<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js">
-</script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-scrollTo/2.1.2/jquery.scrollTo.min.js">
-</script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js">
-</script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/async/1.5.2/async.min.js">
-</script>
-<script src="../ndn-js/build/ndn.min.js">
-</script>
-<script src="js/autocomplete.js">
-</script>
-<script src="js/treeExplorer.js">
-</script>
-<script src="js/catalog.js">
-</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/async/2.0.1/async.min.js"></script>
+<script src="../ndn-js/build/ndn.min.js"></script>
+<script src="js/autocomplete.js"></script>
+<script src="js/treeExplorer.js"></script>
+<script src="js/loading-overlay.js"></script>
+<script src="js/catalog.js"></script>
 
 </head>
 
-<body id="body">
+<body>
 
   <div id="templates">
     <div class="row" id="timeTemplate">
@@ -120,7 +111,7 @@
         <div class="panel panel-info tab-pane fade in active" id="filterSearch">
           <div class="panel-heading">Filter Search</div>
           <div class="panel-body">
-            <div>
+            <div class="well">
               <div id="filters"></div>
               <button id="searchButton" class="btn btn-primary right-fix">Search</button>
             </div>
@@ -191,17 +182,6 @@
               <div class="navbar-text navbar-right">(Page <span class="pageNumber">0</span>) <span class="pageLength">0</span>/<span class="totalResults">0</span> Results</div>
             </nav>
             <table id="resultTable" class="table"></table>
-            <div class="sk-cube-grid">
-              <div class="sk-cube sk-cube1"></div>
-              <div class="sk-cube sk-cube2"></div>
-              <div class="sk-cube sk-cube3"></div>
-              <div class="sk-cube sk-cube4"></div>
-              <div class="sk-cube sk-cube5"></div>
-              <div class="sk-cube sk-cube6"></div>
-              <div class="sk-cube sk-cube7"></div>
-              <div class="sk-cube sk-cube8"></div>
-              <div class="sk-cube sk-cube9"></div>
-            </div>
             <nav class="navbar navbar-inverse col-md-12 resultMenu">
               <ul class="nav navbar-nav navbar-left">
                 <li><a href="#" class="requestSelectedButton">Request Selected</a></li>
@@ -235,7 +215,7 @@
 
   </div>
 
-  <div id="popup">
+  <div class="popup">
     <div id="request" class="panel panel-primary">
       <div class="panel-heading">Confirmation</div>
       <div class="panel-body">
@@ -283,6 +263,28 @@
     </div>
   </div>
 
+  <div class="popup">
+    <div id="loading" class="panel panel-primary loading">
+      <div class="panel-heading">Loading</div>
+      <div class="panel-body">
+        <span id="loading-text">Please wait while we finish loading your request.</span>
+        <span id="cancel-text">Your request has been cancelled.</span>
+        <div class="row">
+          <div class="col-sm-12">
+            <div class="progress">
+              <div class="progress-bar progress-bar-striped active" style="width:100%;">Loading...</div>
+            </div>
+          </div>
+        </div>
+        <div class="row">
+          <div class="col-sm-12">
+            <button id="loading-cancel" class="btn btn-danger pull-right">Cancel</button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
 </body>
 
 </html>
diff --git a/client/catalog/js/catalog.js b/client/catalog/js/catalog.js
index 5203fdc..2dfb368 100644
--- a/client/catalog/js/catalog.js
+++ b/client/catalog/js/catalog.js
@@ -377,9 +377,9 @@
     } else if (resultIndex === 1) {
       this.resultMenu.find('.previous').removeClass('disabled');
     }
-    $.scrollTo("#results", 500, {
-      interrupt: true
-    });
+    $('body').animate({
+      scrollTop: $("#results").offset().top
+    }, 500);
   }
 
   Atmos.prototype.getResults = function(index) {
@@ -455,19 +455,29 @@
     interest.setInterestLifetimeMilliseconds(500);
     interest.setMustBeFresh(true);
     const face = this.face;
-    async.retry(4, function(done) {
-      face.expressInterest(interest, function(interest, data) {
-        done();
-        success(interest, data);
-      }, function(interest) {
-        done("Interest timed out 4 times.", interest);
+    var cancelled = false;
+    var attempt = 0;
+    openLoadingOverlay(function(update){
+      async.retry(4, function(done) {
+        face.expressInterest(interest, function(interest, data) {
+          update(true, attempt, attempt);
+          done();
+          success(interest, data);
+        }, function(interest) {
+          update(false, attempt++, attempt);
+          done("Timeout", interest);
+        });
+      }, function(err, interest) {
+        update(true, attempt, attempt);
+        if (err) {
+          console.log(err, interest);
+          failure(interest);
+        }
       });
-    }, function(err, interest) {
-      if (err) {
-        console.log(err, interest);
-        failure(interest);
-      }
+    }, function(){
+      cancelled = true;
     });
+
   }
 
   /**
@@ -673,7 +683,8 @@
 
             var item = $('<li><a href="#">' + name + '</a></li>');
             sub.append(item);
-            item.click(function() {
+            item.find('a').click(function(e) {
+              e.preventDefault();
               //Click on the side menu filters
               if (item.hasClass('active')) {
                 //Does the filter already exist?
@@ -685,7 +696,7 @@
                 var filter = $('<span class="label label-default"></span>');
                 filter.text(category + ':' + name);
                 scope.filters.append(filter);
-                filter.click(function() {
+                filter.click(function(e) {
                   //Click on a filter
                   filter.remove();
                   item.removeClass('active');
@@ -694,7 +705,8 @@
             });
           });
           //Toggle the menus. (Only respond when the immediate tab is clicked.)
-          e.find('> a').click(function() {
+          e.find('> a').click(function(e) {
+            e.preventDefault();
             scope.categories.find('.subnav').slideUp();
             var t = $(this).siblings('.subnav');
             if (!t.is(':visible')) {
diff --git a/client/catalog/js/loading-overlay.js b/client/catalog/js/loading-overlay.js
new file mode 100644
index 0000000..849524d
--- /dev/null
+++ b/client/catalog/js/loading-overlay.js
@@ -0,0 +1,78 @@
+/**
+ * This function allows the app to pass in a callback function for updating the progress and a function to call if the progress it cancelled.
+ * @param func - A function accepting two functions as parameters, the first will be called with one parameter (which is a callback function
+ * for progress updates) when the overlay is ready.
+ * @param cancel - A function to call if the overlay is cancelled.
+ *
+ * Example:
+ * openLoadingOverlay(function(update){
+ *   //Begin progress, call update if progress is in chunks, otherwise ignore.
+ * }, function(){
+ *   //Cancelled
+ * });
+ */
+var openLoadingOverlay = (function(){
+  "use strict";
+
+  var isOpen = false;
+
+  return function(func, cancel){
+
+    var overlay = $('#loading');
+    var progress = overlay.find('.progress-bar');
+    var cancelButton = overlay.find('#loading-cancel');
+
+    if (isOpen){
+      console.warn("Two overlays are not permitted at the same time. The second will have dummy callbacks");
+      func(function(){});
+      return;
+    }
+
+    isOpen = true;
+
+    var update = function(done, current, total){
+      if (current && total){
+        progress.text(current + '/' + total)
+        .animate({
+            width: Math.round(current/total) + '%'
+          },
+          200,
+          'linear',
+          function(){
+            if (done){
+              reset();
+            }
+          }
+        );
+      } else {
+        if (done){
+          reset();
+        }
+      }
+    };
+
+    var reset = function(){
+      overlay.modal('hide');
+      progress.text('Loading...').css('width', '100%');
+      overlay.removeClass('cancelled').addClass('loading');
+      cancelButton.removeClass('disabled');
+    };
+
+    cancelButton.one('click', function(){
+
+      cancelButton.addClass('disabled');
+      overlay.addClass('cancelled').removeClass('loading');
+
+      setTimeout(reset, 2000);
+      progress.text('Cancelling...').css('width', 0).animate({
+        width: '100%'
+      }, 2000);
+
+    });
+
+    overlay.modal('show');
+
+    func(update);
+
+  };
+})();