-// 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) {
+ // reload the pane (unless it's already loaded)
+ $($(event.target).attr('href')).
+ not('.pane-loaded').
+ trigger('arv:pane:reload');
+});
+
+// Ask a refreshable pane to reload via ajax.
+//
+// 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 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. 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.
+//
+// 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.
+//
+// 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) {
+ 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;
+ }
+
+ // $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.
+ $pane.addClass('pane-stale');