Arvados-DCO-1.1-Signed-off-by: Radhika Chippada <radhika@curoverse.com>
[arvados.git] / apps / workbench / app / assets / javascripts / tab_panes.js
index 6c58a57249d25202ec725a166b6a65298b023e20..b19a277ef76478f75ca73538a793eae6add0f856 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 // Load tab panes on demand. See app/views/application/_content.html.erb
 
 // Fire when a tab is selected/clicked.
@@ -10,46 +14,50 @@ $(document).on('shown.bs.tab', '[data-toggle="tab"]', function(event) {
 
 // Ask a refreshable pane to reload via ajax.
 //
-// Target of this event is the anchor element that manages the pane.  A reload
-// consists of an AJAX call to load the "data-pane-content-url" and replace the
-// contents of the DOM node pointed to by "href".
+// Target of this event is the DOM element to be updated. A reload
+// consists of an AJAX call to load the "data-pane-content-url" and
+// replace the content of the target element with the retrieved HTML.
 //
-// There are four CSS classes set on the object to indicate its state:
+// There are four CSS classes set on the element to indicate its state:
 // pane-loading, pane-stale, pane-loaded, pane-reload-pending
 //
 // There are five states based on the presence or absence of css classes:
 //
-// 1. no pane-* states means the pane must be loaded when the pane becomes active
+// 1. Absence of any pane-* states means the pane is empty, and should
+// be loaded as soon as it becomes visible.
 //
-// 2. "pane-loading" means an AJAX call has been made to reload the pane and we are
-// waiting on a result
+// 2. "pane-loading" means an AJAX call has been made to reload the
+// pane and we are waiting on a result.
 //
-// 3. "pane-loading pane-stale" indicates a pane that is already loading has
-// been invalidated and should schedule a reload immediately when the current
-// load completes.  (This happens when there is a cluster of events, where the
-// reload is triggered by the first event, but we want ensure that we
-// eventually load the final quiescent state).
+// 3. "pane-loading pane-stale" means the pane is loading, but has
+// already been invalidated and should schedule a reload as soon as
+// possible after the current load completes. (This happens when there
+// is a cluster of events, where the reload is triggered by the first
+// event, but we want ensure that we eventually load the final
+// quiescent state).
 //
-// 4. "pane-loaded" means the pane is up to date
+// 4. "pane-loaded" means the pane is up to date.
 //
-// 5. "pane-loaded pane-reload-pending" indicates a reload is scheduled (but has
-// not started yet), suppressing scheduling of any further reloads.
+// 5. "pane-loaded pane-reload-pending" means a reload is needed, and
+// has been scheduled, but has not started because the pane's
+// minimum-time-between-reloads throttle has not yet been reached.
 //
 $(document).on('arv:pane:reload', '[data-pane-content-url]', function(e) {
-    // $pane, the event target, is an element whose content is to be
-    // replaced. Pseudoclasses on $pane (pane-loading, etc) encode the
-    // current loading state.
-    var $pane = $(e.target);
-
-    var content_url = $pane.attr('data-pane-content-url');
-    if (!content_url) {
-        // When reloadable elements are nested, we can receive
-        // arv:pane:reload events even though the selector in .on()
-        // does not match e.target. Ignore such events.
+    if (this != e.target) {
+        // An arv:pane:reload event was sent to an element (e.target)
+        // which happens to have an ancestor (this) matching the above
+        // '[data-pane-content-url]' selector. This happens because
+        // events bubble up the DOM on their way to document. However,
+        // here we only care about events delivered directly to _this_
+        // selected element (i.e., this==e.target), not ones delivered
+        // to its children. The event "e" is uninteresting here.
         return;
     }
 
-    e.stopPropagation();
+    // $pane, the event target, is an element whose content is to be
+    // replaced. Pseudoclasses on $pane (pane-loading, etc) encode the
+    // current loading state.
+    var $pane = $(this);
 
     if ($pane.hasClass('pane-loading')) {
         // Already loading, mark stale to schedule a reload after this one.
@@ -99,10 +107,11 @@ $(document).on('arv:pane:reload', '[data-pane-content-url]', function(e) {
 
     $pane.addClass('pane-loading');
 
+    var content_url = $pane.attr('data-pane-content-url');
     $.ajax(content_url, {dataType: 'html', type: 'GET', context: $pane}).
         done(function(data, status, jqxhr) {
-            // Preserve collapsed state
             var $pane = this;
+            // Preserve collapsed state
             var collapsable = {};
             $(".collapse", this).each(function(i, c) {
                 collapsable[c.id] = $(c).hasClass('in');
@@ -119,7 +128,7 @@ $(document).on('arv:pane:reload', '[data-pane-content-url]', function(e) {
             $pane.removeClass('pane-loading');
             $pane.addClass('pane-loaded');
             $pane.attr('data-loaded-at', (new Date()).getTime());
-            $pane.trigger('arv:pane:loaded');
+            $pane.trigger('arv:pane:loaded', [$pane]);
 
             if ($pane.hasClass('pane-stale')) {
                 $pane.trigger('arv:pane:reload');
@@ -128,7 +137,23 @@ $(document).on('arv:pane:reload', '[data-pane-content-url]', function(e) {
             var $pane = this;
             var errhtml;
             var contentType = jqxhr.getResponseHeader('Content-Type');
-            if (contentType && contentType.match(/\btext\/html\b/)) {
+            if (jqxhr.readyState == 0 || jqxhr.status == 0) {
+                if ($pane.attr('data-loaded-at') > 0) {
+                    // Stale content is already present. Leave it
+                    // there while loading the next page.
+                    $pane.removeClass('pane-loading');
+                    $pane.addClass('pane-loaded');
+                    // ...but schedule another refresh (after a
+                    // throttle delay) in case the act of navigating
+                    // away gets cancelled itself, leaving this page
+                    // with content that we know is stale.
+                    $pane.addClass('pane-stale');
+                    $pane.attr('data-loaded-at', (new Date()).getTime());
+                    $pane.trigger('arv:pane:reload');
+                    return;
+                }
+                errhtml = "Cancelled.";
+            } else if (contentType && contentType.match(/\btext\/html\b/)) {
                 var $response = $(jqxhr.responseText);
                 var $wrapper = $('div#page-wrapper', $response);
                 if ($wrapper.length) {
@@ -143,7 +168,7 @@ $(document).on('arv:pane:reload', '[data-pane-content-url]', function(e) {
                     replace(/</g, '&lt;').
                     replace(/>/g, '&gt;');
             }
-            $pane.html('<div><p>' +
+            $pane.html('<div class="pane-error-display"><p>' +
                       '<a href="#" class="btn btn-primary tab_reload">' +
                       '<i class="fa fa-fw fa-refresh"></i> ' +
                       'Reload tab</a></p><iframe style="width: 100%"></iframe></div>');
@@ -174,9 +199,13 @@ $(document).on('arv:pane:reload:all', function() {
     $('[data-pane-content-url]').trigger('arv:pane:reload');
 });
 
-$(document).on('arv-log-event', '.arv-refresh-on-log-event', function(e) {
+$(document).on('arv-log-event', '.arv-refresh-on-log-event', function(event) {
+    if (this != event.target) {
+        // Not interested in events sent to child nodes.
+        return;
+    }
     // Panes marked arv-refresh-on-log-event should be refreshed
-    $(e.target).trigger('arv:pane:reload');
+    $(event.target).trigger('arv:pane:reload');
 });
 
 // If there is a 'tab counts url' in the nav-tabs element then use it to get some javascript that will update them