MDL-63303 message: fix bugs in message drawer part 4
[moodle.git] / message / amd / src / message_drawer_lazy_load_list.js
blob13d0022af88d102c34e47882321d524c73e446ef
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Lazy loaded list of items.
18  *
19  * @module     core_message/message_drawer_lazy_load_list
20  * @package    message
21  * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 define(
26     'jquery',
27     'core/custom_interaction_events'
29 function(
30     $,
31     CustomEvents
32 ) {
34     var SELECTORS = {
35         ROOT: '[data-region="lazy-load-list"]',
36         LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
37         CONTENT_CONTAINER: '[data-region="content-container"]',
38         EMPTY_MESSAGE: '[data-region="empty-message-container"]',
39         PLACEHOLDER: '[data-region="placeholder-container"]'
40     };
42     /**
43      * Flag element as loading.
44      *
45      * @param {Object} root The section container element.
46      */
47     var startLoading = function(root) {
48         root.attr('data-loading', true);
49     };
51     /**
52      * Flag element as not loading.
53      *
54      * @param {Object} root The section container element.
55      */
56     var stopLoading = function(root) {
57         root.attr('data-loading', false);
58     };
60     /**
61      * Check if the element is loading.
62      *
63      * @param {Object} root The section container element.
64      * @return {Bool}
65      */
66     var isLoading = function(root) {
67         return root.attr('data-loading') === 'true';
68     };
70     /**
71      * Get user id
72      *
73      * @param  {Object} root The section container element.
74      * @return {Number} Logged in user id.
75      */
76     var getUserId = function(root) {
77         return root.attr('data-user-id');
78     };
80     /**
81      * Get the section content container element.
82      *
83      * @param  {Object} root The section container element.
84      * @return {Object} The section content container element.
85      */
86     var getContentContainer = function(root) {
87         return root.find(SELECTORS.CONTENT_CONTAINER);
88     };
90     /**
91      * Get the root element.
92      *
93      * @param  {Object} containerElement The container element to search in.
94      * @return {Object} The list root element.
95      */
96     var getRoot = function(containerElement) {
97         return containerElement.find(SELECTORS.ROOT);
98     };
100     /**
101      * Show the loading icon.
102      *
103      * @param {Object} root The section container element.
104      */
105     var showLoadingIcon = function(root) {
106         root.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');
107     };
109     /**
110      * Hide the loading icon.
111      *
112      * @param {Object} root The section container element.
113      */
114     var hideLoadingIcon = function(root) {
115         root.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
116     };
118     /**
119      * Show the empty message.
120      *
121      * @param {Object} root The section container element.
122      */
123     var showEmptyMessage = function(root) {
124         root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');
125     };
127     /**
128      * Hide the empty message.
129      *
130      * @param {Object} root The section container element.
131      */
132     var hideEmptyMessage = function(root) {
133         root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');
134     };
136     /**
137      * Show the placeholder element.
138      *
139      * @param {Object} root The section container element.
140      */
141     var showPlaceholder = function(root) {
142         root.find(SELECTORS.PLACEHOLDER).removeClass('hidden');
143     };
145     /**
146      * Hide the placeholder element.
147      *
148      * @param {Object} root The section container element.
149      */
150     var hidePlaceholder = function(root) {
151         root.find(SELECTORS.PLACEHOLDER).addClass('hidden');
152     };
154     /**
155      * Show the section content container.
156      *
157      * @param {Object} root The section container element.
158      */
159     var showContent = function(root) {
160         getContentContainer(root).removeClass('hidden');
161     };
163     /**
164      * Hide the section content container.
165      *
166      * @param {Object} root The section container element.
167      */
168     var hideContent = function(root) {
169         getContentContainer(root).addClass('hidden');
170     };
172     /**
173      * If the section has loaded all content.
174      *
175      * @param {Object} root The section container element.
176      * @return {Bool}
177      */
178     var hasLoadedAll = function(root) {
179         return root.attr('data-loaded-all') == 'true';
180     };
182     /**
183      * If the section has loaded all content.
184      *
185      * @param {Object} root The section container element.
186      * @param {Bool} value If all items have been loaded.
187      */
188     var setLoadedAll = function(root, value) {
189         root.attr('data-loaded-all', value);
190     };
192     /**
193      * If the section can load more items.
194      *
195      * @param {Object} root The section container element.
196      * @return {Bool}
197      */
198     var canLoadMore = function(root) {
199         return !hasLoadedAll(root) && !isLoading(root);
200     };
202     /**
203      * Load all items in this container from callback and render them.
204      *
205      * @param {Object} root The section container element.
206      * @param {Function} loadCallback The callback to load items.
207      * @param {Function} renderCallback The callback to render the results.
208      * @return {Object} jQuery promise
209      */
210     var loadAndRender = function(root, loadCallback, renderCallback) {
211         var userId = getUserId(root);
212         startLoading(root);
214         return loadCallback(root, userId)
215             .then(function(items) {
216                 if (items.length > 0) {
217                     var contentContainer = getContentContainer(root);
218                     return renderCallback(contentContainer, items, userId)
219                         .then(function() {
220                             return items;
221                         });
222                 } else {
223                     return items;
224                 }
225             })
226             .then(function(items) {
227                 stopLoading(root);
228                 root.attr('data-seen', true);
230                 if (!items.length) {
231                     setLoadedAll(root, true);
232                 }
234                 return items;
235             })
236             .catch(function() {
237                 stopLoading(root);
238                 root.attr('data-seen', true);
239                 return;
240             });
241     };
243     /**
244      * First load of this section.
245      *
246      * @param {Object} root The section container element.
247      * @param {Function} loadCallback The callback to load items.
248      * @param {Function} renderCallback The callback to render the results.
249      * @return {Object} promise
250      */
251     var initialLoadAndRender = function(root, loadCallback, renderCallback) {
252         getContentContainer(root).empty();
253         showPlaceholder(root);
254         hideContent(root);
255         return loadAndRender(root, loadCallback, renderCallback)
256             .then(function(items) {
257                 hidePlaceholder(root);
259                 if (!items.length) {
260                     showEmptyMessage(root);
261                 } else {
262                     showContent(root);
263                 }
265                 return;
266             })
267             .catch(function() {
268                 hidePlaceholder(root);
269                 showContent(root);
270                 return;
271             });
272     };
274     /**
275      * Listen to, and handle events in this section.
276      *
277      * @param {Object} root The section container element.
278      * @param {Function} loadCallback The callback to load items.
279      * @param {Function} renderCallback The callback to render the results.
280      */
281     var registerEventListeners = function(root, loadCallback, renderCallback) {
282         CustomEvents.define(root, [
283             CustomEvents.events.scrollBottom
284         ]);
286         root.on(CustomEvents.events.scrollBottom, function() {
287             if (canLoadMore(root)) {
288                 showLoadingIcon(root);
289                 loadAndRender(root, loadCallback, renderCallback)
290                     .then(function() {
291                         return hideLoadingIcon(root);
292                     })
293                     .catch(function() {
294                         return hideLoadingIcon(root);
295                     });
296             }
297         });
298     };
300     /**
301      * Setup the section.
302      *
303      * @param {Object} root The section container element.
304      * @param {Function} loadCallback The callback to load items.
305      * @param {Function} renderCallback The callback to render the results.
306      */
307     var show = function(root, loadCallback, renderCallback) {
308         root = $(root);
310         if (!root.attr('data-init')) {
311             registerEventListeners(root, loadCallback, renderCallback);
312             initialLoadAndRender(root, loadCallback, renderCallback);
313             root.attr('data-init', true);
314         }
315     };
317     return {
318         show: show,
319         getContentContainer: getContentContainer,
320         getRoot: getRoot,
321         setLoadedAll: setLoadedAll,
322         showEmptyMessage: showEmptyMessage,
323         hideEmptyMessage: hideEmptyMessage,
324         showContent: showContent,
325         hideContent: hideContent
326     };