AutorÃa | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Javascript to initialise the Recently accessed courses block.** @module block_recentlyaccessedcourses/main* @copyright 2018 Victor Deniz <victor@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/define(['jquery','core/custom_interaction_events','core/notification','core/pubsub','core/paged_content_paging_bar','core/templates','core_course/events','core_course/repository','core/aria',],function($,CustomEvents,Notification,PubSub,PagedContentPagingBar,Templates,CourseEvents,CoursesRepository,Aria) {// Constants.var NUM_COURSES_TOTAL = 10;var SELECTORS = {BLOCK_CONTAINER: '[data-region="recentlyaccessedcourses"]',CARD_CONTAINER: '[data-region="card-deck"]',COURSE_IS_FAVOURITE: '[data-region="is-favourite"]',CONTENT: '[data-region="view-content"]',EMPTY_MESSAGE: '[data-region="empty-message"]',LOADING_PLACEHOLDER: '[data-region="loading-placeholder"]',PAGING_BAR: '[data-region="paging-bar"]',PAGING_BAR_NEXT: '[data-control="next"]',PAGING_BAR_PREVIOUS: '[data-control="previous"]'};// Module variables.var contentLoaded = false;var allCourses = [];var visibleCoursesId = null;var cardWidth = null;var viewIndex = 0;var availableVisibleCards = 1;/*** Show the empty message when no course are found.** @param {object} root The root element for the courses view.*/var showEmptyMessage = function(root) {root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');root.find(SELECTORS.CONTENT).addClass('hidden');};/*** Show the empty message when no course are found.** @param {object} root The root element for the courses view.*/var showContent = function(root) {root.find(SELECTORS.CONTENT).removeClass('hidden');root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');};/*** Show the paging bar.** @param {object} root The root element for the courses view.*/var showPagingBar = function(root) {var pagingBar = root.find(SELECTORS.PAGING_BAR);pagingBar.css('opacity', 1);pagingBar.css('visibility', 'visible');Aria.unhide(pagingBar);};/*** Hide the paging bar.** @param {object} root The root element for the courses view.*/var hidePagingBar = function(root) {var pagingBar = root.find(SELECTORS.PAGING_BAR);pagingBar.css('opacity', 0);pagingBar.css('visibility', 'hidden');Aria.hide(pagingBar);};/*** Show the favourite indicator for the given course (if it's in the list).** @param {object} root The root element for the courses view.* @param {number} courseId The id of the course to be favourited.*/var favouriteCourse = function(root, courseId) {allCourses.forEach(function(course) {if (course.attr('data-course-id') == courseId) {course.find(SELECTORS.COURSE_IS_FAVOURITE).removeClass('hidden');}});};/*** Hide the favourite indicator for the given course (if it's in the list).** @param {object} root The root element for the courses view.* @param {number} courseId The id of the course to be unfavourited.*/var unfavouriteCourse = function(root, courseId) {allCourses.forEach(function(course) {if (course.attr('data-course-id') == courseId) {course.find(SELECTORS.COURSE_IS_FAVOURITE).addClass('hidden');}});};/*** Render the a list of courses.** @param {array} courses containing array of courses.* @return {promise} Resolved with list of rendered courses as jQuery objects.*/var renderAllCourses = function(courses) {var showcoursecategory = $(SELECTORS.BLOCK_CONTAINER).data('displaycoursecategory');var promises = courses.map(function(course) {course.showcoursecategory = showcoursecategory;return Templates.render('block_recentlyaccessedcourses/course-card', course);});return $.when.apply(null, promises).then(function() {var renderedCourses = [];promises.forEach(function(promise) {promise.then(function(html) {renderedCourses.push($(html));return;}).catch(Notification.exception);});return renderedCourses;});};/*** Fetch user's recently accessed courses and reload the content of the block.** @param {int} userid User whose courses will be shown* @returns {promise} The updated content for the block.*/var loadContent = function(userid) {return CoursesRepository.getLastAccessedCourses(userid, NUM_COURSES_TOTAL).then(function(courses) {return renderAllCourses(courses);});};/*** Recalculate the number of courses that should be visible.** @param {object} root The root element for the courses view.*/var recalculateVisibleCourses = function(root) {var container = root.find(SELECTORS.CONTENT).find(SELECTORS.CARD_CONTAINER);var availableWidth = parseFloat(root.css('width'));var numberOfCourses = allCourses.length;var start = 0;if (!cardWidth) {container.html(allCourses[0]);// Render one card initially to calculate the width of the cards// including the margins.cardWidth = allCourses[0].outerWidth(true);}availableVisibleCards = Math.floor(availableWidth / cardWidth);if (viewIndex + availableVisibleCards < numberOfCourses) {start = viewIndex;} else {var overflow = (viewIndex + availableVisibleCards) - numberOfCourses;start = viewIndex - overflow;start = start >= 0 ? start : 0;}// At least show one card.if (availableVisibleCards === 0) {availableVisibleCards = 1;}var coursesToShow = allCourses.slice(start, start + availableVisibleCards);// Create an id for the list of courses we expect to be displayed.var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) {return carry + course.attr('data-course-id');}, '');// Centre the courses if we have an overflow of courses.if (allCourses.length > coursesToShow.length) {container.addClass('justify-content-center');container.removeClass('justify-content-start');} else {container.removeClass('justify-content-center');container.addClass('justify-content-start');}// Don't bother updating the DOM unless the visible courses have changed.if (visibleCoursesId != newVisibleCoursesId) {var pagingBar = root.find(PagedContentPagingBar.rootSelector);container.html(coursesToShow);visibleCoursesId = newVisibleCoursesId;if (availableVisibleCards >= allCourses.length) {hidePagingBar(root);} else {showPagingBar(root);if (viewIndex === 0) {PagedContentPagingBar.disablePreviousControlButtons(pagingBar);} else {PagedContentPagingBar.enablePreviousControlButtons(pagingBar);}if (viewIndex + availableVisibleCards >= allCourses.length) {PagedContentPagingBar.disableNextControlButtons(pagingBar);} else {PagedContentPagingBar.enableNextControlButtons(pagingBar);}}}};/*** Register event listeners for the block.** @param {object} root The root element for the recentlyaccessedcourses block.*/var registerEventListeners = function(root) {var resizeTimeout = null;var drawerToggling = false;PubSub.subscribe(CourseEvents.favourited, function(courseId) {favouriteCourse(root, courseId);});PubSub.subscribe(CourseEvents.unfavorited, function(courseId) {unfavouriteCourse(root, courseId);});PubSub.subscribe('nav-drawer-toggle-start', function() {if (!contentLoaded || !allCourses.length || drawerToggling) {// Nothing to recalculate.return;}drawerToggling = true;var recalculationCount = 0;// This function is going to recalculate the number of courses while// the nav drawer is opening or closes (up to a maximum of 5 recalcs).var doRecalculation = function() {setTimeout(function() {recalculateVisibleCourses(root);recalculationCount++;if (recalculationCount < 5 && drawerToggling) {// If we haven't done too many recalculations and the drawer// is still toggling then recurse.doRecalculation();}}, 100);};// Start the recalculations.doRecalculation(root);});PubSub.subscribe('nav-drawer-toggle-end', function() {drawerToggling = false;});$(window).on('resize', function() {if (!contentLoaded || !allCourses.length) {// Nothing to reclculate.return;}// Resize events fire rapidly so recalculating the visible courses each// time can be expensive. Let's debounce them,if (!resizeTimeout) {resizeTimeout = setTimeout(function() {resizeTimeout = null;recalculateVisibleCourses(root);// The recalculateVisibleCourses function will execute at a rate of 15fps.}, 66);}});CustomEvents.define(root, [CustomEvents.events.activate]);root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_NEXT, function(e, data) {var button = $(e.target).closest(SELECTORS.PAGING_BAR_NEXT);if (!button.hasClass('disabled')) {viewIndex = viewIndex + availableVisibleCards;recalculateVisibleCourses(root);}data.originalEvent.preventDefault();});root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_PREVIOUS, function(e, data) {var button = $(e.target).closest(SELECTORS.PAGING_BAR_PREVIOUS);if (!button.hasClass('disabled')) {viewIndex = viewIndex - availableVisibleCards;viewIndex = viewIndex < 0 ? 0 : viewIndex;recalculateVisibleCourses(root);}data.originalEvent.preventDefault();});};/*** Get and show the recent courses into the block.** @param {int} userid User from which the courses will be obtained* @param {object} root The root element for the recentlyaccessedcourses block.*/var init = function(userid, root) {root = $(root);registerEventListeners(root);loadContent(userid).then(function(renderedCourses) {allCourses = renderedCourses;contentLoaded = true;if (allCourses.length) {showContent(root);recalculateVisibleCourses(root);} else {showEmptyMessage(root);}return;}).catch(Notification.exception);};return {init: init};});