1 // This file is part of Moodle - http://moodle.org/
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.
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/>.
17 * Manage the courses view for the overview block.
19 * @package block_myoverview
20 * @copyright 2018 Bas Brands <bas@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 'block_myoverview/repository',
28 'core/paged_content_factory',
29 'core/custom_interaction_events',
43 ACTION_ADD_FAVOURITE: '[data-action="add-favourite"]',
44 ACTION_REMOVE_FAVOURITE: '[data-action="remove-favourite"]',
45 FAVOURITE_ICON: '[data-region="favourite-icon"]',
46 ICON_IS_FAVOURITE: '[data-region="is-favourite"]',
47 ICON_NOT_FAVOURITE: '[data-region="not-favourite"]',
48 PAGED_CONTENT_CONTAINER: '[data-region="page-container"]'
53 COURSES_CARDS: 'block_myoverview/view-cards',
54 COURSES_LIST: 'block_myoverview/view-list',
55 COURSES_SUMMARY: 'block_myoverview/view-summary',
56 NOCOURSES: 'block_myoverview/no-courses'
59 var NUMCOURSES_PERPAGE = [12, 24, 48];
64 * Get filter values from DOM.
66 * @param {object} root The root element for the courses view.
67 * @return {filters} Set filters.
69 var getFilterValues = function(root) {
71 filters.display = root.attr('data-display');
72 filters.grouping = root.attr('data-grouping');
73 filters.sort = root.attr('data-sort');
77 // We want the paged content controls below the paged content area.
78 // and the controls should be ignored while data is loading.
79 var DEFAULT_PAGED_CONTENT_CONFIG = {
80 ignoreControlWhileLoading: true,
81 controlPlacementBottom: true,
85 * Get enrolled courses from backend.
87 * @param {object} filters The filters for this view.
88 * @param {int} limit The number of courses to show.
89 * @param {int} pageNumber The pagenumber to view.
90 * @return {promise} Resolved with an array of courses.
92 var getMyCourses = function(filters, limit, pageNumber) {
94 return Repository.getEnrolledCoursesByTimeline({
95 offset: pageNumber * limit,
97 classification: filters.grouping,
103 * Get the container element for the favourite icon.
105 * @param {Object} root The course overview container
106 * @param {Number} courseId Course id number
107 * @return {Object} The favourite icon container
109 var getFavouriteIconContainer = function(root, courseId) {
110 return root.find(SELECTORS.FAVOURITE_ICON + '[data-course-id="' + courseId + '"]');
114 * Get the paged content container element.
116 * @param {Object} root The course overview container
117 * @param {Number} index Rendered page index.
118 * @return {Object} The rendered paged container.
120 var getPagedContentContainer = function(root, index) {
121 return root.find('[data-region="paged-content-page"][data-page="' + index + '"]');
125 * Get the course id from a favourite element.
127 * @param {Object} root The favourite icon container element.
128 * @return {Number} Course id.
130 var getFavouriteCourseId = function(root) {
131 return root.attr('data-course-id');
135 * Hide the favourite icon.
137 * @param {Object} root The favourite icon container element.
138 * @param {Number} courseId Course id number.
140 var hideFavouriteIcon = function(root, courseId) {
141 var iconContainer = getFavouriteIconContainer(root, courseId);
142 var isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE);
143 isFavouriteIcon.addClass('hidden');
144 isFavouriteIcon.attr('aria-hidden', true);
145 var notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE);
146 notFavourteIcon.removeClass('hidden');
147 notFavourteIcon.attr('aria-hidden', false);
151 * Show the favourite icon.
153 * @param {Object} root The course overview container.
154 * @param {Number} courseId Course id number.
156 var showFavouriteIcon = function(root, courseId) {
157 var iconContainer = getFavouriteIconContainer(root, courseId);
158 var isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE);
159 isFavouriteIcon.removeClass('hidden');
160 isFavouriteIcon.attr('aria-hidden', false);
161 var notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE);
162 notFavourteIcon.addClass('hidden');
163 notFavourteIcon.attr('aria-hidden', true);
167 * Get the action menu item
169 * @param {Object} root root The course overview container
170 * @param {Number} courseId Course id.
171 * @return {Object} The add to favourite menu item.
173 var getAddFavouriteMenuItem = function(root, courseId) {
174 return root.find('[data-action="add-favourite"][data-course-id="' + courseId + '"]');
178 * Get the action menu item
180 * @param {Object} root root The course overview container
181 * @param {Number} courseId Course id.
182 * @return {Object} The remove from favourites menu item.
184 var getRemoveFavouriteMenuItem = function(root, courseId) {
185 return root.find('[data-action="remove-favourite"][data-course-id="' + courseId + '"]');
189 * Add course to favourites
191 * @param {Object} root The course overview container
192 * @param {Number} courseId Course id number
194 var addToFavourites = function(root, courseId) {
195 var removeAction = getRemoveFavouriteMenuItem(root, courseId);
196 var addAction = getAddFavouriteMenuItem(root, courseId);
198 setCourseFavouriteState(courseId, true).then(function(success) {
200 // Trigger a JS event so the starred courses block can refresh the list of courses.
201 $('body').trigger('myoverview-events:course_starred', [courseId]);
203 removeAction.removeClass('hidden');
204 addAction.addClass('hidden');
205 showFavouriteIcon(root, courseId);
207 Notification.alert('Starring course failed', 'Could not change favourite state');
210 }).catch(Notification.exception);
214 * Remove course from favourites
216 * @param {Object} root The course overview container
217 * @param {Number} courseId Course id number
219 var removeFromFavourites = function(root, courseId) {
220 var removeAction = getRemoveFavouriteMenuItem(root, courseId);
221 var addAction = getAddFavouriteMenuItem(root, courseId);
223 setCourseFavouriteState(courseId, false).then(function(success) {
225 $('body').trigger('myoverview-events:course_unstarred', [courseId]);
226 removeAction.addClass('hidden');
227 addAction.removeClass('hidden');
228 hideFavouriteIcon(root, courseId);
230 Notification.alert('Starring course failed', 'Could not change favourite state');
233 }).catch(Notification.exception);
237 * Set the courses favourite status and push to repository
239 * @param {Number} courseId Course id to favourite.
240 * @param {Bool} status new favourite status.
241 * @return {Promise} Repository promise.
243 var setCourseFavouriteState = function(courseId, status) {
245 return Repository.setFavouriteCourses({
252 }).then(function(result) {
253 if (result.warnings.length == 0) {
254 loadedPages.forEach(function(courseList) {
255 courseList.courses.forEach(function(course, index) {
256 if (course.id == courseId) {
257 courseList.courses[index].isfavourite = status;
265 }).catch(Notification.exception);
269 * Render the dashboard courses.
271 * @param {object} root The root element for the courses view.
272 * @param {array} coursesData containing array of returned courses.
273 * @return {promise} jQuery promise resolved after rendering is complete.
275 var renderCourses = function(root, coursesData) {
277 var filters = getFilterValues(root);
279 var currentTemplate = '';
280 if (filters.display == 'cards') {
281 currentTemplate = TEMPLATES.COURSES_CARDS;
282 } else if (filters.display == 'list') {
283 currentTemplate = TEMPLATES.COURSES_LIST;
285 currentTemplate = TEMPLATES.COURSES_SUMMARY;
288 if (coursesData.courses.length) {
289 return Templates.render(currentTemplate, {
290 courses: coursesData.courses
293 var nocoursesimg = root.attr('data-nocoursesimg');
294 return Templates.render(TEMPLATES.NOCOURSES, {
295 nocoursesimg: nocoursesimg
301 * Intialise the courses list and cards views on page load.
303 * @param {object} root The root element for the courses view.
304 * @param {object} content The content element for the courses view.
306 var init = function(root, content) {
310 if (!root.attr('data-init')) {
311 registerEventListeners(root);
312 root.attr('data-init', true);
315 var filters = getFilterValues(root);
317 var pagedContentPromise = PagedContentFactory.createWithLimit(
319 function(pagesData, actions) {
322 pagesData.forEach(function(pageData) {
323 var currentPage = pageData.pageNumber;
324 var pageNumber = pageData.pageNumber - 1;
326 var pagePromise = getMyCourses(
330 ).then(function(coursesData) {
331 if (coursesData.courses.length < pageData.limit) {
332 actions.allItemsLoaded(pageData.pageNumber);
334 loadedPages[currentPage] = coursesData;
335 return renderCourses(root, coursesData);
337 .catch(Notification.exception);
339 promises.push(pagePromise);
344 DEFAULT_PAGED_CONTENT_CONFIG
347 pagedContentPromise.then(function(html, js) {
348 return Templates.replaceNodeContents(content, html, js);
349 }).catch(Notification.exception);
353 * Listen to, and handle events for the myoverview block.
355 * @param {Object} root The myoverview block container element.
357 var registerEventListeners = function(root) {
358 CustomEvents.define(root, [
359 CustomEvents.events.activate
362 root.on(CustomEvents.events.activate, SELECTORS.ACTION_ADD_FAVOURITE, function(e, data) {
363 var favourite = $(e.target).closest(SELECTORS.ACTION_ADD_FAVOURITE);
364 var courseId = getFavouriteCourseId(favourite);
365 addToFavourites(root, courseId);
366 data.originalEvent.preventDefault();
369 root.on(CustomEvents.events.activate, SELECTORS.ACTION_REMOVE_FAVOURITE, function(e, data) {
370 var favourite = $(e.target).closest(SELECTORS.ACTION_REMOVE_FAVOURITE);
371 var courseId = getFavouriteCourseId(favourite);
372 removeFromFavourites(root, courseId);
373 data.originalEvent.preventDefault();
376 root.on(CustomEvents.events.activate, SELECTORS.FAVOURITE_ICON, function(e, data) {
377 data.originalEvent.preventDefault();
382 * Reset the courses views to their original
383 * state on first page load.
385 * This is called when configuration has changed for the event lists
386 * to cause them to reload their data.
388 * @param {Object} root The root element for the timeline view.
389 * @param {Object} content The content element for the timeline view.
391 var reset = function(root, content) {
393 if (loadedPages.length > 0) {
394 loadedPages.forEach(function(courseList, index) {
395 var pagedContentPage = getPagedContentContainer(root, index);
396 renderCourses(root, courseList).then(function(html, js) {
397 return Templates.replaceNodeContents(pagedContentPage, html, js);
398 }).catch(Notification.exception);