Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Manage the timeline courses view for the timeline block.\n *\n * @copyright  2018 Ryan Wyllie <ryan@moodle.com>\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":"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"}