diff --git a/blocks/timeline/amd/build/view_courses.min.js b/blocks/timeline/amd/build/view_courses.min.js index 06effd8cfa9..11367597895 100644 --- a/blocks/timeline/amd/build/view_courses.min.js +++ b/blocks/timeline/amd/build/view_courses.min.js @@ -4,6 +4,6 @@ * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository","core/pending"],(function($,Notification,CustomEvents,Templates,EventList,CourseRepository,EventsRepository,Pending){var SELECTORS_MORE_COURSES_BUTTON='[data-action="more-courses"]',SELECTORS_MORE_COURSES_BUTTON_CONTAINER='[data-region="more-courses-button-container"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE='[data-region="no-events-empty-message"]',SELECTORS_COURSES_LIST='[data-region="courses-list"]',SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER='[data-region="course-items-loading-placeholder"]',SELECTORS_LOADING_ICON=".loading-icon",SELECTORS_TIMELINE_BLOCK='[data-region="timeline"]',SELECTORS_TIMELINE_SEARCH='[data-action="search"]',TEMPLATES_COURSE_ITEMS="block_timeline/course-items",TEMPLATES_LOADING_ICON="core/loading";const additionalConfig={courseview:!0};var hideLoadingPlaceholder=function(root){root.find(SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")};var hideMoreCoursesButton=function(root){root.find(SELECTORS_MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},showMoreCoursesButton=function(root){root.find(SELECTORS_MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},disableMoreCoursesButtonLoading=function(root){var button=root.find(SELECTORS_MORE_COURSES_BUTTON);button.prop("disabled",!1),button.find(SELECTORS_LOADING_ICON).remove()};const showNoCoursesWithEventsMessage=function(root){const container=root.find(SELECTORS_COURSES_LIST);Templates.replaceNodeContents(container,"",""),root.find(SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE).removeClass("hidden")};var getOffset=function(root){return parseInt(root.attr("data-offset"),10)},setOffset=function(root,offset){root.attr("data-offset",offset)},getLimit=function(root){return parseInt(root.attr("data-limit"),10)},getDaysOffset=function(root){return parseInt(root.attr("data-days-offset"),10)},getDaysLimit=function(root){var daysLimit=root.attr("data-days-limit");return null!=daysLimit?parseInt(daysLimit,10):void 0},getMidnight=function(root){return parseInt(root.attr("data-midnight"),10)},getStartTime=function(root){return getMidnight(root)+86400*getDaysOffset(root)},getEndTime=function(root){let endTime=null;if(root.attr("data-filter-overdue"))endTime=Math.floor(Date.now()/1e3);else{const midnight=getMidnight(root),daysLimit=getDaysLimit(root);null!=daysLimit&&(endTime=midnight+86400*daysLimit)}return endTime},hasReloadedEventsSince=function(root,time){return function(root){return root.data("last-event-load-time")}(root)>time},loadEventsForCourses=function(courses,startTime,endTime,searchValue){return function(courseIds,startTime,limit,endTime,searchValue){var args={courseids:courseIds,starttime:startTime,limit:limit};return endTime&&(args.endtime=endTime),searchValue&&(args.searchvalue=searchValue),EventsRepository.queryByCourses(args)}(courses.map((function(course){return course.id})),startTime,6,endTime,searchValue)},updateDisplayFromCourses=function(courses,root,midnight,daysOffset,daysLimit,append){return Templates.render(TEMPLATES_COURSE_ITEMS,{courses:courses,midnight:midnight,hasdaysoffset:!0,hasdayslimit:null!=daysLimit,daysoffset:daysOffset,dayslimit:daysLimit,nodayslimit:null==daysLimit,courseview:!0,hascourses:!0}).then((function(html){return hideLoadingPlaceholder(root),html&&function(root,html){let append=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var container=root.find(SELECTORS_COURSES_LIST);append?Templates.appendNodeContents(container,html,""):Templates.replaceNodeContents(container,html,"")}(root,html,append),html})).then((function(html){return courses.length<2?hideMoreCoursesButton(root):showMoreCoursesButton(root),html})).catch((function(){hideLoadingPlaceholder(root)}))},loadMoreCourses=function(root){let append=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const pendingPromise=new Pending("block/timeline:load-more-courses");var offset=getOffset(root),limit=getLimit(root);const startTime=getStartTime(root),endTime=getEndTime(root),searchValue=root.closest(SELECTORS_TIMELINE_BLOCK).find(SELECTORS_TIMELINE_SEARCH).val();return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification("inprogress",limit,offset,"fullname asc",searchValue,startTime,endTime).then((function(result){var startEventLoadingTime=Date.now(),courses=result.courses,nextOffset=result.nextoffset,daysOffset=getDaysOffset(root),daysLimit=getDaysLimit(root),midnight=getMidnight(root);const moreCoursesAvailable=result.morecoursesavailable;setOffset(root,nextOffset);var eventsPromise=loadEventsForCourses(courses,startTime,endTime,searchValue),renderPromise=updateDisplayFromCourses(courses,root,midnight,daysOffset,daysLimit,append);return $.when(eventsPromise,renderPromise).then((function(eventsByCourse){return hasReloadedEventsSince(root,startEventLoadingTime)||(courses.length>0?(courses.forEach((function(course){const containerSelector='[data-region="course-events-container"][data-course-id="'+course.id+'"]',eventListRoot=root.find(containerSelector).find(EventList.rootSelector);EventList.init(eventListRoot,additionalConfig)})),moreCoursesAvailable?showMoreCoursesButton(root):hideMoreCoursesButton(root)):(hideMoreCoursesButton(root),0==offset&&showNoCoursesWithEventsMessage(root))),eventsByCourse}))})).then((()=>pendingPromise.resolve())).catch(Notification.exception)},registerEventListeners=function(root){CustomEvents.define(root,[CustomEvents.events.activate]),root.on(CustomEvents.events.activate,SELECTORS_MORE_COURSES_BUTTON,(function(e,data){!function(root){var button=root.find(SELECTORS_MORE_COURSES_BUTTON);button.prop("disabled",!0),Templates.render(TEMPLATES_LOADING_ICON,{}).then((function(html){return button.append(html),html})).catch((function(){return!1}))}(root),loadMoreCourses(root,!0).then((function(){disableMoreCoursesButtonLoading(root)})).catch((function(){disableMoreCoursesButtonLoading(root)})),data&&(data.originalEvent.preventDefault(),data.originalEvent.stopPropagation()),e.stopPropagation()}))},shown=function(root){root.attr("data-seen")||root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(loadMoreCourses(root),root.attr("data-seen",!0))};return{init:function(root){(root=$(root)).find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(!function(root,time){root.data("last-event-load-time",time)}(root,Date.now()),root.hasClass("active")&&(loadMoreCourses(root),root.attr("data-seen",!0)),registerEventListeners(root))},reset:function(root){setOffset(root,0),function(root){root.find(SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass("hidden")}(root),function(root){root.find(SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE).addClass("hidden")}(root),root.removeAttr("data-seen"),root.hasClass("active")&&shown(root)},shown:shown}})); +define("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository","core/pending"],(function($,Notification,CustomEvents,Templates,EventList,CourseRepository,EventsRepository,Pending){var SELECTORS_MORE_COURSES_BUTTON='[data-action="more-courses"]',SELECTORS_MORE_COURSES_BUTTON_CONTAINER='[data-region="more-courses-button-container"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE='[data-region="no-events-empty-message"]',SELECTORS_COURSES_LIST='[data-region="courses-list"]',SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER='[data-region="course-items-loading-placeholder"]',SELECTORS_LOADING_ICON=".loading-icon",SELECTORS_TIMELINE_BLOCK='[data-region="timeline"]',SELECTORS_TIMELINE_SEARCH='[data-action="search"]',TEMPLATES_COURSE_ITEMS="block_timeline/course-items",TEMPLATES_LOADING_ICON="core/loading";const additionalConfig={courseview:!0};var hideLoadingPlaceholder=function(root){root.find(SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")};var hideMoreCoursesButton=function(root){root.find(SELECTORS_MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},showMoreCoursesButton=function(root){root.find(SELECTORS_MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},disableMoreCoursesButtonLoading=function(root){var button=root.find(SELECTORS_MORE_COURSES_BUTTON);button.prop("disabled",!1),button.find(SELECTORS_LOADING_ICON).remove()};const showNoCoursesWithEventsMessage=function(root){const container=root.find(SELECTORS_COURSES_LIST);Templates.replaceNodeContents(container,"",""),root.find(SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE).removeClass("hidden")};var getOffset=function(root){return parseInt(root.attr("data-offset"),10)},setOffset=function(root,offset){root.attr("data-offset",offset)},getLimit=function(root){return parseInt(root.attr("data-limit"),10)},getDaysOffset=function(root){return parseInt(root.attr("data-days-offset"),10)},getDaysLimit=function(root){var daysLimit=root.attr("data-days-limit");return null!=daysLimit?parseInt(daysLimit,10):void 0},getMidnight=function(root){return parseInt(root.attr("data-midnight"),10)},getStartTime=function(root){return getMidnight(root)+86400*getDaysOffset(root)},getEndTime=function(root){let endTime=null;if(root.attr("data-filter-overdue"))endTime=Math.floor(Date.now()/1e3);else{const midnight=getMidnight(root),daysLimit=getDaysLimit(root);null!=daysLimit&&(endTime=midnight+86400*daysLimit)}return endTime},hasReloadedEventsSince=function(root,time){return function(root){return root.data("last-event-load-time")}(root)>time},loadEventsForCourses=function(courses,startTime,endTime,searchValue){return function(courseIds,startTime,limit,endTime,searchValue){var args={courseids:courseIds,starttime:startTime,limit:limit};return endTime&&(args.endtime=endTime),searchValue&&(args.searchvalue=searchValue),EventsRepository.queryByCourses(args)}(courses.map((function(course){return course.id})),startTime,6,endTime,searchValue)},updateDisplayFromCourses=function(courses,root,midnight,daysOffset,daysLimit,append){return Templates.render(TEMPLATES_COURSE_ITEMS,{courses:courses,midnight:midnight,hasdaysoffset:!0,hasdayslimit:null!=daysLimit,daysoffset:daysOffset,dayslimit:daysLimit,nodayslimit:null==daysLimit,courseview:!0,hascourses:!0}).then((function(html){return hideLoadingPlaceholder(root),html&&function(root,html){let append=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var container=root.find(SELECTORS_COURSES_LIST);append?Templates.appendNodeContents(container,html,""):Templates.replaceNodeContents(container,html,"")}(root,html,append),html})).then((function(html){return courses.length<2?hideMoreCoursesButton(root):showMoreCoursesButton(root),html})).catch((function(){hideLoadingPlaceholder(root)}))},loadMoreCourses=function(root){let append=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const pendingPromise=new Pending("block/timeline:load-more-courses");var offset=getOffset(root),limit=getLimit(root);const startTime=getStartTime(root),endTime=getEndTime(root),searchValue=root.closest(SELECTORS_TIMELINE_BLOCK).find(SELECTORS_TIMELINE_SEARCH).val();return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification("all",limit,offset,"fullname asc",searchValue,startTime,endTime).then((function(result){var startEventLoadingTime=Date.now(),courses=result.courses,nextOffset=result.nextoffset,daysOffset=getDaysOffset(root),daysLimit=getDaysLimit(root),midnight=getMidnight(root);const moreCoursesAvailable=result.morecoursesavailable;setOffset(root,nextOffset);var eventsPromise=loadEventsForCourses(courses,startTime,endTime,searchValue),renderPromise=updateDisplayFromCourses(courses,root,midnight,daysOffset,daysLimit,append);return $.when(eventsPromise,renderPromise).then((function(eventsByCourse){return hasReloadedEventsSince(root,startEventLoadingTime)||(courses.length>0?(courses.forEach((function(course){const containerSelector='[data-region="course-events-container"][data-course-id="'+course.id+'"]',eventListRoot=root.find(containerSelector).find(EventList.rootSelector);EventList.init(eventListRoot,additionalConfig)})),moreCoursesAvailable?showMoreCoursesButton(root):hideMoreCoursesButton(root)):(hideMoreCoursesButton(root),0==offset&&showNoCoursesWithEventsMessage(root))),eventsByCourse}))})).then((()=>pendingPromise.resolve())).catch(Notification.exception)},registerEventListeners=function(root){CustomEvents.define(root,[CustomEvents.events.activate]),root.on(CustomEvents.events.activate,SELECTORS_MORE_COURSES_BUTTON,(function(e,data){!function(root){var button=root.find(SELECTORS_MORE_COURSES_BUTTON);button.prop("disabled",!0),Templates.render(TEMPLATES_LOADING_ICON,{}).then((function(html){return button.append(html),html})).catch((function(){return!1}))}(root),loadMoreCourses(root,!0).then((function(){disableMoreCoursesButtonLoading(root)})).catch((function(){disableMoreCoursesButtonLoading(root)})),data&&(data.originalEvent.preventDefault(),data.originalEvent.stopPropagation()),e.stopPropagation()}))},shown=function(root){root.attr("data-seen")||root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(loadMoreCourses(root),root.attr("data-seen",!0))};return{init:function(root){(root=$(root)).find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(!function(root,time){root.data("last-event-load-time",time)}(root,Date.now()),root.hasClass("active")&&(loadMoreCourses(root),root.attr("data-seen",!0)),registerEventListeners(root))},reset:function(root){setOffset(root,0),function(root){root.find(SELECTORS_COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass("hidden")}(root),function(root){root.find(SELECTORS_NO_COURSES_WITH_EVENTS_MESSAGE).addClass("hidden")}(root),root.removeAttr("data-seen"),root.hasClass("active")&&shown(root)},shown:shown}})); //# sourceMappingURL=view_courses.min.js.map \ No newline at end of file diff --git a/blocks/timeline/amd/build/view_courses.min.js.map b/blocks/timeline/amd/build/view_courses.min.js.map index 495db72018d..7e81f9914dd 100644 --- a/blocks/timeline/amd/build/view_courses.min.js.map +++ b/blocks/timeline/amd/build/view_courses.min.js.map @@ -1 +1 @@ -{"version":3,"file":"view_courses.min.js","sources":["../src/view_courses.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline courses view for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/custom_interaction_events',\n 'core/templates',\n 'block_timeline/event_list',\n 'core_course/repository',\n 'block_timeline/calendar_events_repository',\n 'core/pending'\n],\nfunction(\n $,\n Notification,\n CustomEvents,\n Templates,\n EventList,\n CourseRepository,\n EventsRepository,\n Pending\n) {\n\n var SELECTORS = {\n MORE_COURSES_BUTTON: '[data-action=\"more-courses\"]',\n MORE_COURSES_BUTTON_CONTAINER: '[data-region=\"more-courses-button-container\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region=\"no-events-empty-message\"]',\n COURSES_LIST: '[data-region=\"courses-list\"]',\n COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region=\"course-items-loading-placeholder\"]',\n COURSE_EVENTS_CONTAINER: '[data-region=\"course-events-container\"]',\n COURSE_NAME: '[data-region=\"course-name\"]',\n LOADING_ICON: '.loading-icon',\n TIMELINE_BLOCK: '[data-region=\"timeline\"]',\n TIMELINE_SEARCH: '[data-action=\"search\"]'\n };\n\n var TEMPLATES = {\n COURSE_ITEMS: 'block_timeline/course-items',\n LOADING_ICON: 'core/loading'\n };\n\n var COURSE_CLASSIFICATION = 'inprogress';\n var COURSE_SORT = 'fullname asc';\n var COURSE_EVENT_LIMIT = 5;\n var COURSE_LIMIT = 2;\n var SECONDS_IN_DAY = 60 * 60 * 24;\n\n const additionalConfig = {courseview: true};\n\n /**\n * Hide the loading placeholder elements.\n *\n * @param {object} root The rool element.\n */\n var hideLoadingPlaceholder = function(root) {\n root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the loading placeholder elements.\n *\n * @param {object} root The rool element.\n */\n const showLoadingPlaceholder = function(root) {\n root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden');\n };\n\n /**\n * Hide the \"more courses\" button.\n *\n * @param {object} root The rool element.\n */\n var hideMoreCoursesButton = function(root) {\n root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the \"more courses\" button.\n *\n * @param {object} root The rool element.\n */\n var showMoreCoursesButton = function(root) {\n root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Disable the \"more courses\" button and show the loading spinner.\n *\n * @param {object} root The rool element.\n */\n var enableMoreCoursesButtonLoading = function(root) {\n var button = root.find(SELECTORS.MORE_COURSES_BUTTON);\n button.prop('disabled', true);\n Templates.render(TEMPLATES.LOADING_ICON, {})\n .then(function(html) {\n button.append(html);\n return html;\n })\n .catch(function() {\n // It's not important if this false so just do so silently.\n return false;\n });\n };\n\n /**\n * Enable the \"more courses\" button and remove the loading spinner.\n *\n * @param {object} root The rool element.\n */\n var disableMoreCoursesButtonLoading = function(root) {\n var button = root.find(SELECTORS.MORE_COURSES_BUTTON);\n button.prop('disabled', false);\n button.find(SELECTORS.LOADING_ICON).remove();\n };\n\n /**\n * Display the message for when courses have no events available (within the current filtering).\n *\n * @param {object} root The rool element.\n */\n const showNoCoursesWithEventsMessage = function(root) {\n // Remove any course list contents, since we will display the no events message.\n const container = root.find(SELECTORS.COURSES_LIST);\n Templates.replaceNodeContents(container, '', '');\n root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Hide the message for when courses have no events available (within the current filtering).\n *\n * @param {object} root The rool element.\n */\n const hideNoCoursesWithEventsMessage = function(root) {\n root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden');\n };\n\n /**\n * Render the course items HTML to the page.\n *\n * @param {object} root The rool element.\n * @param {string} html The course items HTML to render.\n * @param {boolean} append Whether the HTML should be appended (eg pressed \"show more courses\").\n * Defaults to false - replaces the existing content (eg when modifying filter values).\n */\n var renderCourseItemsHTML = function(root, html, append = false) {\n var container = root.find(SELECTORS.COURSES_LIST);\n\n if (append) {\n Templates.appendNodeContents(container, html, '');\n } else {\n Templates.replaceNodeContents(container, html, '');\n }\n };\n\n /**\n * Return the offset value for fetching courses.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getOffset = function(root) {\n return parseInt(root.attr('data-offset'), 10);\n };\n\n /**\n * Set the offset value for fetching courses.\n *\n * @param {object} root The rool element.\n * @param {Number} offset Offset value.\n */\n var setOffset = function(root, offset) {\n root.attr('data-offset', offset);\n };\n\n /**\n * Return the limit value for fetching courses.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getLimit = function(root) {\n return parseInt(root.attr('data-limit'), 10);\n };\n\n /**\n * Return the days offset value for fetching events.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getDaysOffset = function(root) {\n return parseInt(root.attr('data-days-offset'), 10);\n };\n\n /**\n * Return the days limit value for fetching events. The days\n * limit is optional so undefined will be returned if it isn't\n * set.\n *\n * @param {object} root The rool element.\n * @return {int|undefined}\n */\n var getDaysLimit = function(root) {\n var daysLimit = root.attr('data-days-limit');\n return daysLimit != undefined ? parseInt(daysLimit, 10) : undefined;\n };\n\n /**\n * Return the timestamp for the user's midnight.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getMidnight = function(root) {\n return parseInt(root.attr('data-midnight'), 10);\n };\n\n /**\n * Return the start time for fetching events. This is calculated\n * based on the user's midnight value so that timezones are\n * preserved.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getStartTime = function(root) {\n var midnight = getMidnight(root);\n var daysOffset = getDaysOffset(root);\n return midnight + (daysOffset * SECONDS_IN_DAY);\n };\n\n /**\n * Return the end time for fetching events. This is calculated\n * based on the user's midnight value so that timezones are\n * preserved, unless filtering by overdue, where the current UNIX timestamp is used.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEndTime = function(root) {\n let endTime = null;\n\n if (root.attr('data-filter-overdue')) {\n // If filtering by overdue, end time will be the current timestamp in seconds.\n endTime = Math.floor(Date.now() / 1000);\n } else {\n const midnight = getMidnight(root);\n const daysLimit = getDaysLimit(root);\n\n if (daysLimit != undefined) {\n endTime = midnight + (daysLimit * SECONDS_IN_DAY);\n }\n }\n\n return endTime;\n };\n\n /**\n * Get a list of events for the given course ids. Returns a promise that will\n * be resolved with the events.\n *\n * @param {array} courseIds The list of course ids to fetch events for.\n * @param {Number} startTime Timestamp to fetch events from.\n * @param {Number} limit Limit to the number of events (this applies per course, not total)\n * @param {Number} endTime Timestamp to fetch events to.\n * @param {string|undefined} searchValue Search value\n * @return {object} jQuery promise.\n */\n var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) {\n var args = {\n courseids: courseIds,\n starttime: startTime,\n limit: limit\n };\n\n if (endTime) {\n args.endtime = endTime;\n }\n\n if (searchValue) {\n args.searchvalue = searchValue;\n }\n\n return EventsRepository.queryByCourses(args);\n };\n\n /**\n * Get the last time the events were reloaded.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEventReloadTime = function(root) {\n return root.data('last-event-load-time');\n };\n\n /**\n * Set the last time the events were reloaded.\n *\n * @param {object} root The rool element.\n * @param {Number} time Timestamp in milliseconds.\n */\n var setEventReloadTime = function(root, time) {\n root.data('last-event-load-time', time);\n };\n\n /**\n * Check if events have begun reloading since the given\n * time.\n *\n * @param {object} root The rool element.\n * @param {Number} time Timestamp in milliseconds.\n * @return {bool}\n */\n var hasReloadedEventsSince = function(root, time) {\n return getEventReloadTime(root) > time;\n };\n\n /**\n * Send a request to the server to load the events for the courses.\n *\n * @param {array} courses List of course objects.\n * @param {Number} startTime Timestamp to load events after.\n * @param {int|undefined} endTime Timestamp to load events up until.\n * @param {string|undefined} searchValue Search value\n * @return {object} jQuery promise resolved with the events.\n */\n var loadEventsForCourses = function(courses, startTime, endTime, searchValue) {\n var courseIds = courses.map(function(course) {\n return course.id;\n });\n\n return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue);\n };\n\n /**\n * Render the courses in the DOM once the server has returned the courses.\n *\n * @param {array} courses List of course objects.\n * @param {object} root The root element\n * @param {Number} midnight The midnight timestamp in the user's timezone.\n * @param {Number} daysOffset Number of days from today to offset the events.\n * @param {Number} daysLimit Number of days from today to limit the events to.\n * @param {boolean} append Whether new content should be appended instead of replaced (eg \"show more courses\").\n * @return {object} jQuery promise resolved after rendering is complete.\n */\n var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) {\n // Render the courses template.\n return Templates.render(TEMPLATES.COURSE_ITEMS, {\n courses: courses,\n midnight: midnight,\n hasdaysoffset: true,\n hasdayslimit: daysLimit != undefined,\n daysoffset: daysOffset,\n dayslimit: daysLimit,\n nodayslimit: daysLimit == undefined,\n courseview: true,\n hascourses: true\n }).then(function(html) {\n hideLoadingPlaceholder(root);\n\n if (html) {\n // Template rendering is complete and we have the HTML so we can\n // add it to the DOM.\n renderCourseItemsHTML(root, html, append);\n }\n\n return html;\n })\n .then(function(html) {\n if (courses.length < COURSE_LIMIT) {\n // We know there aren't any more courses because we got back less\n // than we asked for so hide the button to request more.\n hideMoreCoursesButton(root);\n } else {\n // Make sure the button is visible if there are more courses to load.\n showMoreCoursesButton(root);\n }\n\n return html;\n })\n .catch(function() {\n hideLoadingPlaceholder(root);\n });\n };\n\n /**\n * Find all of the visible course blocks and initialise the event\n * list module to being loading the events for the course block.\n *\n * @param {object} root The root element for the timeline courses view.\n * @param {boolean} append Whether content should be appended instead of replaced (eg \"show more courses\"). False by default.\n * @return {object} jQuery promise resolved with courses and events.\n */\n var loadMoreCourses = function(root, append = false) {\n const pendingPromise = new Pending('block/timeline:load-more-courses');\n var offset = getOffset(root);\n var limit = getLimit(root);\n const startTime = getStartTime(root);\n const endTime = getEndTime(root);\n const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();\n\n // Start loading the next set of courses.\n // Fetch up to limit number of courses with at least one action event in the time filtering specified.\n // Courses without events will also be fetched, but hidden in case they have events in other timespans.\n return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification(\n COURSE_CLASSIFICATION,\n limit,\n offset,\n COURSE_SORT,\n searchValue,\n startTime,\n endTime\n ).then(function(result) {\n var startEventLoadingTime = Date.now();\n var courses = result.courses;\n var nextOffset = result.nextoffset;\n var daysOffset = getDaysOffset(root);\n var daysLimit = getDaysLimit(root);\n var midnight = getMidnight(root);\n const moreCoursesAvailable = result.morecoursesavailable;\n\n // Record the next offset if we want to request more courses.\n setOffset(root, nextOffset);\n // Load the events for these courses.\n var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);\n // Render the courses in the DOM.\n var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append);\n\n return $.when(eventsPromise, renderPromise)\n .then(function(eventsByCourse) {\n if (hasReloadedEventsSince(root, startEventLoadingTime)) {\n // All of the events are being reloaded so ignore our results.\n return eventsByCourse;\n }\n\n if (courses.length > 0) {\n // Render the events in the correct course event list.\n courses.forEach(function(course) {\n const courseId = course.id;\n const containerSelector = '[data-region=\"course-events-container\"][data-course-id=\"' + courseId + '\"]';\n const courseEventsContainer = root.find(containerSelector);\n const eventListRoot = courseEventsContainer.find(EventList.rootSelector);\n\n EventList.init(eventListRoot, additionalConfig);\n });\n\n if (!moreCoursesAvailable) {\n // If no more courses with events matching the current filtering exist, hide the more courses button.\n hideMoreCoursesButton(root);\n } else {\n // If more courses exist with events matching the current filtering, show the more courses button.\n showMoreCoursesButton(root);\n }\n } else {\n // No more courses to load, hide the more courses button.\n hideMoreCoursesButton(root);\n\n // A zero offset means this was not loading \"more courses\", so we need to display the no results message.\n if (offset == 0) {\n showNoCoursesWithEventsMessage(root);\n }\n }\n\n return eventsByCourse;\n });\n }).then(() => {\n return pendingPromise.resolve();\n }).catch(Notification.exception);\n };\n\n /**\n * Add event listeners to load more courses for the courses view.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var registerEventListeners = function(root) {\n CustomEvents.define(root, [CustomEvents.events.activate]);\n // Show more courses and load their events when the user clicks the \"more courses\" button.\n root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) {\n enableMoreCoursesButtonLoading(root);\n loadMoreCourses(root, true)\n .then(function() {\n disableMoreCoursesButtonLoading(root);\n return;\n })\n .catch(function() {\n disableMoreCoursesButtonLoading(root);\n });\n\n if (data) {\n data.originalEvent.preventDefault();\n data.originalEvent.stopPropagation();\n }\n e.stopPropagation();\n });\n };\n\n /**\n * Initialise the timeline courses view. Begin loading the events\n * if this view is active. Add the relevant event listeners.\n *\n * This function should only be called once per page load because it\n * is adding event listeners to the page.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var init = function(root) {\n root = $(root);\n\n // Only need to handle course loading if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n setEventReloadTime(root, Date.now());\n\n if (root.hasClass('active')) {\n // Only load if this is active otherwise it will be lazy loaded later.\n loadMoreCourses(root);\n root.attr('data-seen', true);\n }\n\n registerEventListeners(root);\n }\n };\n\n /**\n * Reset the element back to it's initial state. Begin loading the events again\n * if this view is active.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var reset = function(root) {\n\n setOffset(root, 0);\n showLoadingPlaceholder(root);\n hideNoCoursesWithEventsMessage(root);\n root.removeAttr('data-seen');\n\n if (root.hasClass('active')) {\n shown(root);\n }\n };\n\n /**\n * Begin loading the events unless we know there are no actively enrolled courses.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var shown = function(root) {\n if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n loadMoreCourses(root);\n root.attr('data-seen', true);\n }\n };\n\n return {\n init: init,\n reset: reset,\n shown: shown\n };\n});\n"],"names":["define","$","Notification","CustomEvents","Templates","EventList","CourseRepository","EventsRepository","Pending","SELECTORS","TEMPLATES","additionalConfig","courseview","hideLoadingPlaceholder","root","find","addClass","hideMoreCoursesButton","showMoreCoursesButton","removeClass","disableMoreCoursesButtonLoading","button","prop","remove","showNoCoursesWithEventsMessage","container","replaceNodeContents","getOffset","parseInt","attr","setOffset","offset","getLimit","getDaysOffset","getDaysLimit","daysLimit","undefined","getMidnight","getStartTime","getEndTime","endTime","Math","floor","Date","now","midnight","hasReloadedEventsSince","time","data","getEventReloadTime","loadEventsForCourses","courses","startTime","searchValue","courseIds","limit","args","courseids","starttime","endtime","searchvalue","queryByCourses","getEventsForCourseIds","map","course","id","COURSE_EVENT_LIMIT","updateDisplayFromCourses","daysOffset","append","render","hasdaysoffset","hasdayslimit","daysoffset","dayslimit","nodayslimit","hascourses","then","html","appendNodeContents","renderCourseItemsHTML","length","catch","loadMoreCourses","pendingPromise","closest","val","getEnrolledCoursesWithEventsByTimelineClassification","result","startEventLoadingTime","nextOffset","nextoffset","moreCoursesAvailable","morecoursesavailable","eventsPromise","renderPromise","when","eventsByCourse","forEach","containerSelector","eventListRoot","rootSelector","init","resolve","exception","registerEventListeners","events","activate","on","e","enableMoreCoursesButtonLoading","originalEvent","preventDefault","stopPropagation","shown","setEventReloadTime","hasClass","reset","showLoadingPlaceholder","hideNoCoursesWithEventsMessage","removeAttr"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"view_courses.min.js","sources":["../src/view_courses.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Manage the timeline courses view for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/custom_interaction_events',\n 'core/templates',\n 'block_timeline/event_list',\n 'core_course/repository',\n 'block_timeline/calendar_events_repository',\n 'core/pending'\n],\nfunction(\n $,\n Notification,\n CustomEvents,\n Templates,\n EventList,\n CourseRepository,\n EventsRepository,\n Pending\n) {\n\n var SELECTORS = {\n MORE_COURSES_BUTTON: '[data-action=\"more-courses\"]',\n MORE_COURSES_BUTTON_CONTAINER: '[data-region=\"more-courses-button-container\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region=\"no-events-empty-message\"]',\n COURSES_LIST: '[data-region=\"courses-list\"]',\n COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region=\"course-items-loading-placeholder\"]',\n COURSE_EVENTS_CONTAINER: '[data-region=\"course-events-container\"]',\n COURSE_NAME: '[data-region=\"course-name\"]',\n LOADING_ICON: '.loading-icon',\n TIMELINE_BLOCK: '[data-region=\"timeline\"]',\n TIMELINE_SEARCH: '[data-action=\"search\"]'\n };\n\n var TEMPLATES = {\n COURSE_ITEMS: 'block_timeline/course-items',\n LOADING_ICON: 'core/loading'\n };\n\n var COURSE_CLASSIFICATION = 'all';\n var COURSE_SORT = 'fullname asc';\n var COURSE_EVENT_LIMIT = 5;\n var COURSE_LIMIT = 2;\n var SECONDS_IN_DAY = 60 * 60 * 24;\n\n const additionalConfig = {courseview: true};\n\n /**\n * Hide the loading placeholder elements.\n *\n * @param {object} root The rool element.\n */\n var hideLoadingPlaceholder = function(root) {\n root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the loading placeholder elements.\n *\n * @param {object} root The rool element.\n */\n const showLoadingPlaceholder = function(root) {\n root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden');\n };\n\n /**\n * Hide the \"more courses\" button.\n *\n * @param {object} root The rool element.\n */\n var hideMoreCoursesButton = function(root) {\n root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the \"more courses\" button.\n *\n * @param {object} root The rool element.\n */\n var showMoreCoursesButton = function(root) {\n root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Disable the \"more courses\" button and show the loading spinner.\n *\n * @param {object} root The rool element.\n */\n var enableMoreCoursesButtonLoading = function(root) {\n var button = root.find(SELECTORS.MORE_COURSES_BUTTON);\n button.prop('disabled', true);\n Templates.render(TEMPLATES.LOADING_ICON, {})\n .then(function(html) {\n button.append(html);\n return html;\n })\n .catch(function() {\n // It's not important if this false so just do so silently.\n return false;\n });\n };\n\n /**\n * Enable the \"more courses\" button and remove the loading spinner.\n *\n * @param {object} root The rool element.\n */\n var disableMoreCoursesButtonLoading = function(root) {\n var button = root.find(SELECTORS.MORE_COURSES_BUTTON);\n button.prop('disabled', false);\n button.find(SELECTORS.LOADING_ICON).remove();\n };\n\n /**\n * Display the message for when courses have no events available (within the current filtering).\n *\n * @param {object} root The rool element.\n */\n const showNoCoursesWithEventsMessage = function(root) {\n // Remove any course list contents, since we will display the no events message.\n const container = root.find(SELECTORS.COURSES_LIST);\n Templates.replaceNodeContents(container, '', '');\n root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Hide the message for when courses have no events available (within the current filtering).\n *\n * @param {object} root The rool element.\n */\n const hideNoCoursesWithEventsMessage = function(root) {\n root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden');\n };\n\n /**\n * Render the course items HTML to the page.\n *\n * @param {object} root The rool element.\n * @param {string} html The course items HTML to render.\n * @param {boolean} append Whether the HTML should be appended (eg pressed \"show more courses\").\n * Defaults to false - replaces the existing content (eg when modifying filter values).\n */\n var renderCourseItemsHTML = function(root, html, append = false) {\n var container = root.find(SELECTORS.COURSES_LIST);\n\n if (append) {\n Templates.appendNodeContents(container, html, '');\n } else {\n Templates.replaceNodeContents(container, html, '');\n }\n };\n\n /**\n * Return the offset value for fetching courses.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getOffset = function(root) {\n return parseInt(root.attr('data-offset'), 10);\n };\n\n /**\n * Set the offset value for fetching courses.\n *\n * @param {object} root The rool element.\n * @param {Number} offset Offset value.\n */\n var setOffset = function(root, offset) {\n root.attr('data-offset', offset);\n };\n\n /**\n * Return the limit value for fetching courses.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getLimit = function(root) {\n return parseInt(root.attr('data-limit'), 10);\n };\n\n /**\n * Return the days offset value for fetching events.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getDaysOffset = function(root) {\n return parseInt(root.attr('data-days-offset'), 10);\n };\n\n /**\n * Return the days limit value for fetching events. The days\n * limit is optional so undefined will be returned if it isn't\n * set.\n *\n * @param {object} root The rool element.\n * @return {int|undefined}\n */\n var getDaysLimit = function(root) {\n var daysLimit = root.attr('data-days-limit');\n return daysLimit != undefined ? parseInt(daysLimit, 10) : undefined;\n };\n\n /**\n * Return the timestamp for the user's midnight.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getMidnight = function(root) {\n return parseInt(root.attr('data-midnight'), 10);\n };\n\n /**\n * Return the start time for fetching events. This is calculated\n * based on the user's midnight value so that timezones are\n * preserved.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getStartTime = function(root) {\n var midnight = getMidnight(root);\n var daysOffset = getDaysOffset(root);\n return midnight + (daysOffset * SECONDS_IN_DAY);\n };\n\n /**\n * Return the end time for fetching events. This is calculated\n * based on the user's midnight value so that timezones are\n * preserved, unless filtering by overdue, where the current UNIX timestamp is used.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEndTime = function(root) {\n let endTime = null;\n\n if (root.attr('data-filter-overdue')) {\n // If filtering by overdue, end time will be the current timestamp in seconds.\n endTime = Math.floor(Date.now() / 1000);\n } else {\n const midnight = getMidnight(root);\n const daysLimit = getDaysLimit(root);\n\n if (daysLimit != undefined) {\n endTime = midnight + (daysLimit * SECONDS_IN_DAY);\n }\n }\n\n return endTime;\n };\n\n /**\n * Get a list of events for the given course ids. Returns a promise that will\n * be resolved with the events.\n *\n * @param {array} courseIds The list of course ids to fetch events for.\n * @param {Number} startTime Timestamp to fetch events from.\n * @param {Number} limit Limit to the number of events (this applies per course, not total)\n * @param {Number} endTime Timestamp to fetch events to.\n * @param {string|undefined} searchValue Search value\n * @return {object} jQuery promise.\n */\n var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) {\n var args = {\n courseids: courseIds,\n starttime: startTime,\n limit: limit\n };\n\n if (endTime) {\n args.endtime = endTime;\n }\n\n if (searchValue) {\n args.searchvalue = searchValue;\n }\n\n return EventsRepository.queryByCourses(args);\n };\n\n /**\n * Get the last time the events were reloaded.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEventReloadTime = function(root) {\n return root.data('last-event-load-time');\n };\n\n /**\n * Set the last time the events were reloaded.\n *\n * @param {object} root The rool element.\n * @param {Number} time Timestamp in milliseconds.\n */\n var setEventReloadTime = function(root, time) {\n root.data('last-event-load-time', time);\n };\n\n /**\n * Check if events have begun reloading since the given\n * time.\n *\n * @param {object} root The rool element.\n * @param {Number} time Timestamp in milliseconds.\n * @return {bool}\n */\n var hasReloadedEventsSince = function(root, time) {\n return getEventReloadTime(root) > time;\n };\n\n /**\n * Send a request to the server to load the events for the courses.\n *\n * @param {array} courses List of course objects.\n * @param {Number} startTime Timestamp to load events after.\n * @param {int|undefined} endTime Timestamp to load events up until.\n * @param {string|undefined} searchValue Search value\n * @return {object} jQuery promise resolved with the events.\n */\n var loadEventsForCourses = function(courses, startTime, endTime, searchValue) {\n var courseIds = courses.map(function(course) {\n return course.id;\n });\n\n return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue);\n };\n\n /**\n * Render the courses in the DOM once the server has returned the courses.\n *\n * @param {array} courses List of course objects.\n * @param {object} root The root element\n * @param {Number} midnight The midnight timestamp in the user's timezone.\n * @param {Number} daysOffset Number of days from today to offset the events.\n * @param {Number} daysLimit Number of days from today to limit the events to.\n * @param {boolean} append Whether new content should be appended instead of replaced (eg \"show more courses\").\n * @return {object} jQuery promise resolved after rendering is complete.\n */\n var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) {\n // Render the courses template.\n return Templates.render(TEMPLATES.COURSE_ITEMS, {\n courses: courses,\n midnight: midnight,\n hasdaysoffset: true,\n hasdayslimit: daysLimit != undefined,\n daysoffset: daysOffset,\n dayslimit: daysLimit,\n nodayslimit: daysLimit == undefined,\n courseview: true,\n hascourses: true\n }).then(function(html) {\n hideLoadingPlaceholder(root);\n\n if (html) {\n // Template rendering is complete and we have the HTML so we can\n // add it to the DOM.\n renderCourseItemsHTML(root, html, append);\n }\n\n return html;\n })\n .then(function(html) {\n if (courses.length < COURSE_LIMIT) {\n // We know there aren't any more courses because we got back less\n // than we asked for so hide the button to request more.\n hideMoreCoursesButton(root);\n } else {\n // Make sure the button is visible if there are more courses to load.\n showMoreCoursesButton(root);\n }\n\n return html;\n })\n .catch(function() {\n hideLoadingPlaceholder(root);\n });\n };\n\n /**\n * Find all of the visible course blocks and initialise the event\n * list module to being loading the events for the course block.\n *\n * @param {object} root The root element for the timeline courses view.\n * @param {boolean} append Whether content should be appended instead of replaced (eg \"show more courses\"). False by default.\n * @return {object} jQuery promise resolved with courses and events.\n */\n var loadMoreCourses = function(root, append = false) {\n const pendingPromise = new Pending('block/timeline:load-more-courses');\n var offset = getOffset(root);\n var limit = getLimit(root);\n const startTime = getStartTime(root);\n const endTime = getEndTime(root);\n const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();\n\n // Start loading the next set of courses.\n // Fetch up to limit number of courses with at least one action event in the time filtering specified.\n // Courses without events will also be fetched, but hidden in case they have events in other timespans.\n return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification(\n COURSE_CLASSIFICATION,\n limit,\n offset,\n COURSE_SORT,\n searchValue,\n startTime,\n endTime\n ).then(function(result) {\n var startEventLoadingTime = Date.now();\n var courses = result.courses;\n var nextOffset = result.nextoffset;\n var daysOffset = getDaysOffset(root);\n var daysLimit = getDaysLimit(root);\n var midnight = getMidnight(root);\n const moreCoursesAvailable = result.morecoursesavailable;\n\n // Record the next offset if we want to request more courses.\n setOffset(root, nextOffset);\n // Load the events for these courses.\n var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);\n // Render the courses in the DOM.\n var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append);\n\n return $.when(eventsPromise, renderPromise)\n .then(function(eventsByCourse) {\n if (hasReloadedEventsSince(root, startEventLoadingTime)) {\n // All of the events are being reloaded so ignore our results.\n return eventsByCourse;\n }\n\n if (courses.length > 0) {\n // Render the events in the correct course event list.\n courses.forEach(function(course) {\n const courseId = course.id;\n const containerSelector = '[data-region=\"course-events-container\"][data-course-id=\"' + courseId + '\"]';\n const courseEventsContainer = root.find(containerSelector);\n const eventListRoot = courseEventsContainer.find(EventList.rootSelector);\n\n EventList.init(eventListRoot, additionalConfig);\n });\n\n if (!moreCoursesAvailable) {\n // If no more courses with events matching the current filtering exist, hide the more courses button.\n hideMoreCoursesButton(root);\n } else {\n // If more courses exist with events matching the current filtering, show the more courses button.\n showMoreCoursesButton(root);\n }\n } else {\n // No more courses to load, hide the more courses button.\n hideMoreCoursesButton(root);\n\n // A zero offset means this was not loading \"more courses\", so we need to display the no results message.\n if (offset == 0) {\n showNoCoursesWithEventsMessage(root);\n }\n }\n\n return eventsByCourse;\n });\n }).then(() => {\n return pendingPromise.resolve();\n }).catch(Notification.exception);\n };\n\n /**\n * Add event listeners to load more courses for the courses view.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var registerEventListeners = function(root) {\n CustomEvents.define(root, [CustomEvents.events.activate]);\n // Show more courses and load their events when the user clicks the \"more courses\" button.\n root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) {\n enableMoreCoursesButtonLoading(root);\n loadMoreCourses(root, true)\n .then(function() {\n disableMoreCoursesButtonLoading(root);\n return;\n })\n .catch(function() {\n disableMoreCoursesButtonLoading(root);\n });\n\n if (data) {\n data.originalEvent.preventDefault();\n data.originalEvent.stopPropagation();\n }\n e.stopPropagation();\n });\n };\n\n /**\n * Initialise the timeline courses view. Begin loading the events\n * if this view is active. Add the relevant event listeners.\n *\n * This function should only be called once per page load because it\n * is adding event listeners to the page.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var init = function(root) {\n root = $(root);\n\n // Only need to handle course loading if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n setEventReloadTime(root, Date.now());\n\n if (root.hasClass('active')) {\n // Only load if this is active otherwise it will be lazy loaded later.\n loadMoreCourses(root);\n root.attr('data-seen', true);\n }\n\n registerEventListeners(root);\n }\n };\n\n /**\n * Reset the element back to it's initial state. Begin loading the events again\n * if this view is active.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var reset = function(root) {\n\n setOffset(root, 0);\n showLoadingPlaceholder(root);\n hideNoCoursesWithEventsMessage(root);\n root.removeAttr('data-seen');\n\n if (root.hasClass('active')) {\n shown(root);\n }\n };\n\n /**\n * Begin loading the events unless we know there are no actively enrolled courses.\n *\n * @param {object} root The root element for the timeline courses view.\n */\n var shown = function(root) {\n if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n loadMoreCourses(root);\n root.attr('data-seen', true);\n }\n };\n\n return {\n init: init,\n reset: reset,\n shown: shown\n };\n});\n"],"names":["define","$","Notification","CustomEvents","Templates","EventList","CourseRepository","EventsRepository","Pending","SELECTORS","TEMPLATES","additionalConfig","courseview","hideLoadingPlaceholder","root","find","addClass","hideMoreCoursesButton","showMoreCoursesButton","removeClass","disableMoreCoursesButtonLoading","button","prop","remove","showNoCoursesWithEventsMessage","container","replaceNodeContents","getOffset","parseInt","attr","setOffset","offset","getLimit","getDaysOffset","getDaysLimit","daysLimit","undefined","getMidnight","getStartTime","getEndTime","endTime","Math","floor","Date","now","midnight","hasReloadedEventsSince","time","data","getEventReloadTime","loadEventsForCourses","courses","startTime","searchValue","courseIds","limit","args","courseids","starttime","endtime","searchvalue","queryByCourses","getEventsForCourseIds","map","course","id","COURSE_EVENT_LIMIT","updateDisplayFromCourses","daysOffset","append","render","hasdaysoffset","hasdayslimit","daysoffset","dayslimit","nodayslimit","hascourses","then","html","appendNodeContents","renderCourseItemsHTML","length","catch","loadMoreCourses","pendingPromise","closest","val","getEnrolledCoursesWithEventsByTimelineClassification","result","startEventLoadingTime","nextOffset","nextoffset","moreCoursesAvailable","morecoursesavailable","eventsPromise","renderPromise","when","eventsByCourse","forEach","containerSelector","eventListRoot","rootSelector","init","resolve","exception","registerEventListeners","events","activate","on","e","enableMoreCoursesButtonLoading","originalEvent","preventDefault","stopPropagation","shown","setEventReloadTime","hasClass","reset","showLoadingPlaceholder","hideNoCoursesWithEventsMessage","removeAttr"],"mappings":";;;;;;AAsBAA,qCACA,CACI,SACA,oBACA,iCACA,iBACA,4BACA,yBACA,4CACA,iBAEJ,SACIC,EACAC,aACAC,aACAC,UACAC,UACAC,iBACAC,iBACAC,aAGIC,8BACqB,+BADrBA,wCAE+B,gDAF/BA,mCAG0B,2CAH1BA,yCAIgC,0CAJhCA,uBAKc,+BALdA,2CAMkC,mDANlCA,uBASc,gBATdA,yBAUgB,2BAVhBA,0BAWiB,yBAGjBC,uBACc,8BADdA,uBAEc,qBASZC,iBAAmB,CAACC,YAAY,OAOlCC,uBAAyB,SAASC,MAClCA,KAAKC,KAAKN,4CAA4CO,SAAS,eAiB/DC,sBAAwB,SAASH,MACjCA,KAAKC,KAAKN,yCAAyCO,SAAS,WAQ5DE,sBAAwB,SAASJ,MACjCA,KAAKC,KAAKN,yCAAyCU,YAAY,WA2B/DC,gCAAkC,SAASN,UACvCO,OAASP,KAAKC,KAAKN,+BACvBY,OAAOC,KAAK,YAAY,GACxBD,OAAON,KAAKN,wBAAwBc,gBAQlCC,+BAAiC,SAASV,YAEtCW,UAAYX,KAAKC,KAAKN,wBAC5BL,UAAUsB,oBAAoBD,UAAW,GAAI,IAC7CX,KAAKC,KAAKN,0CAA0CU,YAAY,eAoChEQ,UAAY,SAASb,aACdc,SAASd,KAAKe,KAAK,eAAgB,KAS1CC,UAAY,SAAShB,KAAMiB,QAC3BjB,KAAKe,KAAK,cAAeE,SASzBC,SAAW,SAASlB,aACbc,SAASd,KAAKe,KAAK,cAAe,KASzCI,cAAgB,SAASnB,aAClBc,SAASd,KAAKe,KAAK,oBAAqB,KAW/CK,aAAe,SAASpB,UACpBqB,UAAYrB,KAAKe,KAAK,0BACNO,MAAbD,UAAyBP,SAASO,UAAW,SAAMC,GAS1DC,YAAc,SAASvB,aAChBc,SAASd,KAAKe,KAAK,iBAAkB,KAW5CS,aAAe,SAASxB,aACTuB,YAAYvB,MArLV,MAsLAmB,cAAcnB,OAY/ByB,WAAa,SAASzB,UAClB0B,QAAU,QAEV1B,KAAKe,KAAK,uBAEVW,QAAUC,KAAKC,MAAMC,KAAKC,MAAQ,SAC/B,OACGC,SAAWR,YAAYvB,MACvBqB,UAAYD,aAAapB,MAEdsB,MAAbD,YACAK,QAAUK,SA7MD,MA6MaV,kBAIvBK,SA4DPM,uBAAyB,SAAShC,KAAMiC,aAtBnB,SAASjC,aACvBA,KAAKkC,KAAK,wBAsBVC,CAAmBnC,MAAQiC,MAYlCG,qBAAuB,SAASC,QAASC,UAAWZ,QAASa,oBA3DrC,SAASC,UAAWF,UAAWG,MAAOf,QAASa,iBACnEG,KAAO,CACPC,UAAWH,UACXI,UAAWN,UACXG,MAAOA,cAGPf,UACAgB,KAAKG,QAAUnB,SAGfa,cACAG,KAAKI,YAAcP,aAGhB9C,iBAAiBsD,eAAeL,MAiDhCM,CAJSX,QAAQY,KAAI,SAASC,eAC1BA,OAAOC,MAGsBb,UAAWc,EAAwB1B,QAASa,cAcpFc,yBAA2B,SAAShB,QAASrC,KAAM+B,SAAUuB,WAAYjC,UAAWkC,eAE7EjE,UAAUkE,OAAO5D,uBAAwB,CAC5CyC,QAASA,QACTN,SAAUA,SACV0B,eAAe,EACfC,aAA2BpC,MAAbD,UACdsC,WAAYL,WACZM,UAAWvC,UACXwC,YAA0BvC,MAAbD,UACbvB,YAAY,EACZgE,YAAY,IACbC,MAAK,SAASC,aACbjE,uBAAuBC,MAEnBgE,MAzNgB,SAAShE,KAAMgE,UAAMT,mEACzC5C,UAAYX,KAAKC,KAAKN,wBAEtB4D,OACAjE,UAAU2E,mBAAmBtD,UAAWqD,KAAM,IAE9C1E,UAAUsB,oBAAoBD,UAAWqD,KAAM,IAsN3CE,CAAsBlE,KAAMgE,KAAMT,QAG/BS,QAEVD,MAAK,SAASC,aACP3B,QAAQ8B,OAtUD,EAyUPhE,sBAAsBH,MAGtBI,sBAAsBJ,MAGnBgE,QAEVI,OAAM,WACHrE,uBAAuBC,UAY3BqE,gBAAkB,SAASrE,UAAMuD,qEAC3Be,eAAiB,IAAI5E,QAAQ,wCAC/BuB,OAASJ,UAAUb,MACnByC,MAAQvB,SAASlB,YACfsC,UAAYd,aAAaxB,MACzB0B,QAAUD,WAAWzB,MACrBuC,YAAcvC,KAAKuE,QAAQ5E,0BAA0BM,KAAKN,2BAA2B6E,aAKpFhF,iBAAiBiF,qDA5WA,MA8WpBhC,MACAxB,OA9WU,eAgXVsB,YACAD,UACAZ,SACFqC,MAAK,SAASW,YACRC,sBAAwB9C,KAAKC,MAC7BO,QAAUqC,OAAOrC,QACjBuC,WAAaF,OAAOG,WACpBvB,WAAanC,cAAcnB,MAC3BqB,UAAYD,aAAapB,MACzB+B,SAAWR,YAAYvB,YACrB8E,qBAAuBJ,OAAOK,qBAGpC/D,UAAUhB,KAAM4E,gBAEZI,cAAgB5C,qBAAqBC,QAASC,UAAWZ,QAASa,aAElE0C,cAAgB5B,yBAAyBhB,QAASrC,KAAM+B,SAAUuB,WAAYjC,UAAWkC,eAEtFpE,EAAE+F,KAAKF,cAAeC,eACxBlB,MAAK,SAASoB,uBACPnD,uBAAuBhC,KAAM2E,yBAK7BtC,QAAQ8B,OAAS,GAEjB9B,QAAQ+C,SAAQ,SAASlC,cAEfmC,kBAAoB,2DADTnC,OAAOC,GAC0E,KAE5FmC,cADwBtF,KAAKC,KAAKoF,mBACIpF,KAAKV,UAAUgG,cAE3DhG,UAAUiG,KAAKF,cAAezF,qBAG7BiF,qBAKD1E,sBAAsBJ,MAHtBG,sBAAsBH,QAO1BG,sBAAsBH,MAGR,GAAViB,QACAP,+BAA+BV,QA3B5BmF,qBAiCpBpB,MAAK,IACGO,eAAemB,YACvBrB,MAAMhF,aAAasG,YAQtBC,uBAAyB,SAAS3F,MAClCX,aAAaH,OAAOc,KAAM,CAACX,aAAauG,OAAOC,WAE/C7F,KAAK8F,GAAGzG,aAAauG,OAAOC,SAAUlG,+BAA+B,SAASoG,EAAG7D,OArYhD,SAASlC,UACtCO,OAASP,KAAKC,KAAKN,+BACvBY,OAAOC,KAAK,YAAY,GACxBlB,UAAUkE,OAAO5D,uBAAwB,IACpCmE,MAAK,SAASC,aACXzD,OAAOgD,OAAOS,MACPA,QAEVI,OAAM,kBAEI,KA4XX4B,CAA+BhG,MAC/BqE,gBAAgBrE,MAAM,GACjB+D,MAAK,WACFzD,gCAAgCN,SAGnCoE,OAAM,WACH9D,gCAAgCN,SAGpCkC,OACAA,KAAK+D,cAAcC,iBACnBhE,KAAK+D,cAAcE,mBAEvBJ,EAAEI,sBAqDNC,MAAQ,SAASpG,MACZA,KAAKe,KAAK,cAAiBf,KAAKC,KAAKN,oCAAoCwE,SAC1EE,gBAAgBrE,MAChBA,KAAKe,KAAK,aAAa,WAIxB,CACHyE,KAhDO,SAASxF,OAChBA,KAAOb,EAAEa,OAGCC,KAAKN,oCAAoCwE,UAjN9B,SAASnE,KAAMiC,MACpCjC,KAAKkC,KAAK,uBAAwBD,MAiN9BoE,CAAmBrG,KAAM6B,KAAKC,OAE1B9B,KAAKsG,SAAS,YAEdjC,gBAAgBrE,MAChBA,KAAKe,KAAK,aAAa,IAG3B4E,uBAAuB3F,QAoC3BuG,MA1BQ,SAASvG,MAEjBgB,UAAUhB,KAAM,GArdW,SAASA,MACpCA,KAAKC,KAAKN,4CAA4CU,YAAY,UAqdlEmG,CAAuBxG,MAjZY,SAASA,MAC5CA,KAAKC,KAAKN,0CAA0CO,SAAS,UAiZ7DuG,CAA+BzG,MAC/BA,KAAK0G,WAAW,aAEZ1G,KAAKsG,SAAS,WACdF,MAAMpG,OAmBVoG,MAAOA"} \ No newline at end of file diff --git a/blocks/timeline/amd/src/view_courses.js b/blocks/timeline/amd/src/view_courses.js index d627c1ce734..985666711e2 100644 --- a/blocks/timeline/amd/src/view_courses.js +++ b/blocks/timeline/amd/src/view_courses.js @@ -61,7 +61,7 @@ function( LOADING_ICON: 'core/loading' }; - var COURSE_CLASSIFICATION = 'inprogress'; + var COURSE_CLASSIFICATION = 'all'; var COURSE_SORT = 'fullname asc'; var COURSE_EVENT_LIMIT = 5; var COURSE_LIMIT = 2; diff --git a/blocks/timeline/tests/behat/block_timeline_courses.feature b/blocks/timeline/tests/behat/block_timeline_courses.feature index 71b2d0b2a4d..90720be4c7e 100644 --- a/blocks/timeline/tests/behat/block_timeline_courses.feature +++ b/blocks/timeline/tests/behat/block_timeline_courses.feature @@ -45,11 +45,14 @@ Feature: The timeline block allows users to see upcoming courses When I click on "Sort by courses" "link" in the "Timeline" "block" Then I should see "Course 1" in the ".block_timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block_timeline [data-region='view-courses']" "css_element" - And I should not see "Show more courses" in the "Timeline" "block" + And I should see "Show more courses" in the "Timeline" "block" + And I click on "Show more courses" "button" in the "Timeline" "block" + And I should see "Course 4" in the ".block_timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should exist in the "Timeline" "block" And I should see "Choice closes" in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" And I should see "Feedback closes" in the "Timeline" "block" + And "Test feedback 4" "link" should exist in the "Timeline" "block" And "Test feedback 5" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should not see "Course 3" in the "Timeline" "block" @@ -79,10 +82,10 @@ Feature: The timeline block allows users to see upcoming courses And "Test assign 2" "link" should exist in the "Timeline" "block" And I should see "Assignment is due" in the "Timeline" "block" And I should see "Choice closes" in the "Timeline" "block" + And I should see "Course 4" in the "Timeline" "block" + And I should see "Test feedback 4" in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" - And I should not see "Course 4" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" - And I should not see "Test feedback 4" in the "Timeline" "block" Scenario: Persistent sort filter Given I log in as "student1" @@ -93,9 +96,12 @@ Feature: The timeline block allows users to see upcoming courses When I reload the page Then I should see "Course 1" in the "Timeline" "block" And I should see "Course 2" in the "Timeline" "block" - And I should not see "Show more courses" in the "Timeline" "block" + And I should see "Show more courses" in the "Timeline" "block" + And I click on "Show more courses" "button" in the "Timeline" "block" + And I should see "Course 4" in the ".block_timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should exist in the "Timeline" "block" And "Test feedback 1" "link" should exist in the "Timeline" "block" + And "Test feedback 4" "link" should exist in the "Timeline" "block" And "Test feedback 5" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" And I should not see "Course 3" in the "Timeline" "block" @@ -126,9 +132,9 @@ Feature: The timeline block allows users to see upcoming courses And "Test feedback 3" "link" should exist in the "Timeline" "block" And "Test assign 1" "link" should exist in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" - And I should not see "Course 4" in the "Timeline" "block" + And I should see "Course 4" in the "Timeline" "block" And "Test choice 2" "link" should not exist in the "Timeline" "block" - And "Test feedback 4" "link" should not exist in the "Timeline" "block" + And "Test feedback 4" "link" should exist in the "Timeline" "block" Scenario: Current filtering always applies in courses view Given I log in as "student1" @@ -208,8 +214,10 @@ Feature: The timeline block allows users to see upcoming courses And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" + And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" - And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" diff --git a/blocks/timeline/tests/behat/block_timeline_search.feature b/blocks/timeline/tests/behat/block_timeline_search.feature index 5fa95d82dca..579779cf787 100644 --- a/blocks/timeline/tests/behat/block_timeline_search.feature +++ b/blocks/timeline/tests/behat/block_timeline_search.feature @@ -111,16 +111,18 @@ Feature: The timeline block allows users to search for upcoming activities And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "All" "link" in the "Timeline" "block" And I click on "Show more courses" "button" in the "Timeline" "block" + And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" + And I click on "Show more courses" "button" in the "Timeline" "block" And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 1" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element" And I should see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element" - And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element" - And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element" - And I should not see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element" - And I should not see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element" When I set the field "Search by activity type or name" to "choice 1" And I wait until "Course 4" "text" does not exist Then I should see "Test choice 1" in the "Timeline" "block"