4084: Only load tab on switch if it doesn't have "pane-loaded" class. Increase
[arvados.git] / apps / workbench / app / assets / javascripts / tab_panes.js
1 // Load tab panes on demand. See app/views/application/_content.html.erb
2
3 // Fire when a tab is selected/clicked.
4 $(document).on('shown.bs.tab', '[data-toggle="tab"]', function(event) {
5     // When we switch tabs, remove "active" from any refreshable panes within
6     // the previous tab content so they don't continue to refresh unnecessarily, and
7     // add "active" to any refreshable panes under the newly shown tab content.
8
9     var tgt = $($(event.relatedTarget).attr('href'));
10     $(".pane-anchor", tgt).each(function (i, e) {
11         var a = $($(e).attr('href'));
12         a.removeClass("active");
13     });
14
15     tgt = $($(event.target).attr('href'));
16     $(".pane-anchor", tgt).each(function (i, e) {
17         var a = $($(e).attr('href'));
18         a.addClass("active");
19     });
20
21     if (!$(event.target).hasClass("pane-loaded")) {
22         // pane needs to be loaded
23         $(event.target).trigger('arv:pane:reload');
24     }
25 });
26
27 // Ask a refreshable pane to reload via ajax.
28 //
29 // Target of this event is the anchor element that manages the pane.  A reload
30 // consists of an AJAX call to load the "data-pane-content-url" and replace the
31 // contents of the DOM node pointed to by "href".
32 //
33 // There are four CSS classes set on the object to indicate its state:
34 // pane-loading, pane-stale, pane-loaded, pane-reload-pending
35 //
36 // There are five states based on the presence or absence of css classes:
37 //
38 // 1. no pane-* states means the pane must be loaded when the pane becomes active
39 //
40 // 2. "pane-loading" means an AJAX call has been made to reload the pane and we are
41 // waiting on a result
42 //
43 // 3. "pane-loading pane-stale" indicates a pane that is already loading has
44 // been invalidated and should schedule a reload immediately when the current
45 // load completes.  (This happens when there is a cluster of events, where the
46 // reload is triggered by the first event, but we want ensure that we
47 // eventually load the final quiescent state).
48 //
49 // 4. "pane-loaded" means the pane is up to date
50 //
51 // 5. "pane-loaded pane-reload-pending" indicates a reload is scheduled (but has
52 // not started yet), suppressing scheduling of any further reloads.
53 //
54 $(document).on('arv:pane:reload', function(e) {
55     e.stopPropagation();
56
57     // '$anchor' is the event target, which is a .pane-anchor or a bootstrap
58     // tab anchor.  This is the element that stores the state of the pane.  The
59     // actual element that will contain the content is pointed to in the 'href'
60     // attribute of etarget.
61     var $anchor = $(e.target);
62
63     if ($anchor.hasClass('pane-loading')) {
64         // Already loading, mark stale to schedule a reload after this one.
65         $anchor.addClass('pane-stale');
66         return;
67     }
68
69     if ($anchor.hasClass('pane-no-auto-reload') && $anchor.hasClass('pane-loaded')) {
70         // Have to explicitly remove pane-loaded if we want it to reload.
71         return;
72     }
73
74     var throttle = $anchor.attr('data-load-throttle');
75     if (!throttle) {
76         throttle = 15000;
77     }
78     var now = (new Date()).getTime();
79     var loaded_at = $anchor.attr('data-loaded-at');
80     var since_last_load = now - loaded_at;
81     if (loaded_at && (since_last_load < throttle)) {
82         if (!$anchor.hasClass('pane-reload-pending')) {
83             $anchor.addClass('pane-reload-pending');
84             setTimeout((function() {
85                 $anchor.trigger('arv:pane:reload');
86             }), throttle - since_last_load);
87         }
88         return;
89     }
90
91     // We know this doesn't have 'pane-loading' because we tested for it above
92     $anchor.removeClass('pane-reload-pending');
93     $anchor.removeClass('pane-loaded');
94     $anchor.removeClass('pane-stale');
95
96     // $pane is the actual content area that is going to be updated.
97     var $pane = $($anchor.attr('href'));
98     if ($pane.hasClass('active')) {
99         $anchor.addClass('pane-loading');
100
101         var content_url = $anchor.attr('data-pane-content-url');
102         $.ajax(content_url, {dataType: 'html', type: 'GET', context: $pane}).
103             done(function(data, status, jqxhr) {
104                 // Preserve collapsed state
105                 var collapsable = {};
106                 $(".collapse", this).each(function(i, c) {
107                     collapsable[c.id] = $(c).hasClass('in');
108                 });
109                 var tmp = $(data);
110                 $(".collapse", tmp).each(function(i, c) {
111                     if (collapsable[c.id]) {
112                         $(c).addClass('in');
113                     } else {
114                         $(c).removeClass('in');
115                     }
116                 });
117                 this.html(tmp);
118                 $anchor.removeClass('pane-loading');
119                 $anchor.addClass('pane-loaded');
120                 $anchor.attr('data-loaded-at', (new Date()).getTime());
121                 this.trigger('arv:pane:loaded');
122
123                 if ($anchor.hasClass('pane-stale')) {
124                     $anchor.trigger('arv:pane:reload');
125                 }
126             }).fail(function(jqxhr, status, error) {
127                 var errhtml;
128                 if (jqxhr.getResponseHeader('Content-Type').match(/\btext\/html\b/)) {
129                     var $response = $(jqxhr.responseText);
130                     var $wrapper = $('div#page-wrapper', $response);
131                     if ($wrapper.length) {
132                         errhtml = $wrapper.html();
133                     } else {
134                         errhtml = jqxhr.responseText;
135                     }
136                 } else {
137                     errhtml = ("An error occurred: " +
138                                (jqxhr.responseText || status)).
139                         replace(/&/g, '&amp;').
140                         replace(/</g, '&lt;').
141                         replace(/>/g, '&gt;');
142                 }
143                 this.html('<div><p>' +
144                         '<a href="#" class="btn btn-primary tab_reload">' +
145                         '<i class="fa fa-fw fa-refresh"></i> ' +
146                         'Reload tab</a></p><iframe style="width: 100%"></iframe></div>');
147                 $('.tab_reload', this).click(function() {
148                     this.html('<div class="spinner spinner-32px spinner-h-center"></div>');
149                     $anchor.trigger('arv:pane:reload');
150                 });
151                 // We want to render the error in an iframe, in order to
152                 // avoid conflicts with the main page's element ids, etc.
153                 // In order to do that dynamically, we have to set a
154                 // timeout on the iframe window to load our HTML *after*
155                 // the default source (e.g., about:blank) has loaded.
156                 var iframe = $('iframe', this)[0];
157                 iframe.contentWindow.setTimeout(function() {
158                     $('body', iframe.contentDocument).html(errhtml);
159                     iframe.height = iframe.contentDocument.body.scrollHeight + "px";
160                 }, 1);
161                 $anchor.removeClass('pane-loading');
162                 $anchor.addClass('pane-loaded');
163             });
164     } else {
165         // When the user selects e.target tab, show a spinner instead of
166         // old content while loading.
167         $pane.html('<div class="spinner spinner-32px spinner-h-center"></div>');
168     }
169 });
170
171 // Mark all panes as stale/dirty. Refresh any 'active' panes.
172 $(document).on('arv:pane:reload:all', function() {
173     $('.pane-anchor').trigger('arv:pane:reload');
174 });
175
176 $(document).on('ready ajax:complete', function() {
177     // Panes marked arv-refresh-on-log-event should be refreshed
178     $('.pane-anchor.arv-refresh-on-log-event').on('arv-log-event', function(e) {
179         $(e.target).trigger('arv:pane:reload');
180     });
181 });
182
183 // If there is a 'tab counts url' in the nav-tabs element then use it to get some javascript that will update them
184 $(document).on('ready count-change', function() {
185     var tabCountsUrl = $('ul.nav-tabs').data('tab-counts-url');
186     if( tabCountsUrl && tabCountsUrl.length ) {
187         $.get( tabCountsUrl );
188     }
189 });