diff --git a/blocks/timeline/amd/build/view_courses.min.js b/blocks/timeline/amd/build/view_courses.min.js index f184a5f8c79..164b1833b48 100644 --- a/blocks/timeline/amd/build/view_courses.min.js +++ b/blocks/timeline/amd/build/view_courses.min.js @@ -1,2 +1,2 @@ -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(a,b,c,d,e,f,g,h){var i={MORE_COURSES_BUTTON:"[data-action=\"more-courses\"]",MORE_COURSES_BUTTON_CONTAINER:"[data-region=\"more-courses-button-container\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]",NO_COURSES_WITH_EVENTS_MESSAGE:"[data-region=\"no-events-empty-message\"]",COURSES_LIST:"[data-region=\"courses-list\"]",COURSE_ITEMS_LOADING_PLACEHOLDER:"[data-region=\"course-items-loading-placeholder\"]",COURSE_EVENTS_CONTAINER:"[data-region=\"course-events-container\"]",COURSE_NAME:"[data-region=\"course-name\"]",LOADING_ICON:".loading-icon",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-action=\"search\"]"},j={COURSE_ITEMS:"block_timeline/course-items",LOADING_ICON:"core/loading"},k=86400,l={courseview:!0},m=function(a){a.find(i.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")},n=function(a){a.find(i.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass("hidden")},o=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},p=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},q=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!0);d.render(j.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},r=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!1);b.find(i.LOADING_ICON).remove()},s=function(a){var b=a.find(i.COURSES_LIST);d.replaceNodeContents(b,"","");a.find(i.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass("hidden")},t=function(a){a.find(i.NO_COURSES_WITH_EVENTS_MESSAGE).addClass("hidden")},u=function(a,b){var c=2b},H=function(a,b,c,d){var e=a.map(function(a){return a.id});return D(e,b,5+1,c,d)},I=function(a,b,c,e,f,g){return d.render(j.COURSE_ITEMS,{courses:a,midnight:c,hasdaysoffset:!0,hasdayslimit:f!=void 0,daysoffset:e,dayslimit:f,nodayslimit:f==void 0,courseview:!0,hascourses:!0}).then(function(a){m(b);if(a){u(b,a,g)}return a}).then(function(c){if(a.length<2){o(b)}else{p(b)}return c}).catch(function(){m(b)})},J=function(c){var d=1b},H=function(a,b,c,d){var e=a.map(function(a){return a.id});return D(e,b,5+1,c,d)},I=function(a,b,c,e,f,g){return d.render(j.COURSE_ITEMS,{courses:a,midnight:c,hasdaysoffset:!0,hasdayslimit:f!=void 0,daysoffset:e,dayslimit:f,nodayslimit:f==void 0,courseview:!0,hascourses:!0}).then(function(a){m(b);if(a){u(b,a,g)}return a}).then(function(c){if(a.length<2){o(b)}else{p(b)}return c}).catch(function(){m(b)})},J=function(c){var d=1.\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.\n *\n * @param {object} root The rool element.\n * @return {Number}\n */\n var getEndTime = function(root) {\n var midnight = getMidnight(root);\n var daysLimit = getDaysLimit(root);\n return daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : null;\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"],"file":"view_courses.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/view_courses.js"],"names":["define","$","Notification","CustomEvents","Templates","EventList","CourseRepository","EventsRepository","Pending","SELECTORS","MORE_COURSES_BUTTON","MORE_COURSES_BUTTON_CONTAINER","NO_COURSES_EMPTY_MESSAGE","NO_COURSES_WITH_EVENTS_MESSAGE","COURSES_LIST","COURSE_ITEMS_LOADING_PLACEHOLDER","COURSE_EVENTS_CONTAINER","COURSE_NAME","LOADING_ICON","TIMELINE_BLOCK","TIMELINE_SEARCH","TEMPLATES","COURSE_ITEMS","SECONDS_IN_DAY","additionalConfig","courseview","hideLoadingPlaceholder","root","find","addClass","showLoadingPlaceholder","removeClass","hideMoreCoursesButton","showMoreCoursesButton","enableMoreCoursesButtonLoading","button","prop","render","then","html","append","catch","disableMoreCoursesButtonLoading","remove","showNoCoursesWithEventsMessage","container","replaceNodeContents","hideNoCoursesWithEventsMessage","renderCourseItemsHTML","appendNodeContents","getOffset","parseInt","attr","setOffset","offset","getLimit","getDaysOffset","getDaysLimit","daysLimit","getMidnight","getStartTime","midnight","daysOffset","getEndTime","endTime","Math","floor","Date","now","getEventsForCourseIds","courseIds","startTime","limit","searchValue","args","courseids","starttime","endtime","searchvalue","queryByCourses","getEventReloadTime","data","setEventReloadTime","time","hasReloadedEventsSince","loadEventsForCourses","courses","map","course","id","updateDisplayFromCourses","hasdaysoffset","hasdayslimit","daysoffset","dayslimit","nodayslimit","hascourses","length","loadMoreCourses","pendingPromise","closest","val","getEnrolledCoursesWithEventsByTimelineClassification","result","startEventLoadingTime","nextOffset","nextoffset","moreCoursesAvailable","morecoursesavailable","eventsPromise","renderPromise","when","eventsByCourse","forEach","courseId","courseEventsContainer","eventListRoot","rootSelector","init","resolve","exception","registerEventListeners","events","activate","on","e","originalEvent","preventDefault","stopPropagation","shown","hasClass","reset","removeAttr"],"mappings":"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","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"],"file":"view_courses.min.js"} \ No newline at end of file diff --git a/blocks/timeline/amd/build/view_nav.min.js b/blocks/timeline/amd/build/view_nav.min.js index f0a027b4da7..dec2c5d3983 100644 --- a/blocks/timeline/amd/build/view_nav.min.js +++ b/blocks/timeline/amd/build/view_nav.min.js @@ -1,2 +1,2 @@ -define ("block_timeline/view_nav",["jquery","core/custom_interaction_events","block_timeline/view","core/ajax","core/notification","core/utils"],function(a,b,c,d,e,f){var g={TIMELINE_DAY_FILTER:"[data-region=\"day-filter\"]",TIMELINE_DAY_FILTER_OPTION:"[data-from]",TIMELINE_VIEW_SELECTOR:"[data-region=\"view-selector\"]",DATA_DAYS_OFFSET:"[data-days-offset]",DATA_DAYS_LIMIT:"[data-days-limit]",TIMELINE_SEARCH_INPUT:"[data-action=\"search\"]",TIMELINE_SEARCH_CLEAR_ICON:"[data-action=\"clearsearch\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]"},h=function(a,b){d.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])[0].fail(e.exception)},i=function(d,f){var i=d.find(g.TIMELINE_DAY_FILTER);b.define(i,[b.events.activate]);i.on(b.events.activate,g.TIMELINE_DAY_FILTER_OPTION,function(b,e){var i=a(b.currentTarget).data("filtername");h("block_timeline_user_filter_preference",i);var j=a(b.target).closest(g.TIMELINE_DAY_FILTER_OPTION);if("true"==j.attr("aria-current")){return}var k=j.attr("data-from"),l=j.attr("data-to"),m=d.find(g.DATA_DAYS_OFFSET);m.attr("data-days-offset",k);if(l!=void 0){m.attr("data-days-limit",l)}else{m.removeAttr("data-days-limit")}c.reset(f);e.originalEvent.preventDefault()})},j=function(d,f){var i=d.find(g.TIMELINE_VIEW_SELECTOR);i.on("shown shown.bs.tab",function(b){c.shown(f);a(b.target).removeClass("active")});b.define(i,[b.events.activate]);i.on(b.events.activate,"[data-toggle='tab']",function(b){var c=a(b.currentTarget).data("filtername");h("block_timeline_user_sort_preference",c)})},k=function(a,b){var c=a.find(g.TIMELINE_SEARCH_INPUT),d=a.find(g.TIMELINE_SEARCH_CLEAR_ICON);c.on("input",f.debounce(function(){if(""!==c.val()){l(d,b)}else{m(d,b)}},300));d.on("click",function(){c.val("");m(d,b);c.focus()})},l=function(a,b){a.removeClass("d-none");c.reset(b)},m=function(a,b){a.addClass("d-none");c.reset(b)};return{init:function init(b,c){b=a(b);j(b,c);if(!b.find(g.NO_COURSES_EMPTY_MESSAGE).length){i(b,c);k(b,c)}}}}); +define ("block_timeline/view_nav",["jquery","core/custom_interaction_events","block_timeline/view","core/ajax","core/notification","core/utils"],function(a,b,c,d,e,f){var g={TIMELINE_DAY_FILTER:"[data-region=\"day-filter\"]",TIMELINE_DAY_FILTER_OPTION:"[data-from]",TIMELINE_VIEW_SELECTOR:"[data-region=\"view-selector\"]",DATA_DAYS_OFFSET:"[data-days-offset]",DATA_DAYS_LIMIT:"[data-days-limit]",TIMELINE_SEARCH_INPUT:"[data-action=\"search\"]",TIMELINE_SEARCH_CLEAR_ICON:"[data-action=\"clearsearch\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]"},h=function(a,b){d.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])[0].fail(e.exception)},i=function(d,f){var i=d.find(g.TIMELINE_DAY_FILTER);b.define(i,[b.events.activate]);i.on(b.events.activate,g.TIMELINE_DAY_FILTER_OPTION,function(b,e){var i=a(b.currentTarget).data("filtername");h("block_timeline_user_filter_preference",i);var j=a(b.target).closest(g.TIMELINE_DAY_FILTER_OPTION);if("true"==j.attr("aria-current")){return}var k=j.attr("data-from"),l=j.attr("data-to"),m=d.find(g.DATA_DAYS_OFFSET);m.attr("data-days-offset",k);if(l!=void 0){m.attr("data-days-limit",l)}else{m.removeAttr("data-days-limit")}if("overdue"===j.attr("data-filtername")){m.attr("data-filter-overdue",!0)}else{m.removeAttr("data-filter-overdue")}c.reset(f);e.originalEvent.preventDefault()})},j=function(d,f){var i=d.find(g.TIMELINE_VIEW_SELECTOR);i.on("shown shown.bs.tab",function(b){c.shown(f);a(b.target).removeClass("active")});b.define(i,[b.events.activate]);i.on(b.events.activate,"[data-toggle='tab']",function(b){var c=a(b.currentTarget).data("filtername");h("block_timeline_user_sort_preference",c)})},k=function(a,b){var c=a.find(g.TIMELINE_SEARCH_INPUT),d=a.find(g.TIMELINE_SEARCH_CLEAR_ICON);c.on("input",f.debounce(function(){if(""!==c.val()){l(d,b)}else{m(d,b)}},300));d.on("click",function(){c.val("");m(d,b);c.focus()})},l=function(a,b){a.removeClass("d-none");c.reset(b)},m=function(a,b){a.addClass("d-none");c.reset(b)};return{init:function init(b,c){b=a(b);j(b,c);if(!b.find(g.NO_COURSES_EMPTY_MESSAGE).length){i(b,c);k(b,c)}}}}); //# sourceMappingURL=view_nav.min.js.map diff --git a/blocks/timeline/amd/build/view_nav.min.js.map b/blocks/timeline/amd/build/view_nav.min.js.map index 34f6a466152..a772fa0f3c4 100644 --- a/blocks/timeline/amd/build/view_nav.min.js.map +++ b/blocks/timeline/amd/build/view_nav.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/view_nav.js"],"names":["define","$","CustomEvents","View","Ajax","Notification","Utils","SELECTORS","TIMELINE_DAY_FILTER","TIMELINE_DAY_FILTER_OPTION","TIMELINE_VIEW_SELECTOR","DATA_DAYS_OFFSET","DATA_DAYS_LIMIT","TIMELINE_SEARCH_INPUT","TIMELINE_SEARCH_CLEAR_ICON","NO_COURSES_EMPTY_MESSAGE","updateUserPreferences","type","value","call","methodname","args","preferences","fail","exception","registerTimelineDaySelector","root","timelineViewRoot","timelineDaySelectorContainer","find","events","activate","on","e","data","filtername","currentTarget","option","target","closest","attr","daysOffset","daysLimit","elementsWithDaysOffset","removeAttr","reset","originalEvent","preventDefault","registerViewSelector","viewSelector","shown","removeClass","registerSearch","searchInput","clearSearchIcon","debounce","val","activeSearchState","clearSearchState","focus","addClass","init","length"],"mappings":"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","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 view navigation 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/custom_interaction_events',\n 'block_timeline/view',\n 'core/ajax',\n 'core/notification',\n 'core/utils'\n],\nfunction(\n $,\n CustomEvents,\n View,\n Ajax,\n Notification,\n Utils\n) {\n\n var SELECTORS = {\n TIMELINE_DAY_FILTER: '[data-region=\"day-filter\"]',\n TIMELINE_DAY_FILTER_OPTION: '[data-from]',\n TIMELINE_VIEW_SELECTOR: '[data-region=\"view-selector\"]',\n DATA_DAYS_OFFSET: '[data-days-offset]',\n DATA_DAYS_LIMIT: '[data-days-limit]',\n TIMELINE_SEARCH_INPUT: '[data-action=\"search\"]',\n TIMELINE_SEARCH_CLEAR_ICON: '[data-action=\"clearsearch\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n };\n\n /**\n * Generic handler to persist user preferences\n *\n * @param {string} type The name of the attribute you're updating\n * @param {string} value The value of the attribute you're updating\n */\n var updateUserPreferences = function(type, value) {\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: type,\n value: value\n }\n ]\n }\n };\n\n Ajax.call([request])[0]\n .fail(Notification.exception);\n };\n\n /**\n * Event listener for the day selector (\"Next 7 days\", \"Next 30 days\", etc).\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n var registerTimelineDaySelector = function(root, timelineViewRoot) {\n var timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);\n\n CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);\n timelineDaySelectorContainer.on(\n CustomEvents.events.activate,\n SELECTORS.TIMELINE_DAY_FILTER_OPTION,\n function(e, data) {\n // Update the user preference\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_filter_preference';\n updateUserPreferences(type, filtername);\n\n var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);\n\n if (option.attr('aria-current') == 'true') {\n // If it's already active then we don't need to do anything.\n return;\n }\n\n var daysOffset = option.attr('data-from');\n var daysLimit = option.attr('data-to');\n var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);\n\n elementsWithDaysOffset.attr('data-days-offset', daysOffset);\n\n if (daysLimit != undefined) {\n elementsWithDaysOffset.attr('data-days-limit', daysLimit);\n } else {\n elementsWithDaysOffset.removeAttr('data-days-limit');\n }\n\n // Reset the views to reinitialise the event lists now that we've\n // updated the day limits.\n View.reset(timelineViewRoot);\n\n data.originalEvent.preventDefault();\n }\n );\n };\n\n /**\n * Event listener for the \"sort\" button in the timeline navigation that allows for\n * changing between the timeline dates and courses views.\n *\n * On a view change we tell the timeline view module that the view has been shown\n * so that it can handle how to display the appropriate view.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n var registerViewSelector = function(root, timelineViewRoot) {\n var viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);\n\n // Listen for when the user changes tab so that we can show the first set of courses\n // and load their events when they request the sort by courses view for the first time.\n viewSelector.on('shown shown.bs.tab', function(e) {\n View.shown(timelineViewRoot);\n $(e.target).removeClass('active');\n });\n\n\n // Event selector for user_sort\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(CustomEvents.events.activate, \"[data-toggle='tab']\", function(e) {\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_sort_preference';\n updateUserPreferences(type, filtername);\n });\n };\n\n /**\n * Event listener for the \"search\" input field in the timeline navigation that allows for\n * searching the activity name, course name and activity type.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n const registerSearch = (root, timelineViewRoot) => {\n const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);\n const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);\n searchInput.on('input', Utils.debounce(() => {\n if (searchInput.val() !== '') {\n activeSearchState(clearSearchIcon, timelineViewRoot);\n } else {\n clearSearchState(clearSearchIcon, timelineViewRoot);\n }\n }, 300));\n clearSearchIcon.on('click', () => {\n searchInput.val('');\n clearSearchState(clearSearchIcon, timelineViewRoot);\n searchInput.focus();\n });\n };\n\n /**\n * Show the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n const activeSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.removeClass('d-none');\n View.reset(timelineViewRoot);\n };\n\n /**\n * Hide the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n const clearSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.addClass('d-none');\n View.reset(timelineViewRoot);\n };\n\n /**\n * Initialise the timeline view navigation by adding event listeners to\n * the navigation elements.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n var init = function(root, timelineViewRoot) {\n root = $(root);\n\n registerViewSelector(root, timelineViewRoot);\n\n // Only need to handle filtering if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n registerTimelineDaySelector(root, timelineViewRoot);\n registerSearch(root, timelineViewRoot);\n }\n };\n\n return {\n init: init\n };\n});\n"],"file":"view_nav.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/view_nav.js"],"names":["define","$","CustomEvents","View","Ajax","Notification","Utils","SELECTORS","TIMELINE_DAY_FILTER","TIMELINE_DAY_FILTER_OPTION","TIMELINE_VIEW_SELECTOR","DATA_DAYS_OFFSET","DATA_DAYS_LIMIT","TIMELINE_SEARCH_INPUT","TIMELINE_SEARCH_CLEAR_ICON","NO_COURSES_EMPTY_MESSAGE","updateUserPreferences","type","value","call","methodname","args","preferences","fail","exception","registerTimelineDaySelector","root","timelineViewRoot","timelineDaySelectorContainer","find","events","activate","on","e","data","filtername","currentTarget","option","target","closest","attr","daysOffset","daysLimit","elementsWithDaysOffset","removeAttr","reset","originalEvent","preventDefault","registerViewSelector","viewSelector","shown","removeClass","registerSearch","searchInput","clearSearchIcon","debounce","val","activeSearchState","clearSearchState","focus","addClass","init","length"],"mappings":"AAsBAA,OAAM,2BACN,CACI,QADJ,CAEI,gCAFJ,CAGI,qBAHJ,CAII,WAJJ,CAKI,mBALJ,CAMI,YANJ,CADM,CASN,SACIC,CADJ,CAEIC,CAFJ,CAGIC,CAHJ,CAIIC,CAJJ,CAKIC,CALJ,CAMIC,CANJ,CAOE,IAEMC,CAAAA,CAAS,CAAG,CACZC,mBAAmB,CAAE,8BADT,CAEZC,0BAA0B,CAAE,aAFhB,CAGZC,sBAAsB,CAAE,iCAHZ,CAIZC,gBAAgB,CAAE,oBAJN,CAKZC,eAAe,CAAE,mBALL,CAMZC,qBAAqB,CAAE,0BANX,CAOZC,0BAA0B,CAAE,+BAPhB,CAQZC,wBAAwB,CAAE,4CARd,CAFlB,CAmBMC,CAAqB,CAAG,SAASC,CAAT,CAAeC,CAAf,CAAsB,CAa9Cd,CAAI,CAACe,IAAL,CAAU,CAZI,CACVC,UAAU,CAAE,mCADF,CAEVC,IAAI,CAAE,CACFC,WAAW,CAAE,CACT,CACIL,IAAI,CAAEA,CADV,CAEIC,KAAK,CAAEA,CAFX,CADS,CADX,CAFI,CAYJ,CAAV,EAAqB,CAArB,EACKK,IADL,CACUlB,CAAY,CAACmB,SADvB,CAEH,CAlCH,CA0CMC,CAA2B,CAAG,SAASC,CAAT,CAAeC,CAAf,CAAiC,CAC/D,GAAIC,CAAAA,CAA4B,CAAGF,CAAI,CAACG,IAAL,CAAUtB,CAAS,CAACC,mBAApB,CAAnC,CAEAN,CAAY,CAACF,MAAb,CAAoB4B,CAApB,CAAkD,CAAC1B,CAAY,CAAC4B,MAAb,CAAoBC,QAArB,CAAlD,EACAH,CAA4B,CAACI,EAA7B,CACI9B,CAAY,CAAC4B,MAAb,CAAoBC,QADxB,CAEIxB,CAAS,CAACE,0BAFd,CAGI,SAASwB,CAAT,CAAYC,CAAZ,CAAkB,IAEVC,CAAAA,CAAU,CAAGlC,CAAC,CAACgC,CAAC,CAACG,aAAH,CAAD,CAAmBF,IAAnB,CAAwB,YAAxB,CAFH,CAIdlB,CAAqB,CADV,uCACU,CAAOmB,CAAP,CAArB,CAEA,GAAIE,CAAAA,CAAM,CAAGpC,CAAC,CAACgC,CAAC,CAACK,MAAH,CAAD,CAAYC,OAAZ,CAAoBhC,CAAS,CAACE,0BAA9B,CAAb,CAEA,GAAmC,MAA/B,EAAA4B,CAAM,CAACG,IAAP,CAAY,cAAZ,CAAJ,CAA2C,CAEvC,MACH,CAXa,GAaVC,CAAAA,CAAU,CAAGJ,CAAM,CAACG,IAAP,CAAY,WAAZ,CAbH,CAcVE,CAAS,CAAGL,CAAM,CAACG,IAAP,CAAY,SAAZ,CAdF,CAeVG,CAAsB,CAAGjB,CAAI,CAACG,IAAL,CAAUtB,CAAS,CAACI,gBAApB,CAff,CAiBdgC,CAAsB,CAACH,IAAvB,CAA4B,kBAA5B,CAAgDC,CAAhD,EAEA,GAAIC,CAAS,QAAb,CAA4B,CACxBC,CAAsB,CAACH,IAAvB,CAA4B,iBAA5B,CAA+CE,CAA/C,CACH,CAFD,IAEO,CACHC,CAAsB,CAACC,UAAvB,CAAkC,iBAAlC,CACH,CAED,GAAuC,SAAnC,GAAAP,CAAM,CAACG,IAAP,CAAY,iBAAZ,CAAJ,CAAkD,CAC9CG,CAAsB,CAACH,IAAvB,CAA4B,qBAA5B,IACH,CAFD,IAEO,CACHG,CAAsB,CAACC,UAAvB,CAAkC,qBAAlC,CACH,CAIDzC,CAAI,CAAC0C,KAAL,CAAWlB,CAAX,EAEAO,CAAI,CAACY,aAAL,CAAmBC,cAAnB,EACH,CAvCL,CAyCH,CAvFH,CAmGMC,CAAoB,CAAG,SAAStB,CAAT,CAAeC,CAAf,CAAiC,CACxD,GAAIsB,CAAAA,CAAY,CAAGvB,CAAI,CAACG,IAAL,CAAUtB,CAAS,CAACG,sBAApB,CAAnB,CAIAuC,CAAY,CAACjB,EAAb,CAAgB,oBAAhB,CAAsC,SAASC,CAAT,CAAY,CAC9C9B,CAAI,CAAC+C,KAAL,CAAWvB,CAAX,EACA1B,CAAC,CAACgC,CAAC,CAACK,MAAH,CAAD,CAAYa,WAAZ,CAAwB,QAAxB,CACH,CAHD,EAOAjD,CAAY,CAACF,MAAb,CAAoBiD,CAApB,CAAkC,CAAC/C,CAAY,CAAC4B,MAAb,CAAoBC,QAArB,CAAlC,EACAkB,CAAY,CAACjB,EAAb,CAAgB9B,CAAY,CAAC4B,MAAb,CAAoBC,QAApC,CAA8C,qBAA9C,CAAqE,SAASE,CAAT,CAAY,IACzEE,CAAAA,CAAU,CAAGlC,CAAC,CAACgC,CAAC,CAACG,aAAH,CAAD,CAAmBF,IAAnB,CAAwB,YAAxB,CAD4D,CAG7ElB,CAAqB,CADV,qCACU,CAAOmB,CAAP,CACxB,CAJD,CAKH,CArHH,CA8HQiB,CAAc,CAAG,SAAC1B,CAAD,CAAOC,CAAP,CAA4B,IACzC0B,CAAAA,CAAW,CAAG3B,CAAI,CAACG,IAAL,CAAUtB,CAAS,CAACM,qBAApB,CAD2B,CAEzCyC,CAAe,CAAG5B,CAAI,CAACG,IAAL,CAAUtB,CAAS,CAACO,0BAApB,CAFuB,CAG/CuC,CAAW,CAACrB,EAAZ,CAAe,OAAf,CAAwB1B,CAAK,CAACiD,QAAN,CAAe,UAAM,CACzC,GAA0B,EAAtB,GAAAF,CAAW,CAACG,GAAZ,EAAJ,CAA8B,CAC1BC,CAAiB,CAACH,CAAD,CAAkB3B,CAAlB,CACpB,CAFD,IAEO,CACH+B,CAAgB,CAACJ,CAAD,CAAkB3B,CAAlB,CACnB,CACJ,CANuB,CAMrB,GANqB,CAAxB,EAOA2B,CAAe,CAACtB,EAAhB,CAAmB,OAAnB,CAA4B,UAAM,CAC9BqB,CAAW,CAACG,GAAZ,CAAgB,EAAhB,EACAE,CAAgB,CAACJ,CAAD,CAAkB3B,CAAlB,CAAhB,CACA0B,CAAW,CAACM,KAAZ,EACH,CAJD,CAKH,CA7IH,CAqJQF,CAAiB,CAAG,SAACH,CAAD,CAAkB3B,CAAlB,CAAuC,CAC7D2B,CAAe,CAACH,WAAhB,CAA4B,QAA5B,EACAhD,CAAI,CAAC0C,KAAL,CAAWlB,CAAX,CACH,CAxJH,CAgKQ+B,CAAgB,CAAG,SAACJ,CAAD,CAAkB3B,CAAlB,CAAuC,CAC5D2B,CAAe,CAACM,QAAhB,CAAyB,QAAzB,EACAzD,CAAI,CAAC0C,KAAL,CAAWlB,CAAX,CACH,CAnKH,CAwLE,MAAO,CACHkC,IAAI,CAbG,QAAPA,CAAAA,IAAO,CAASnC,CAAT,CAAeC,CAAf,CAAiC,CACxCD,CAAI,CAAGzB,CAAC,CAACyB,CAAD,CAAR,CAEAsB,CAAoB,CAACtB,CAAD,CAAOC,CAAP,CAApB,CAGA,GAAI,CAACD,CAAI,CAACG,IAAL,CAAUtB,CAAS,CAACQ,wBAApB,EAA8C+C,MAAnD,CAA2D,CACvDrC,CAA2B,CAACC,CAAD,CAAOC,CAAP,CAA3B,CACAyB,CAAc,CAAC1B,CAAD,CAAOC,CAAP,CACjB,CACJ,CAEM,CAGV,CA3MK,CAAN","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 view navigation 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/custom_interaction_events',\n 'block_timeline/view',\n 'core/ajax',\n 'core/notification',\n 'core/utils'\n],\nfunction(\n $,\n CustomEvents,\n View,\n Ajax,\n Notification,\n Utils\n) {\n\n var SELECTORS = {\n TIMELINE_DAY_FILTER: '[data-region=\"day-filter\"]',\n TIMELINE_DAY_FILTER_OPTION: '[data-from]',\n TIMELINE_VIEW_SELECTOR: '[data-region=\"view-selector\"]',\n DATA_DAYS_OFFSET: '[data-days-offset]',\n DATA_DAYS_LIMIT: '[data-days-limit]',\n TIMELINE_SEARCH_INPUT: '[data-action=\"search\"]',\n TIMELINE_SEARCH_CLEAR_ICON: '[data-action=\"clearsearch\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n };\n\n /**\n * Generic handler to persist user preferences\n *\n * @param {string} type The name of the attribute you're updating\n * @param {string} value The value of the attribute you're updating\n */\n var updateUserPreferences = function(type, value) {\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: type,\n value: value\n }\n ]\n }\n };\n\n Ajax.call([request])[0]\n .fail(Notification.exception);\n };\n\n /**\n * Event listener for the day selector (\"Next 7 days\", \"Next 30 days\", etc).\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n var registerTimelineDaySelector = function(root, timelineViewRoot) {\n var timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);\n\n CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);\n timelineDaySelectorContainer.on(\n CustomEvents.events.activate,\n SELECTORS.TIMELINE_DAY_FILTER_OPTION,\n function(e, data) {\n // Update the user preference\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_filter_preference';\n updateUserPreferences(type, filtername);\n\n var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);\n\n if (option.attr('aria-current') == 'true') {\n // If it's already active then we don't need to do anything.\n return;\n }\n\n var daysOffset = option.attr('data-from');\n var daysLimit = option.attr('data-to');\n var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);\n\n elementsWithDaysOffset.attr('data-days-offset', daysOffset);\n\n if (daysLimit != undefined) {\n elementsWithDaysOffset.attr('data-days-limit', daysLimit);\n } else {\n elementsWithDaysOffset.removeAttr('data-days-limit');\n }\n\n if (option.attr('data-filtername') === 'overdue') {\n elementsWithDaysOffset.attr('data-filter-overdue', true);\n } else {\n elementsWithDaysOffset.removeAttr('data-filter-overdue');\n }\n\n // Reset the views to reinitialise the event lists now that we've\n // updated the day limits.\n View.reset(timelineViewRoot);\n\n data.originalEvent.preventDefault();\n }\n );\n };\n\n /**\n * Event listener for the \"sort\" button in the timeline navigation that allows for\n * changing between the timeline dates and courses views.\n *\n * On a view change we tell the timeline view module that the view has been shown\n * so that it can handle how to display the appropriate view.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n var registerViewSelector = function(root, timelineViewRoot) {\n var viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);\n\n // Listen for when the user changes tab so that we can show the first set of courses\n // and load their events when they request the sort by courses view for the first time.\n viewSelector.on('shown shown.bs.tab', function(e) {\n View.shown(timelineViewRoot);\n $(e.target).removeClass('active');\n });\n\n\n // Event selector for user_sort\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(CustomEvents.events.activate, \"[data-toggle='tab']\", function(e) {\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_sort_preference';\n updateUserPreferences(type, filtername);\n });\n };\n\n /**\n * Event listener for the \"search\" input field in the timeline navigation that allows for\n * searching the activity name, course name and activity type.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n const registerSearch = (root, timelineViewRoot) => {\n const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);\n const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);\n searchInput.on('input', Utils.debounce(() => {\n if (searchInput.val() !== '') {\n activeSearchState(clearSearchIcon, timelineViewRoot);\n } else {\n clearSearchState(clearSearchIcon, timelineViewRoot);\n }\n }, 300));\n clearSearchIcon.on('click', () => {\n searchInput.val('');\n clearSearchState(clearSearchIcon, timelineViewRoot);\n searchInput.focus();\n });\n };\n\n /**\n * Show the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n const activeSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.removeClass('d-none');\n View.reset(timelineViewRoot);\n };\n\n /**\n * Hide the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n const clearSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.addClass('d-none');\n View.reset(timelineViewRoot);\n };\n\n /**\n * Initialise the timeline view navigation by adding event listeners to\n * the navigation elements.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\n var init = function(root, timelineViewRoot) {\n root = $(root);\n\n registerViewSelector(root, timelineViewRoot);\n\n // Only need to handle filtering if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n registerTimelineDaySelector(root, timelineViewRoot);\n registerSearch(root, timelineViewRoot);\n }\n };\n\n return {\n init: init\n };\n});\n"],"file":"view_nav.min.js"} \ 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 218e4e8a07a..d627c1ce734 100644 --- a/blocks/timeline/amd/src/view_courses.js +++ b/blocks/timeline/amd/src/view_courses.js @@ -254,15 +254,27 @@ function( /** * Return the end time for fetching events. This is calculated * based on the user's midnight value so that timezones are - * preserved. + * preserved, unless filtering by overdue, where the current UNIX timestamp is used. * * @param {object} root The rool element. * @return {Number} */ var getEndTime = function(root) { - var midnight = getMidnight(root); - var daysLimit = getDaysLimit(root); - return daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : null; + let endTime = null; + + if (root.attr('data-filter-overdue')) { + // If filtering by overdue, end time will be the current timestamp in seconds. + endTime = Math.floor(Date.now() / 1000); + } else { + const midnight = getMidnight(root); + const daysLimit = getDaysLimit(root); + + if (daysLimit != undefined) { + endTime = midnight + (daysLimit * SECONDS_IN_DAY); + } + } + + return endTime; }; /** diff --git a/blocks/timeline/amd/src/view_nav.js b/blocks/timeline/amd/src/view_nav.js index 109c7538664..2097277317a 100644 --- a/blocks/timeline/amd/src/view_nav.js +++ b/blocks/timeline/amd/src/view_nav.js @@ -110,6 +110,12 @@ function( elementsWithDaysOffset.removeAttr('data-days-limit'); } + if (option.attr('data-filtername') === 'overdue') { + elementsWithDaysOffset.attr('data-filter-overdue', true); + } else { + elementsWithDaysOffset.removeAttr('data-filter-overdue'); + } + // Reset the views to reinitialise the event lists now that we've // updated the day limits. View.reset(timelineViewRoot); diff --git a/blocks/timeline/templates/view.mustache b/blocks/timeline/templates/view.mustache index daa71a839d1..a0fe296666a 100644 --- a/blocks/timeline/templates/view.mustache +++ b/blocks/timeline/templates/view.mustache @@ -44,7 +44,8 @@ "daysoffset": -14, "dayslimit": false, "limit": 0, - "hascourses": true + "hascourses": true, + "overdue": false } }}
@@ -61,6 +62,7 @@ data-days-limit="{{dayslimit}}" data-days-offset="{{daysoffset}}" data-no-events-url="{{urls.noevents}}" + {{#overdue}}data-filter-overdue="{{overdue}}"{{/overdue}} id="view_courses_{{uniqid}}" > {{> block_timeline/view-courses }} diff --git a/blocks/timeline/tests/behat/block_timeline_courses.feature b/blocks/timeline/tests/behat/block_timeline_courses.feature index 76b6032edae..71b2d0b2a4d 100644 --- a/blocks/timeline/tests/behat/block_timeline_courses.feature +++ b/blocks/timeline/tests/behat/block_timeline_courses.feature @@ -15,6 +15,7 @@ Feature: The timeline block allows users to see upcoming courses | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | | Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## | | Course 4 | C4 | 0 | ##first day of next month## | ##last day of next month## | + | Course 5 | C5 | 0 | ##yesterday## | ##tomorrow## | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | | choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | @@ -192,7 +193,13 @@ Feature: The timeline block allows users to see upcoming courses And I should not see "Test choice 1" Scenario: Only courses with matching events are displayed and are refreshed when filtering changes - Given I log in as "student1" + Given the following "activities" exist: + | activity | course | idnumber | name | intro | timeopen | duedate | + | assign | C5 | assign3 | Test assign 3 | Test assign description | ##yesterday## | ##now +1 minute## | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C5 | student | + When I log in as "student1" And I click on "Sort timeline items" "button" in the "Timeline" "block" And I click on "Sort by courses" "link" in the "Timeline" "block" And I click on "Filter timeline by date" "button" in the "Timeline" "block" @@ -201,30 +208,38 @@ 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 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" And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And I click on "Filter timeline by date" "button" in the "Timeline" "block" - When I click on "Overdue" "link" in the "Timeline" "block" + And I click on "Overdue" "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 "Course 3" in the "Timeline" "block" + And I should not see "Course 5" in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" And "Test assign 1" "link" should exist in the "Timeline" "block" And "Test assign 2" "link" should exist in the "Timeline" "block" + And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test feedback 5" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" And I click on "Filter timeline by date" "button" in the "Timeline" "block" And I click on "Next 7 days" "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 5" in the ".block-timeline [data-region='view-courses']" "css_element" And I should not see "Course 3" in the "Timeline" "block" And I should not see "Show more courses" in the "Timeline" "block" 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" And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" diff --git a/blocks/timeline/tests/behat/block_timeline_courses_overdue.feature b/blocks/timeline/tests/behat/block_timeline_courses_overdue.feature new file mode 100644 index 00000000000..738ab304e80 --- /dev/null +++ b/blocks/timeline/tests/behat/block_timeline_courses_overdue.feature @@ -0,0 +1,125 @@ +@block @block_timeline @javascript +Feature: The timeline block allows users to see courses with overdue activities + In order to view overdue activities in the timeline block + As a student + I can select the overdue filter in courses view + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | idnumber | + | student1 | Student | 1 | student1@example.com | S1 | + And the following "courses" exist: + | fullname | shortname | category | startdate | enddate | + | Course 1 | C1 | 0 | ##now -3 months## | ##tomorrow## | + | Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## | + | Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## | + | Course 4 | C4 | 0 | ##yesterday## | ##tomorrow## | + | Course 5 | C5 | 0 | ##yesterday## | ##tomorrow## | + | Course 6 | C6 | 0 | ##yesterday## | ##tomorrow## | + And the following "activities" exist: + | activity | course | idnumber | name | intro | timeopen | duedate | + | assign | C1 | assign1 | Test assign 1 | Assign due last month | ##now -2 months## | ##now -1 month## | + | assign | C2 | assign2 | Test assign 2 | Assign due yesterday | ##now -2 days## | ##yesterday## | + | assign | C3 | assign3 | Test assign 3 | Assign due yesterday | ##now -2 days## | ##yesterday## | + | assign | C4 | assign4 | Test assign 4 | Assign due later today | ##yesterday## | ##now +10 minutes## | + | assign | C5 | assign5 | Test assign 5 | Assign due yesterday | ##now -2 days## | ##yesterday## | + | assign | C6 | assign6 | Test assign 6 | Assign due tomorrow | ##yesterday## | ##tomorrow## | + + Scenario: No activities to display as overdue displays expected message + Given the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student1 | C4 | student | + | student1 | C6 | student | + Given I log in as "student1" + And I click on "Sort timeline items" "button" in the "Timeline" "block" + And I click on "Sort by courses" "link" in the "Timeline" "block" + And I click on "Filter timeline by date" "button" in the "Timeline" "block" + When I click on "Overdue" "link" in the "Timeline" "block" + Then I should see "No activities require action" in the "Timeline" "block" + And I reload the page + And I should see "No activities require action" in the "Timeline" "block" + + Scenario: If filtering by overdue, only courses with a matching item are included + Given the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student1 | C2 | student | + | student1 | C4 | student | + | student1 | C5 | student | + | student1 | C6 | student | + When I log in as "student1" + And I click on "Sort timeline items" "button" in the "Timeline" "block" + And I click on "Sort by courses" "link" in the "Timeline" "block" + And I click on "Filter timeline by date" "button" in the "Timeline" "block" + And I click on "Overdue" "link" in the "Timeline" "block" + Then I should not see "Show more courses" in the "Timeline" "block" + And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should see "Course 5" 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 "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Course 6" 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 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And I reload the page + And I should not see "Show more courses" in the "Timeline" "block" + And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should see "Course 5" 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 "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Course 6" 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 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + + Scenario: If filtering by overdue, only courses with a matching item are included and loading more is supported + Given the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student1 | C2 | student | + | student1 | C3 | student | + | student1 | C4 | student | + | student1 | C5 | student | + | student1 | C6 | student | + When I log in as "student1" + And I click on "Sort timeline items" "button" in the "Timeline" "block" + And I click on "Sort by courses" "link" in the "Timeline" "block" + And I click on "Filter timeline by date" "button" in the "Timeline" "block" + And I click on "Overdue" "link" in the "Timeline" "block" + And I click on "Show more courses" "button" in the "Timeline" "block" + Then 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 5" 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 4" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Course 6" 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" + And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Show more courses" in the "Timeline" "block" + And I reload the page + And I click on "Show more courses" "button" in the "Timeline" "block" + 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 5" 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 4" in the ".block-timeline [data-region='view-courses']" "css_element" + And I should not see "Course 6" 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" + And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element" + And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"