weekly release 5.0dev
[moodle.git] / blocks / timeline / amd / src / view_nav.js
blob8e45995a4bf06c6c6816bdbc345bac0b078f34f7
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Manage the timeline view navigation for the timeline block.
18  *
19  * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
20  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21  */
23 import $ from 'jquery';
24 import * as CustomEvents from 'core/custom_interaction_events';
25 import * as View from 'block_timeline/view';
26 import * as Notification from 'core/notification';
27 import * as Utils from 'core/utils';
28 import * as UserRepository from 'core_user/repository';
30 const SELECTORS = {
31     TIMELINE_DAY_FILTER: '[data-region="day-filter"]',
32     TIMELINE_DAY_FILTER_OPTION: '[data-from]',
33     TIMELINE_VIEW_SELECTOR: '[data-region="view-selector"]',
34     DATA_DAYS_OFFSET: '[data-days-offset]',
35     DATA_DAYS_LIMIT: '[data-days-limit]',
36     TIMELINE_SEARCH_INPUT: '[data-action="search"]',
37     TIMELINE_SEARCH_CLEAR_ICON: '[data-action="clearsearch"]',
38     NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]',
41 /**
42  * Event listener for the day selector ("Next 7 days", "Next 30 days", etc).
43  *
44  * @param {object} root The root element for the timeline block
45  * @param {object} timelineViewRoot The root element for the timeline view
46  */
47 const registerTimelineDaySelector = function(root, timelineViewRoot) {
48     const timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);
50     CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);
51     timelineDaySelectorContainer.on(
52         CustomEvents.events.activate,
53         SELECTORS.TIMELINE_DAY_FILTER_OPTION,
54         function(e, data) {
55             // Update the user preference
56             var filtername = $(e.currentTarget).data('filtername');
57             var type = 'block_timeline_user_filter_preference';
58             UserRepository.setUserPreference(type, filtername)
59                 .catch(Notification.exception);
61             var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);
63             if (option.attr('aria-current') == 'true') {
64                 // If it's already active then we don't need to do anything.
65                 return;
66             }
68             var daysOffset = option.attr('data-from');
69             var daysLimit = option.attr('data-to');
70             var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);
72             elementsWithDaysOffset.attr('data-days-offset', daysOffset);
74             if (daysLimit != undefined) {
75                 elementsWithDaysOffset.attr('data-days-limit', daysLimit);
76             } else {
77                 elementsWithDaysOffset.removeAttr('data-days-limit');
78             }
80             if (option.attr('data-filtername') === 'overdue') {
81                 elementsWithDaysOffset.attr('data-filter-overdue', true);
82             } else {
83                 elementsWithDaysOffset.removeAttr('data-filter-overdue');
84             }
86             // Reset the views to reinitialise the event lists now that we've
87             // updated the day limits.
88             View.reset(timelineViewRoot);
90             data.originalEvent.preventDefault();
91         }
92     );
95 /**
96  * Event listener for the "sort" button in the timeline navigation that allows for
97  * changing between the timeline dates and courses views.
98  *
99  * On a view change we tell the timeline view module that the view has been shown
100  * so that it can handle how to display the appropriate view.
102  * @param {object} root The root element for the timeline block
103  * @param {object} timelineViewRoot The root element for the timeline view
104  */
105 const registerViewSelector = function(root, timelineViewRoot) {
106     const viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);
108     // Listen for when the user changes tab so that we can show the first set of courses
109     // and load their events when they request the sort by courses view for the first time.
110     viewSelector.on('shown shown.bs.tab', function(e) {
111         View.shown(timelineViewRoot);
112         $(e.target).removeClass('active');
113     });
116     // Event selector for user_sort
117     CustomEvents.define(viewSelector, [CustomEvents.events.activate]);
118     viewSelector.on(CustomEvents.events.activate, "[data-toggle='tab']", function(e) {
119         var filtername = $(e.currentTarget).data('filtername');
120         var type = 'block_timeline_user_sort_preference';
121         UserRepository.setUserPreference(type, filtername)
122             .catch(Notification.exception);
123     });
127  * Event listener for the "search" input field in the timeline navigation that allows for
128  * searching the activity name, course name and activity type.
130  * @param {object} root The root element for the timeline block
131  * @param {object} timelineViewRoot The root element for the timeline view
132  */
133 const registerSearch = (root, timelineViewRoot) => {
134     const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);
135     const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);
136     searchInput.on('input', Utils.debounce(() => {
137         if (searchInput.val() !== '') {
138             activeSearchState(clearSearchIcon, timelineViewRoot);
139         } else {
140             clearSearchState(clearSearchIcon, timelineViewRoot);
141         }
142     }, 1000));
143     clearSearchIcon.on('click', () => {
144         searchInput.val('');
145         clearSearchState(clearSearchIcon, timelineViewRoot);
146         searchInput.focus();
147     });
151  * Show the clear search icon.
153  * @param {object} clearSearchIcon Clear search icon element.
154  * @param {object} timelineViewRoot The root element for the timeline view
155  */
156 const activeSearchState = (clearSearchIcon, timelineViewRoot) => {
157     clearSearchIcon.removeClass('d-none');
158     View.reset(timelineViewRoot);
162  * Hide the clear search icon.
164  * @param {object} clearSearchIcon Clear search icon element.
165  * @param {object} timelineViewRoot The root element for the timeline view
166  */
167 const clearSearchState = (clearSearchIcon, timelineViewRoot) => {
168     clearSearchIcon.addClass('d-none');
169     View.reset(timelineViewRoot);
173  * Initialise the timeline view navigation by adding event listeners to
174  * the navigation elements.
176  * @param {jQuery|HTMLElement} root The root element for the timeline block
177  * @param {object} timelineViewRoot The root element for the timeline view
178  */
179 export const init = function(root, timelineViewRoot) {
180     root = $(root);
182     registerViewSelector(root, timelineViewRoot);
184     // Only need to handle filtering if the user is actively enrolled in a course.
185     if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
186         registerTimelineDaySelector(root, timelineViewRoot);
187         registerSearch(root, timelineViewRoot);
188     }