1 |
efrain |
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.
|
|
|
12 |
//
|
|
|
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/>.
|
|
|
15 |
|
|
|
16 |
/**
|
|
|
17 |
* Manage the timeline courses view 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 |
*/
|
|
|
22 |
|
|
|
23 |
define(
|
|
|
24 |
[
|
|
|
25 |
'jquery',
|
|
|
26 |
'core/notification',
|
|
|
27 |
'core/custom_interaction_events',
|
|
|
28 |
'core/templates',
|
|
|
29 |
'block_timeline/event_list',
|
|
|
30 |
'core_course/repository',
|
|
|
31 |
'block_timeline/calendar_events_repository',
|
|
|
32 |
'core/pending'
|
|
|
33 |
],
|
|
|
34 |
function(
|
|
|
35 |
$,
|
|
|
36 |
Notification,
|
|
|
37 |
CustomEvents,
|
|
|
38 |
Templates,
|
|
|
39 |
EventList,
|
|
|
40 |
CourseRepository,
|
|
|
41 |
EventsRepository,
|
|
|
42 |
Pending
|
|
|
43 |
) {
|
|
|
44 |
|
|
|
45 |
var SELECTORS = {
|
|
|
46 |
MORE_COURSES_BUTTON: '[data-action="more-courses"]',
|
|
|
47 |
MORE_COURSES_BUTTON_CONTAINER: '[data-region="more-courses-button-container"]',
|
|
|
48 |
NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]',
|
|
|
49 |
NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region="no-events-empty-message"]',
|
|
|
50 |
COURSES_LIST: '[data-region="courses-list"]',
|
|
|
51 |
COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region="course-items-loading-placeholder"]',
|
|
|
52 |
COURSE_EVENTS_CONTAINER: '[data-region="course-events-container"]',
|
|
|
53 |
COURSE_NAME: '[data-region="course-name"]',
|
|
|
54 |
LOADING_ICON: '.loading-icon',
|
|
|
55 |
TIMELINE_BLOCK: '[data-region="timeline"]',
|
|
|
56 |
TIMELINE_SEARCH: '[data-action="search"]'
|
|
|
57 |
};
|
|
|
58 |
|
|
|
59 |
var TEMPLATES = {
|
|
|
60 |
COURSE_ITEMS: 'block_timeline/course-items',
|
|
|
61 |
LOADING_ICON: 'core/loading'
|
|
|
62 |
};
|
|
|
63 |
|
|
|
64 |
var COURSE_CLASSIFICATION = 'all';
|
|
|
65 |
var COURSE_SORT = 'fullname asc';
|
|
|
66 |
var COURSE_EVENT_LIMIT = 5;
|
|
|
67 |
var COURSE_LIMIT = 2;
|
|
|
68 |
var SECONDS_IN_DAY = 60 * 60 * 24;
|
|
|
69 |
|
|
|
70 |
const additionalConfig = {courseview: true};
|
|
|
71 |
|
|
|
72 |
/**
|
|
|
73 |
* Hide the loading placeholder elements.
|
|
|
74 |
*
|
|
|
75 |
* @param {object} root The rool element.
|
|
|
76 |
*/
|
|
|
77 |
var hideLoadingPlaceholder = function(root) {
|
|
|
78 |
root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden');
|
|
|
79 |
};
|
|
|
80 |
|
|
|
81 |
/**
|
|
|
82 |
* Show the loading placeholder elements.
|
|
|
83 |
*
|
|
|
84 |
* @param {object} root The rool element.
|
|
|
85 |
*/
|
|
|
86 |
const showLoadingPlaceholder = function(root) {
|
|
|
87 |
root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden');
|
|
|
88 |
};
|
|
|
89 |
|
|
|
90 |
/**
|
|
|
91 |
* Hide the "more courses" button.
|
|
|
92 |
*
|
|
|
93 |
* @param {object} root The rool element.
|
|
|
94 |
*/
|
|
|
95 |
var hideMoreCoursesButton = function(root) {
|
|
|
96 |
root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).addClass('hidden');
|
|
|
97 |
};
|
|
|
98 |
|
|
|
99 |
/**
|
|
|
100 |
* Show the "more courses" button.
|
|
|
101 |
*
|
|
|
102 |
* @param {object} root The rool element.
|
|
|
103 |
*/
|
|
|
104 |
var showMoreCoursesButton = function(root) {
|
|
|
105 |
root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).removeClass('hidden');
|
|
|
106 |
};
|
|
|
107 |
|
|
|
108 |
/**
|
|
|
109 |
* Disable the "more courses" button and show the loading spinner.
|
|
|
110 |
*
|
|
|
111 |
* @param {object} root The rool element.
|
|
|
112 |
*/
|
|
|
113 |
var enableMoreCoursesButtonLoading = function(root) {
|
|
|
114 |
var button = root.find(SELECTORS.MORE_COURSES_BUTTON);
|
|
|
115 |
button.prop('disabled', true);
|
|
|
116 |
Templates.render(TEMPLATES.LOADING_ICON, {})
|
|
|
117 |
.then(function(html) {
|
|
|
118 |
button.append(html);
|
|
|
119 |
return html;
|
|
|
120 |
})
|
|
|
121 |
.catch(function() {
|
|
|
122 |
// It's not important if this false so just do so silently.
|
|
|
123 |
return false;
|
|
|
124 |
});
|
|
|
125 |
};
|
|
|
126 |
|
|
|
127 |
/**
|
|
|
128 |
* Enable the "more courses" button and remove the loading spinner.
|
|
|
129 |
*
|
|
|
130 |
* @param {object} root The rool element.
|
|
|
131 |
*/
|
|
|
132 |
var disableMoreCoursesButtonLoading = function(root) {
|
|
|
133 |
var button = root.find(SELECTORS.MORE_COURSES_BUTTON);
|
|
|
134 |
button.prop('disabled', false);
|
|
|
135 |
button.find(SELECTORS.LOADING_ICON).remove();
|
|
|
136 |
};
|
|
|
137 |
|
|
|
138 |
/**
|
|
|
139 |
* Display the message for when courses have no events available (within the current filtering).
|
|
|
140 |
*
|
|
|
141 |
* @param {object} root The rool element.
|
|
|
142 |
*/
|
|
|
143 |
const showNoCoursesWithEventsMessage = function(root) {
|
|
|
144 |
// Remove any course list contents, since we will display the no events message.
|
|
|
145 |
const container = root.find(SELECTORS.COURSES_LIST);
|
|
|
146 |
Templates.replaceNodeContents(container, '', '');
|
|
|
147 |
root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden');
|
|
|
148 |
};
|
|
|
149 |
|
|
|
150 |
/**
|
|
|
151 |
* Hide the message for when courses have no events available (within the current filtering).
|
|
|
152 |
*
|
|
|
153 |
* @param {object} root The rool element.
|
|
|
154 |
*/
|
|
|
155 |
const hideNoCoursesWithEventsMessage = function(root) {
|
|
|
156 |
root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden');
|
|
|
157 |
};
|
|
|
158 |
|
|
|
159 |
/**
|
|
|
160 |
* Render the course items HTML to the page.
|
|
|
161 |
*
|
|
|
162 |
* @param {object} root The rool element.
|
|
|
163 |
* @param {string} html The course items HTML to render.
|
|
|
164 |
* @param {boolean} append Whether the HTML should be appended (eg pressed "show more courses").
|
|
|
165 |
* Defaults to false - replaces the existing content (eg when modifying filter values).
|
|
|
166 |
*/
|
|
|
167 |
var renderCourseItemsHTML = function(root, html, append = false) {
|
|
|
168 |
var container = root.find(SELECTORS.COURSES_LIST);
|
|
|
169 |
|
|
|
170 |
if (append) {
|
|
|
171 |
Templates.appendNodeContents(container, html, '');
|
|
|
172 |
} else {
|
|
|
173 |
Templates.replaceNodeContents(container, html, '');
|
|
|
174 |
}
|
|
|
175 |
};
|
|
|
176 |
|
|
|
177 |
/**
|
|
|
178 |
* Return the offset value for fetching courses.
|
|
|
179 |
*
|
|
|
180 |
* @param {object} root The rool element.
|
|
|
181 |
* @return {Number}
|
|
|
182 |
*/
|
|
|
183 |
var getOffset = function(root) {
|
|
|
184 |
return parseInt(root.attr('data-offset'), 10);
|
|
|
185 |
};
|
|
|
186 |
|
|
|
187 |
/**
|
|
|
188 |
* Set the offset value for fetching courses.
|
|
|
189 |
*
|
|
|
190 |
* @param {object} root The rool element.
|
|
|
191 |
* @param {Number} offset Offset value.
|
|
|
192 |
*/
|
|
|
193 |
var setOffset = function(root, offset) {
|
|
|
194 |
root.attr('data-offset', offset);
|
|
|
195 |
};
|
|
|
196 |
|
|
|
197 |
/**
|
|
|
198 |
* Return the limit value for fetching courses.
|
|
|
199 |
*
|
|
|
200 |
* @param {object} root The rool element.
|
|
|
201 |
* @return {Number}
|
|
|
202 |
*/
|
|
|
203 |
var getLimit = function(root) {
|
|
|
204 |
return parseInt(root.attr('data-limit'), 10);
|
|
|
205 |
};
|
|
|
206 |
|
|
|
207 |
/**
|
|
|
208 |
* Return the days offset value for fetching events.
|
|
|
209 |
*
|
|
|
210 |
* @param {object} root The rool element.
|
|
|
211 |
* @return {Number}
|
|
|
212 |
*/
|
|
|
213 |
var getDaysOffset = function(root) {
|
|
|
214 |
return parseInt(root.attr('data-days-offset'), 10);
|
|
|
215 |
};
|
|
|
216 |
|
|
|
217 |
/**
|
|
|
218 |
* Return the days limit value for fetching events. The days
|
|
|
219 |
* limit is optional so undefined will be returned if it isn't
|
|
|
220 |
* set.
|
|
|
221 |
*
|
|
|
222 |
* @param {object} root The rool element.
|
|
|
223 |
* @return {int|undefined}
|
|
|
224 |
*/
|
|
|
225 |
var getDaysLimit = function(root) {
|
|
|
226 |
var daysLimit = root.attr('data-days-limit');
|
|
|
227 |
return daysLimit != undefined ? parseInt(daysLimit, 10) : undefined;
|
|
|
228 |
};
|
|
|
229 |
|
|
|
230 |
/**
|
|
|
231 |
* Return the timestamp for the user's midnight.
|
|
|
232 |
*
|
|
|
233 |
* @param {object} root The rool element.
|
|
|
234 |
* @return {Number}
|
|
|
235 |
*/
|
|
|
236 |
var getMidnight = function(root) {
|
|
|
237 |
return parseInt(root.attr('data-midnight'), 10);
|
|
|
238 |
};
|
|
|
239 |
|
|
|
240 |
/**
|
|
|
241 |
* Return the start time for fetching events. This is calculated
|
|
|
242 |
* based on the user's midnight value so that timezones are
|
|
|
243 |
* preserved.
|
|
|
244 |
*
|
|
|
245 |
* @param {object} root The rool element.
|
|
|
246 |
* @return {Number}
|
|
|
247 |
*/
|
|
|
248 |
var getStartTime = function(root) {
|
|
|
249 |
var midnight = getMidnight(root);
|
|
|
250 |
var daysOffset = getDaysOffset(root);
|
|
|
251 |
return midnight + (daysOffset * SECONDS_IN_DAY);
|
|
|
252 |
};
|
|
|
253 |
|
|
|
254 |
/**
|
|
|
255 |
* Return the end time for fetching events. This is calculated
|
|
|
256 |
* based on the user's midnight value so that timezones are
|
|
|
257 |
* preserved, unless filtering by overdue, where the current UNIX timestamp is used.
|
|
|
258 |
*
|
|
|
259 |
* @param {object} root The rool element.
|
|
|
260 |
* @return {Number}
|
|
|
261 |
*/
|
|
|
262 |
var getEndTime = function(root) {
|
|
|
263 |
let endTime = null;
|
|
|
264 |
|
|
|
265 |
if (root.attr('data-filter-overdue')) {
|
|
|
266 |
// If filtering by overdue, end time will be the current timestamp in seconds.
|
|
|
267 |
endTime = Math.floor(Date.now() / 1000);
|
|
|
268 |
} else {
|
|
|
269 |
const midnight = getMidnight(root);
|
|
|
270 |
const daysLimit = getDaysLimit(root);
|
|
|
271 |
|
|
|
272 |
if (daysLimit != undefined) {
|
|
|
273 |
endTime = midnight + (daysLimit * SECONDS_IN_DAY);
|
|
|
274 |
}
|
|
|
275 |
}
|
|
|
276 |
|
|
|
277 |
return endTime;
|
|
|
278 |
};
|
|
|
279 |
|
|
|
280 |
/**
|
|
|
281 |
* Get a list of events for the given course ids. Returns a promise that will
|
|
|
282 |
* be resolved with the events.
|
|
|
283 |
*
|
|
|
284 |
* @param {array} courseIds The list of course ids to fetch events for.
|
|
|
285 |
* @param {Number} startTime Timestamp to fetch events from.
|
|
|
286 |
* @param {Number} limit Limit to the number of events (this applies per course, not total)
|
|
|
287 |
* @param {Number} endTime Timestamp to fetch events to.
|
|
|
288 |
* @param {string|undefined} searchValue Search value
|
|
|
289 |
* @return {object} jQuery promise.
|
|
|
290 |
*/
|
|
|
291 |
var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) {
|
|
|
292 |
var args = {
|
|
|
293 |
courseids: courseIds,
|
|
|
294 |
starttime: startTime,
|
|
|
295 |
limit: limit
|
|
|
296 |
};
|
|
|
297 |
|
|
|
298 |
if (endTime) {
|
|
|
299 |
args.endtime = endTime;
|
|
|
300 |
}
|
|
|
301 |
|
|
|
302 |
if (searchValue) {
|
|
|
303 |
args.searchvalue = searchValue;
|
|
|
304 |
}
|
|
|
305 |
|
|
|
306 |
return EventsRepository.queryByCourses(args);
|
|
|
307 |
};
|
|
|
308 |
|
|
|
309 |
/**
|
|
|
310 |
* Get the last time the events were reloaded.
|
|
|
311 |
*
|
|
|
312 |
* @param {object} root The rool element.
|
|
|
313 |
* @return {Number}
|
|
|
314 |
*/
|
|
|
315 |
var getEventReloadTime = function(root) {
|
|
|
316 |
return root.data('last-event-load-time');
|
|
|
317 |
};
|
|
|
318 |
|
|
|
319 |
/**
|
|
|
320 |
* Set the last time the events were reloaded.
|
|
|
321 |
*
|
|
|
322 |
* @param {object} root The rool element.
|
|
|
323 |
* @param {Number} time Timestamp in milliseconds.
|
|
|
324 |
*/
|
|
|
325 |
var setEventReloadTime = function(root, time) {
|
|
|
326 |
root.data('last-event-load-time', time);
|
|
|
327 |
};
|
|
|
328 |
|
|
|
329 |
/**
|
|
|
330 |
* Check if events have begun reloading since the given
|
|
|
331 |
* time.
|
|
|
332 |
*
|
|
|
333 |
* @param {object} root The rool element.
|
|
|
334 |
* @param {Number} time Timestamp in milliseconds.
|
|
|
335 |
* @return {bool}
|
|
|
336 |
*/
|
|
|
337 |
var hasReloadedEventsSince = function(root, time) {
|
|
|
338 |
return getEventReloadTime(root) > time;
|
|
|
339 |
};
|
|
|
340 |
|
|
|
341 |
/**
|
|
|
342 |
* Send a request to the server to load the events for the courses.
|
|
|
343 |
*
|
|
|
344 |
* @param {array} courses List of course objects.
|
|
|
345 |
* @param {Number} startTime Timestamp to load events after.
|
|
|
346 |
* @param {int|undefined} endTime Timestamp to load events up until.
|
|
|
347 |
* @param {string|undefined} searchValue Search value
|
|
|
348 |
* @return {object} jQuery promise resolved with the events.
|
|
|
349 |
*/
|
|
|
350 |
var loadEventsForCourses = function(courses, startTime, endTime, searchValue) {
|
|
|
351 |
var courseIds = courses.map(function(course) {
|
|
|
352 |
return course.id;
|
|
|
353 |
});
|
|
|
354 |
|
|
|
355 |
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue);
|
|
|
356 |
};
|
|
|
357 |
|
|
|
358 |
/**
|
|
|
359 |
* Render the courses in the DOM once the server has returned the courses.
|
|
|
360 |
*
|
|
|
361 |
* @param {array} courses List of course objects.
|
|
|
362 |
* @param {object} root The root element
|
|
|
363 |
* @param {Number} midnight The midnight timestamp in the user's timezone.
|
|
|
364 |
* @param {Number} daysOffset Number of days from today to offset the events.
|
|
|
365 |
* @param {Number} daysLimit Number of days from today to limit the events to.
|
|
|
366 |
* @param {boolean} append Whether new content should be appended instead of replaced (eg "show more courses").
|
|
|
367 |
* @return {object} jQuery promise resolved after rendering is complete.
|
|
|
368 |
*/
|
|
|
369 |
var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) {
|
|
|
370 |
// Render the courses template.
|
|
|
371 |
return Templates.render(TEMPLATES.COURSE_ITEMS, {
|
|
|
372 |
courses: courses,
|
|
|
373 |
midnight: midnight,
|
|
|
374 |
hasdaysoffset: true,
|
|
|
375 |
hasdayslimit: daysLimit != undefined,
|
|
|
376 |
daysoffset: daysOffset,
|
|
|
377 |
dayslimit: daysLimit,
|
|
|
378 |
nodayslimit: daysLimit == undefined,
|
|
|
379 |
courseview: true,
|
|
|
380 |
hascourses: true
|
|
|
381 |
}).then(function(html) {
|
|
|
382 |
hideLoadingPlaceholder(root);
|
|
|
383 |
|
|
|
384 |
if (html) {
|
|
|
385 |
// Template rendering is complete and we have the HTML so we can
|
|
|
386 |
// add it to the DOM.
|
|
|
387 |
renderCourseItemsHTML(root, html, append);
|
|
|
388 |
}
|
|
|
389 |
|
|
|
390 |
return html;
|
|
|
391 |
})
|
|
|
392 |
.then(function(html) {
|
|
|
393 |
if (courses.length < COURSE_LIMIT) {
|
|
|
394 |
// We know there aren't any more courses because we got back less
|
|
|
395 |
// than we asked for so hide the button to request more.
|
|
|
396 |
hideMoreCoursesButton(root);
|
|
|
397 |
} else {
|
|
|
398 |
// Make sure the button is visible if there are more courses to load.
|
|
|
399 |
showMoreCoursesButton(root);
|
|
|
400 |
}
|
|
|
401 |
|
|
|
402 |
return html;
|
|
|
403 |
})
|
|
|
404 |
.catch(function() {
|
|
|
405 |
hideLoadingPlaceholder(root);
|
|
|
406 |
});
|
|
|
407 |
};
|
|
|
408 |
|
|
|
409 |
/**
|
|
|
410 |
* Find all of the visible course blocks and initialise the event
|
|
|
411 |
* list module to being loading the events for the course block.
|
|
|
412 |
*
|
|
|
413 |
* @param {object} root The root element for the timeline courses view.
|
|
|
414 |
* @param {boolean} append Whether content should be appended instead of replaced (eg "show more courses"). False by default.
|
|
|
415 |
* @return {object} jQuery promise resolved with courses and events.
|
|
|
416 |
*/
|
|
|
417 |
var loadMoreCourses = function(root, append = false) {
|
|
|
418 |
const pendingPromise = new Pending('block/timeline:load-more-courses');
|
|
|
419 |
var offset = getOffset(root);
|
|
|
420 |
var limit = getLimit(root);
|
|
|
421 |
const startTime = getStartTime(root);
|
|
|
422 |
const endTime = getEndTime(root);
|
|
|
423 |
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
|
|
|
424 |
|
|
|
425 |
// Start loading the next set of courses.
|
|
|
426 |
// Fetch up to limit number of courses with at least one action event in the time filtering specified.
|
|
|
427 |
// Courses without events will also be fetched, but hidden in case they have events in other timespans.
|
|
|
428 |
return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification(
|
|
|
429 |
COURSE_CLASSIFICATION,
|
|
|
430 |
limit,
|
|
|
431 |
offset,
|
|
|
432 |
COURSE_SORT,
|
|
|
433 |
searchValue,
|
|
|
434 |
startTime,
|
|
|
435 |
endTime
|
|
|
436 |
).then(function(result) {
|
|
|
437 |
var startEventLoadingTime = Date.now();
|
|
|
438 |
var courses = result.courses;
|
|
|
439 |
var nextOffset = result.nextoffset;
|
|
|
440 |
var daysOffset = getDaysOffset(root);
|
|
|
441 |
var daysLimit = getDaysLimit(root);
|
|
|
442 |
var midnight = getMidnight(root);
|
|
|
443 |
const moreCoursesAvailable = result.morecoursesavailable;
|
|
|
444 |
|
|
|
445 |
// Record the next offset if we want to request more courses.
|
|
|
446 |
setOffset(root, nextOffset);
|
|
|
447 |
// Load the events for these courses.
|
|
|
448 |
var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);
|
|
|
449 |
// Render the courses in the DOM.
|
|
|
450 |
var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append);
|
|
|
451 |
|
|
|
452 |
return $.when(eventsPromise, renderPromise)
|
|
|
453 |
.then(function(eventsByCourse) {
|
|
|
454 |
if (hasReloadedEventsSince(root, startEventLoadingTime)) {
|
|
|
455 |
// All of the events are being reloaded so ignore our results.
|
|
|
456 |
return eventsByCourse;
|
|
|
457 |
}
|
|
|
458 |
|
|
|
459 |
if (courses.length > 0) {
|
|
|
460 |
// Render the events in the correct course event list.
|
|
|
461 |
courses.forEach(function(course) {
|
|
|
462 |
const courseId = course.id;
|
|
|
463 |
const containerSelector = '[data-region="course-events-container"][data-course-id="' + courseId + '"]';
|
|
|
464 |
const courseEventsContainer = root.find(containerSelector);
|
|
|
465 |
const eventListRoot = courseEventsContainer.find(EventList.rootSelector);
|
|
|
466 |
|
|
|
467 |
EventList.init(eventListRoot, additionalConfig);
|
|
|
468 |
});
|
|
|
469 |
|
|
|
470 |
if (!moreCoursesAvailable) {
|
|
|
471 |
// If no more courses with events matching the current filtering exist, hide the more courses button.
|
|
|
472 |
hideMoreCoursesButton(root);
|
|
|
473 |
} else {
|
|
|
474 |
// If more courses exist with events matching the current filtering, show the more courses button.
|
|
|
475 |
showMoreCoursesButton(root);
|
|
|
476 |
}
|
|
|
477 |
} else {
|
|
|
478 |
// No more courses to load, hide the more courses button.
|
|
|
479 |
hideMoreCoursesButton(root);
|
|
|
480 |
|
|
|
481 |
// A zero offset means this was not loading "more courses", so we need to display the no results message.
|
|
|
482 |
if (offset == 0) {
|
|
|
483 |
showNoCoursesWithEventsMessage(root);
|
|
|
484 |
}
|
|
|
485 |
}
|
|
|
486 |
|
|
|
487 |
return eventsByCourse;
|
|
|
488 |
});
|
|
|
489 |
}).then(() => {
|
|
|
490 |
return pendingPromise.resolve();
|
|
|
491 |
}).catch(Notification.exception);
|
|
|
492 |
};
|
|
|
493 |
|
|
|
494 |
/**
|
|
|
495 |
* Add event listeners to load more courses for the courses view.
|
|
|
496 |
*
|
|
|
497 |
* @param {object} root The root element for the timeline courses view.
|
|
|
498 |
*/
|
|
|
499 |
var registerEventListeners = function(root) {
|
|
|
500 |
CustomEvents.define(root, [CustomEvents.events.activate]);
|
|
|
501 |
// Show more courses and load their events when the user clicks the "more courses" button.
|
|
|
502 |
root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) {
|
|
|
503 |
enableMoreCoursesButtonLoading(root);
|
|
|
504 |
loadMoreCourses(root, true)
|
|
|
505 |
.then(function() {
|
|
|
506 |
disableMoreCoursesButtonLoading(root);
|
|
|
507 |
return;
|
|
|
508 |
})
|
|
|
509 |
.catch(function() {
|
|
|
510 |
disableMoreCoursesButtonLoading(root);
|
|
|
511 |
});
|
|
|
512 |
|
|
|
513 |
if (data) {
|
|
|
514 |
data.originalEvent.preventDefault();
|
|
|
515 |
data.originalEvent.stopPropagation();
|
|
|
516 |
}
|
|
|
517 |
e.stopPropagation();
|
|
|
518 |
});
|
|
|
519 |
};
|
|
|
520 |
|
|
|
521 |
/**
|
|
|
522 |
* Initialise the timeline courses view. Begin loading the events
|
|
|
523 |
* if this view is active. Add the relevant event listeners.
|
|
|
524 |
*
|
|
|
525 |
* This function should only be called once per page load because it
|
|
|
526 |
* is adding event listeners to the page.
|
|
|
527 |
*
|
|
|
528 |
* @param {object} root The root element for the timeline courses view.
|
|
|
529 |
*/
|
|
|
530 |
var init = function(root) {
|
|
|
531 |
root = $(root);
|
|
|
532 |
|
|
|
533 |
// Only need to handle course loading if the user is actively enrolled in a course.
|
|
|
534 |
if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
|
|
|
535 |
setEventReloadTime(root, Date.now());
|
|
|
536 |
|
|
|
537 |
if (root.hasClass('active')) {
|
|
|
538 |
// Only load if this is active otherwise it will be lazy loaded later.
|
|
|
539 |
loadMoreCourses(root);
|
|
|
540 |
root.attr('data-seen', true);
|
|
|
541 |
}
|
|
|
542 |
|
|
|
543 |
registerEventListeners(root);
|
|
|
544 |
}
|
|
|
545 |
};
|
|
|
546 |
|
|
|
547 |
/**
|
|
|
548 |
* Reset the element back to it's initial state. Begin loading the events again
|
|
|
549 |
* if this view is active.
|
|
|
550 |
*
|
|
|
551 |
* @param {object} root The root element for the timeline courses view.
|
|
|
552 |
*/
|
|
|
553 |
var reset = function(root) {
|
|
|
554 |
|
|
|
555 |
setOffset(root, 0);
|
|
|
556 |
showLoadingPlaceholder(root);
|
|
|
557 |
hideNoCoursesWithEventsMessage(root);
|
|
|
558 |
root.removeAttr('data-seen');
|
|
|
559 |
|
|
|
560 |
if (root.hasClass('active')) {
|
|
|
561 |
shown(root);
|
|
|
562 |
}
|
|
|
563 |
};
|
|
|
564 |
|
|
|
565 |
/**
|
|
|
566 |
* Begin loading the events unless we know there are no actively enrolled courses.
|
|
|
567 |
*
|
|
|
568 |
* @param {object} root The root element for the timeline courses view.
|
|
|
569 |
*/
|
|
|
570 |
var shown = function(root) {
|
|
|
571 |
if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
|
|
|
572 |
loadMoreCourses(root);
|
|
|
573 |
root.attr('data-seen', true);
|
|
|
574 |
}
|
|
|
575 |
};
|
|
|
576 |
|
|
|
577 |
return {
|
|
|
578 |
init: init,
|
|
|
579 |
reset: reset,
|
|
|
580 |
shown: shown
|
|
|
581 |
};
|
|
|
582 |
});
|