X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/c203e53c2929c2ddf1b079ec077364f8f4d23c40..1693dc615e67c1bae3b6b52c0631fc58d8475d8f:/apps/workbench/app/assets/javascripts/tab_panes.js diff --git a/apps/workbench/app/assets/javascripts/tab_panes.js b/apps/workbench/app/assets/javascripts/tab_panes.js index ea17f6cc06..6356055f30 100644 --- a/apps/workbench/app/assets/javascripts/tab_panes.js +++ b/apps/workbench/app/assets/javascripts/tab_panes.js @@ -1,79 +1,181 @@ // Load tab panes on demand. See app/views/application/_content.html.erb -// Fire when a tab is selected/clicked. Check whether the content in -// the corresponding pane is loaded (or is being loaded). If not, -// start an AJAX request to load the content. -$(document).on('shown.bs.tab', '[data-toggle="tab"]', function(e) { - var content_url = $(e.target).attr('data-pane-content-url'); - var $pane = $($(e.target).attr('href')); - if ($pane.hasClass('loaded')) +// Fire when a tab is selected/clicked. +$(document).on('shown.bs.tab', '[data-toggle="tab"]', function(event) { + // When we switch tabs, remove "active" from any refreshable panes within + // the previous tab content so they don't continue to refresh unnecessarily, and + // add "active" to any refreshable panes under the newly shown tab content. + + var tgt = $($(event.relatedTarget).attr('href')); + $(".pane-anchor", tgt).each(function (i, e) { + var a = $($(e).attr('href')); + a.removeClass("active"); + }); + + tgt = $($(event.target).attr('href')); + $(".pane-anchor", tgt).each(function (i, e) { + var a = $($(e).attr('href')); + a.addClass("active"); + }); + + // Now trigger reload of the newly shown tab pane. + $(event.target).trigger('arv:pane:reload'); +}); + +// 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". +// +// There are four CSS classes set on the object 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 +// +// 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). +// +// 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. +// +$(document).on('arv:pane:reload', function(e) { + e.stopPropagation(); + + // '$anchor' is the event target, which is a .pane-anchor or a bootstrap + // tab anchor. This is the element that stores the state of the pane. The + // actual element that will contain the content is pointed to in the 'href' + // attribute of etarget. + var $anchor = $(e.target); + + if ($anchor.hasClass('pane-loading')) { + // Already loading, mark stale to schedule a reload after this one. + $anchor.addClass('pane-stale'); + return; + } + + if ($anchor.hasClass('pane-no-auto-reload') && $anchor.hasClass('pane-loaded')) { + // Have to explicitly remove pane-loaded if we want it to reload. + return; + } + + var throttle = $anchor.attr('data-load-throttle'); + if (!throttle) { + throttle = 3000; + } + var now = (new Date()).getTime(); + var loaded_at = $anchor.attr('data-loaded-at'); + var since_last_load = now - loaded_at; + if (loaded_at && (since_last_load < throttle)) { + if (!$anchor.hasClass('pane-reload-pending')) { + $anchor.addClass('pane-reload-pending'); + setTimeout((function() { + $anchor.trigger('arv:pane:reload'); + }), throttle - since_last_load); + } return; - $.ajax(content_url, {dataType: 'html', type: 'GET', context: $pane}). - done(function(data, status, jqxhr) { - $('> div > div', this).html(data); - $(this).addClass('loaded'); - $(this).trigger('arv:pane:loaded'); - }).fail(function(jqxhr, status, error) { - var errhtml; - if (jqxhr.getResponseHeader('Content-Type').match(/\btext\/html\b/)) { - var $response = $(jqxhr.responseText); - var $wrapper = $('div#page-wrapper', $response); - if ($wrapper.length) { - errhtml = $wrapper.html(); + } + + // We know this doesn't have 'pane-loading' because we tested for it above + $anchor.removeClass('pane-reload-pending'); + $anchor.removeClass('pane-loaded'); + $anchor.removeClass('pane-stale'); + + // $pane is the actual content area that is going to be updated. + var $pane = $($anchor.attr('href')); + if ($pane.hasClass('active')) { + $anchor.addClass('pane-loading'); + + var content_url = $anchor.attr('data-pane-content-url'); + $.ajax(content_url, {dataType: 'html', type: 'GET', context: $pane}). + done(function(data, status, jqxhr) { + // Preserve collapsed state + var collapsable = {}; + $(".collapse", this).each(function(i, c) { + collapsable[c.id] = $(c).hasClass('in'); + }); + var tmp = $(data); + $(".collapse", tmp).each(function(i, c) { + if (collapsable[c.id]) { + $(c).addClass('in'); + } else { + $(c).removeClass('in'); + } + }); + this.html(tmp); + $anchor.removeClass('pane-loading'); + $anchor.addClass('pane-loaded'); + $anchor.attr('data-loaded-at', (new Date()).getTime()); + this.trigger('arv:pane:loaded'); + + if ($anchor.hasClass('pane-stale')) { + $anchor.trigger('arv:pane:reload'); + } + }).fail(function(jqxhr, status, error) { + var errhtml; + if (jqxhr.getResponseHeader('Content-Type').match(/\btext\/html\b/)) { + var $response = $(jqxhr.responseText); + var $wrapper = $('div#page-wrapper', $response); + if ($wrapper.length) { + errhtml = $wrapper.html(); + } else { + errhtml = jqxhr.responseText; + } } else { - errhtml = jqxhr.responseText; + errhtml = ("An error occurred: " + + (jqxhr.responseText || status)). + replace(/&/g, '&'). + replace(//g, '>'); } - } else { - errhtml = ("An error occurred: " + - (jqxhr.responseText || status)). - replace(/&/g, '&'). - replace(//g, '>'); - } - $('> div > div', this).html( - '
' + - '' + - ' ' + - 'Reload tab
' + + '' + + ' ' + + 'Reload tab