Fix Canceled -> Cancelled (american vs canadian spelling, canada wins)
[arvados.git] / apps / workbench / app / assets / javascripts / infinite_scroll.js
1 function maybe_load_more_content(event) {
2     var scroller = this;        // element with scroll bars
3     var $container;             // element that receives new content
4     var src;                    // url for retrieving content
5     var scrollHeight;
6     var spinner, colspan;
7     var serial = Date.now();
8     var params;
9     scrollHeight = scroller.scrollHeight || $('body')[0].scrollHeight;
10     if ($(scroller).scrollTop() + $(scroller).height()
11         >
12         scrollHeight - 50)
13     {
14         $container = $(event.data.container);
15         if (!$container.attr('data-infinite-content-href0')) {
16             // Remember the first page source url, so we can refresh
17             // from page 1 later.
18             $container.attr('data-infinite-content-href0',
19                             $container.attr('data-infinite-content-href'));
20         }
21         src = $container.attr('data-infinite-content-href');
22         if (!src || !$container.is(':visible'))
23             // Finished
24             return;
25
26         // Don't start another request until this one finishes
27         $container.attr('data-infinite-content-href', null);
28         spinner = '<div class="spinner spinner-32px spinner-h-center"></div>';
29         if ($container.is('table,tbody,thead,tfoot')) {
30             // Hack to determine how many columns a new tr should have
31             // in order to reach full width.
32             colspan = $container.closest('table').
33                 find('tr').eq(0).find('td,th').length;
34             if (colspan == 0)
35                 colspan = '*';
36             spinner = ('<tr class="spinner"><td colspan="' + colspan + '">' +
37                        spinner +
38                        '</td></tr>');
39         }
40         $container.find(".spinner").detach();
41         $container.append(spinner);
42         $container.attr('data-infinite-serial', serial);
43
44         // Combine infiniteContentParams from multiple sources. This
45         // mechanism allows each of several components to set and
46         // update its own set of filters, without having to worry
47         // about stomping on some other component's filters.
48         //
49         // For example, filterable.js writes filters in
50         // infiniteContentParamsFilterable ("search for text foo")
51         // without worrying about clobbering the filters set up by the
52         // tab pane ("only show jobs and pipelines in this tab").
53         params = {};
54         $.each($container.data(), function(datakey, datavalue) {
55             // Note: We attach these data to DOM elements using
56             // <element data-foo-bar="baz">. We store/retrieve them
57             // using $('element').data('foo-bar'), although
58             // .data('fooBar') would also work. The "all data" hash
59             // returned by $('element').data(), however, always has
60             // keys like 'fooBar'. In other words, where we have a
61             // choice, we stick with the 'foo-bar' style to be
62             // consistent with HTML. Here, our only option is
63             // 'fooBar'.
64             if (/^infiniteContentParams/.exec(datakey)) {
65                 if (datavalue instanceof Object) {
66                     $.each(datavalue, function(hkey, hvalue) {
67                         if (hvalue instanceof Array) {
68                             params[hkey] = (params[hkey] || []).concat(hvalue);
69                         } else if (hvalue instanceof Object) {
70                             $.extend(params[hkey], hvalue);
71                         } else {
72                             params[hkey] = hvalue;
73                         }
74                     });
75                 }
76             }
77         });
78         $.each(params, function(k,v) {
79             if (v instanceof Object) {
80                 params[k] = JSON.stringify(v);
81             }
82         });
83
84         $.ajax(src,
85                {dataType: 'json',
86                 type: 'GET',
87                 data: params,
88                 context: {container: $container, src: src, serial: serial}}).
89             fail(function(jqxhr, status, error) {
90                 var $faildiv;
91                 var $container = this.container;
92                 if ($container.attr('data-infinite-serial') != this.serial) {
93                     // A newer request is already in progress.
94                     return;
95                 }
96                 if (jqxhr.readyState == 0 || jqxhr.status == 0) {
97                     message = "Cancelled."
98                 } else if (jqxhr.responseJSON && jqxhr.responseJSON.errors) {
99                     message = jqxhr.responseJSON.errors.join("; ");
100                 } else {
101                     message = "Request failed.";
102                 }
103                 // TODO: report the message to the user.
104                 console.log(message);
105                 $faildiv = $('<div />').
106                     attr('data-infinite-content-href', this.src).
107                     addClass('infinite-retry').
108                     append('<span class="fa fa-warning" /> Oops, request failed. <button class="btn btn-xs btn-primary">Retry</button>');
109                 $container.find('div.spinner').replaceWith($faildiv);
110             }).
111             done(function(data, status, jqxhr) {
112                 if ($container.attr('data-infinite-serial') != this.serial) {
113                     // A newer request is already in progress.
114                     return;
115                 }
116                 $container.find(".spinner").detach();
117                 $container.append(data.content);
118                 $container.attr('data-infinite-content-href', data.next_page_href);
119             });
120      }
121 }
122
123 function ping_all_scrollers() {
124     // Send a scroll event to all scroll listeners that might need
125     // updating. Adding infinite-scroller class to the window element
126     // doesn't work, so we add it explicitly here.
127     $('.infinite-scroller').add(window).trigger('scroll');
128 }
129
130 $(document).
131     on('click', 'div.infinite-retry button', function() {
132         var $retry_div = $(this).closest('.infinite-retry');
133         var $container = $(this).closest('.infinite-scroller-ready')
134         $container.attr('data-infinite-content-href',
135                         $retry_div.attr('data-infinite-content-href'));
136         $retry_div.
137             replaceWith('<div class="spinner spinner-32px spinner-h-center" />');
138         ping_all_scrollers();
139     }).
140     on('refresh-content', '[data-infinite-scroller]', function() {
141         // Clear all rows, reset source href to initial state, and
142         // (if the container is visible) start loading content.
143         var first_page_href = $(this).attr('data-infinite-content-href0');
144         if (!first_page_href)
145             first_page_href = $(this).attr('data-infinite-content-href');
146         $(this).
147             html('').
148             attr('data-infinite-content-href', first_page_href);
149         ping_all_scrollers();
150     }).
151     on('ready ajax:complete', function() {
152         $('[data-infinite-scroller]').each(function() {
153             if ($(this).hasClass('infinite-scroller-ready'))
154                 return;
155             $(this).addClass('infinite-scroller-ready');
156
157             // $scroller is the DOM element that hears "scroll"
158             // events: sometimes it's a div, sometimes it's
159             // window. Here, "this" is the DOM element containing the
160             // result rows. We pass it to maybe_load_more_content in
161             // event.data.
162             var $scroller = $($(this).attr('data-infinite-scroller'));
163             if (!$scroller.hasClass('smart-scroll') &&
164                 'scroll' != $scroller.css('overflow-y'))
165                 $scroller = $(window);
166             $scroller.
167                 addClass('infinite-scroller').
168                 on('scroll resize', { container: this }, maybe_load_more_content).
169                 trigger('scroll');
170         });
171     });