Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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.
12
//
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/>.
15
 
16
/**
17
 * Javascript to initialise the Recently accessed courses block.
18
 *
19
 * @module     block_recentlyaccessedcourses/main
20
 * @copyright  2018 Victor Deniz <victor@moodle.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
define(
25
    [
26
        'jquery',
27
        'core/custom_interaction_events',
28
        'core/notification',
29
        'core/pubsub',
30
        'core/paged_content_paging_bar',
31
        'core/templates',
32
        'core_course/events',
33
        'core_course/repository',
34
        'core/aria',
35
    ],
36
    function(
37
        $,
38
        CustomEvents,
39
        Notification,
40
        PubSub,
41
        PagedContentPagingBar,
42
        Templates,
43
        CourseEvents,
44
        CoursesRepository,
45
        Aria
46
    ) {
47
 
48
        // Constants.
49
        var NUM_COURSES_TOTAL = 10;
50
        var SELECTORS = {
51
            BLOCK_CONTAINER: '[data-region="recentlyaccessedcourses"]',
52
            CARD_CONTAINER: '[data-region="card-deck"]',
53
            COURSE_IS_FAVOURITE: '[data-region="is-favourite"]',
54
            CONTENT: '[data-region="view-content"]',
55
            EMPTY_MESSAGE: '[data-region="empty-message"]',
56
            LOADING_PLACEHOLDER: '[data-region="loading-placeholder"]',
57
            PAGING_BAR: '[data-region="paging-bar"]',
58
            PAGING_BAR_NEXT: '[data-control="next"]',
59
            PAGING_BAR_PREVIOUS: '[data-control="previous"]'
60
        };
61
        // Module variables.
62
        var contentLoaded = false;
63
        var allCourses = [];
64
        var visibleCoursesId = null;
65
        var cardWidth = null;
66
        var viewIndex = 0;
67
        var availableVisibleCards = 1;
68
 
69
        /**
70
         * Show the empty message when no course are found.
71
         *
72
         * @param {object} root The root element for the courses view.
73
         */
74
        var showEmptyMessage = function(root) {
75
            root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');
76
            root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');
77
            root.find(SELECTORS.CONTENT).addClass('hidden');
78
        };
79
 
80
        /**
81
         * Show the empty message when no course are found.
82
         *
83
         * @param {object} root The root element for the courses view.
84
         */
85
        var showContent = function(root) {
86
            root.find(SELECTORS.CONTENT).removeClass('hidden');
87
            root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');
88
            root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');
89
        };
90
 
91
        /**
92
         * Show the paging bar.
93
         *
94
         * @param {object} root The root element for the courses view.
95
         */
96
        var showPagingBar = function(root) {
97
            var pagingBar = root.find(SELECTORS.PAGING_BAR);
98
            pagingBar.css('opacity', 1);
99
            pagingBar.css('visibility', 'visible');
100
            Aria.unhide(pagingBar);
101
        };
102
 
103
        /**
104
         * Hide the paging bar.
105
         *
106
         * @param {object} root The root element for the courses view.
107
         */
108
        var hidePagingBar = function(root) {
109
            var pagingBar = root.find(SELECTORS.PAGING_BAR);
110
            pagingBar.css('opacity', 0);
111
            pagingBar.css('visibility', 'hidden');
112
            Aria.hide(pagingBar);
113
        };
114
 
115
        /**
116
         * Show the favourite indicator for the given course (if it's in the list).
117
         *
118
         * @param {object} root The root element for the courses view.
119
         * @param {number} courseId The id of the course to be favourited.
120
         */
121
        var favouriteCourse = function(root, courseId) {
122
            allCourses.forEach(function(course) {
123
                if (course.attr('data-course-id') == courseId) {
124
                    course.find(SELECTORS.COURSE_IS_FAVOURITE).removeClass('hidden');
125
                }
126
            });
127
        };
128
 
129
        /**
130
         * Hide the favourite indicator for the given course (if it's in the list).
131
         *
132
         * @param {object} root The root element for the courses view.
133
         * @param {number} courseId The id of the course to be unfavourited.
134
         */
135
        var unfavouriteCourse = function(root, courseId) {
136
            allCourses.forEach(function(course) {
137
                if (course.attr('data-course-id') == courseId) {
138
                    course.find(SELECTORS.COURSE_IS_FAVOURITE).addClass('hidden');
139
                }
140
            });
141
        };
142
 
143
        /**
144
         * Render the a list of courses.
145
         *
146
         * @param {array} courses containing array of courses.
147
         * @return {promise} Resolved with list of rendered courses as jQuery objects.
148
         */
149
        var renderAllCourses = function(courses) {
150
            var showcoursecategory = $(SELECTORS.BLOCK_CONTAINER).data('displaycoursecategory');
151
            var promises = courses.map(function(course) {
152
                course.showcoursecategory = showcoursecategory;
153
                return Templates.render('block_recentlyaccessedcourses/course-card', course);
154
            });
155
 
156
            return $.when.apply(null, promises).then(function() {
157
                var renderedCourses = [];
158
 
159
                promises.forEach(function(promise) {
160
                    promise.then(function(html) {
161
                        renderedCourses.push($(html));
162
                        return;
163
                    })
164
                    .catch(Notification.exception);
165
                });
166
 
167
                return renderedCourses;
168
            });
169
        };
170
 
171
        /**
172
         * Fetch user's recently accessed courses and reload the content of the block.
173
         *
174
         * @param {int} userid User whose courses will be shown
175
         * @returns {promise} The updated content for the block.
176
         */
177
        var loadContent = function(userid) {
178
            return CoursesRepository.getLastAccessedCourses(userid, NUM_COURSES_TOTAL)
179
                .then(function(courses) {
180
                    return renderAllCourses(courses);
181
                });
182
        };
183
 
184
        /**
185
         * Recalculate the number of courses that should be visible.
186
         *
187
         * @param {object} root The root element for the courses view.
188
         */
189
        var recalculateVisibleCourses = function(root) {
190
            var container = root.find(SELECTORS.CONTENT).find(SELECTORS.CARD_CONTAINER);
191
            var availableWidth = parseFloat(root.css('width'));
192
            var numberOfCourses = allCourses.length;
193
            var start = 0;
194
 
195
            if (!cardWidth) {
196
                container.html(allCourses[0]);
197
                // Render one card initially to calculate the width of the cards
198
                // including the margins.
199
                cardWidth = allCourses[0].outerWidth(true);
200
            }
201
 
202
            availableVisibleCards = Math.floor(availableWidth / cardWidth);
203
 
204
            if (viewIndex + availableVisibleCards < numberOfCourses) {
205
                start = viewIndex;
206
            } else {
207
                var overflow = (viewIndex + availableVisibleCards) - numberOfCourses;
208
                start = viewIndex - overflow;
209
                start = start >= 0 ? start : 0;
210
            }
211
 
212
            // At least show one card.
213
            if (availableVisibleCards === 0) {
214
                availableVisibleCards = 1;
215
            }
216
 
217
            var coursesToShow = allCourses.slice(start, start + availableVisibleCards);
218
            // Create an id for the list of courses we expect to be displayed.
219
            var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) {
220
                return carry + course.attr('data-course-id');
221
            }, '');
222
 
223
            // Centre the courses if we have an overflow of courses.
224
            if (allCourses.length > coursesToShow.length) {
225
                container.addClass('justify-content-center');
226
                container.removeClass('justify-content-start');
227
            } else {
228
                container.removeClass('justify-content-center');
229
                container.addClass('justify-content-start');
230
            }
231
 
232
            // Don't bother updating the DOM unless the visible courses have changed.
233
            if (visibleCoursesId != newVisibleCoursesId) {
234
                var pagingBar = root.find(PagedContentPagingBar.rootSelector);
235
                container.html(coursesToShow);
236
                visibleCoursesId = newVisibleCoursesId;
237
 
238
                if (availableVisibleCards >= allCourses.length) {
239
                    hidePagingBar(root);
240
                } else {
241
                    showPagingBar(root);
242
 
243
                    if (viewIndex === 0) {
244
                        PagedContentPagingBar.disablePreviousControlButtons(pagingBar);
245
                    } else {
246
                        PagedContentPagingBar.enablePreviousControlButtons(pagingBar);
247
                    }
248
 
249
                    if (viewIndex + availableVisibleCards >= allCourses.length) {
250
                        PagedContentPagingBar.disableNextControlButtons(pagingBar);
251
                    } else {
252
                        PagedContentPagingBar.enableNextControlButtons(pagingBar);
253
                    }
254
                }
255
            }
256
        };
257
 
258
        /**
259
         * Register event listeners for the block.
260
         *
261
         * @param {object} root The root element for the recentlyaccessedcourses block.
262
         */
263
        var registerEventListeners = function(root) {
264
            var resizeTimeout = null;
265
            var drawerToggling = false;
266
 
267
            PubSub.subscribe(CourseEvents.favourited, function(courseId) {
268
                favouriteCourse(root, courseId);
269
            });
270
 
271
            PubSub.subscribe(CourseEvents.unfavorited, function(courseId) {
272
                unfavouriteCourse(root, courseId);
273
            });
274
 
275
            PubSub.subscribe('nav-drawer-toggle-start', function() {
276
                if (!contentLoaded || !allCourses.length || drawerToggling) {
277
                    // Nothing to recalculate.
278
                    return;
279
                }
280
 
281
                drawerToggling = true;
282
                var recalculationCount = 0;
283
                // This function is going to recalculate the number of courses while
284
                // the nav drawer is opening or closes (up to a maximum of 5 recalcs).
285
                var doRecalculation = function() {
286
                    setTimeout(function() {
287
                        recalculateVisibleCourses(root);
288
                        recalculationCount++;
289
 
290
                        if (recalculationCount < 5 && drawerToggling) {
291
                            // If we haven't done too many recalculations and the drawer
292
                            // is still toggling then recurse.
293
                            doRecalculation();
294
                        }
295
                    }, 100);
296
                };
297
 
298
                // Start the recalculations.
299
                doRecalculation(root);
300
            });
301
 
302
            PubSub.subscribe('nav-drawer-toggle-end', function() {
303
                drawerToggling = false;
304
            });
305
 
306
            $(window).on('resize', function() {
307
                if (!contentLoaded || !allCourses.length) {
308
                    // Nothing to reclculate.
309
                    return;
310
                }
311
 
312
                // Resize events fire rapidly so recalculating the visible courses each
313
                // time can be expensive. Let's debounce them,
314
                if (!resizeTimeout) {
315
                    resizeTimeout = setTimeout(function() {
316
                        resizeTimeout = null;
317
                        recalculateVisibleCourses(root);
318
                    // The recalculateVisibleCourses function will execute at a rate of 15fps.
319
                    }, 66);
320
                }
321
            });
322
 
323
            CustomEvents.define(root, [CustomEvents.events.activate]);
324
            root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_NEXT, function(e, data) {
325
                var button = $(e.target).closest(SELECTORS.PAGING_BAR_NEXT);
326
                if (!button.hasClass('disabled')) {
327
                    viewIndex = viewIndex + availableVisibleCards;
328
                    recalculateVisibleCourses(root);
329
                }
330
 
331
                data.originalEvent.preventDefault();
332
            });
333
 
334
            root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_PREVIOUS, function(e, data) {
335
                var button = $(e.target).closest(SELECTORS.PAGING_BAR_PREVIOUS);
336
                if (!button.hasClass('disabled')) {
337
                    viewIndex = viewIndex - availableVisibleCards;
338
                    viewIndex = viewIndex < 0 ? 0 : viewIndex;
339
                    recalculateVisibleCourses(root);
340
                }
341
 
342
                data.originalEvent.preventDefault();
343
            });
344
        };
345
 
346
        /**
347
         * Get and show the recent courses into the block.
348
         *
349
         * @param {int} userid User from which the courses will be obtained
350
         * @param {object} root The root element for the recentlyaccessedcourses block.
351
         */
352
        var init = function(userid, root) {
353
            root = $(root);
354
 
355
            registerEventListeners(root);
356
            loadContent(userid)
357
                .then(function(renderedCourses) {
358
                    allCourses = renderedCourses;
359
                    contentLoaded = true;
360
 
361
                    if (allCourses.length) {
362
                        showContent(root);
363
                        recalculateVisibleCourses(root);
364
                    } else {
365
                        showEmptyMessage(root);
366
                    }
367
 
368
                    return;
369
                })
370
                .catch(Notification.exception);
371
        };
372
 
373
        return {
374
            init: init
375
        };
376
    });