Commit 41080050 authored by Damyon Wiese's avatar Damyon Wiese
Browse files

Merge branch 'MDL-60558-master' of git://github.com/andrewnicols/moodle

parents ae348e78 e3491a09
......@@ -61,7 +61,7 @@ class block_calendar_upcoming extends block_base {
$courses = [$course->id => $course];
}
$calendar = new calendar_information(0, 0, 0, time());
$calendar->set_sources($course, $courses);
$calendar->set_sources($course, $courses, $this->page->category);
list($data, $template) = calendar_get_view($calendar, 'upcoming_mini');
......@@ -78,5 +78,3 @@ class block_calendar_upcoming extends block_base {
return $this->content;
}
}
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -63,8 +63,6 @@ define([
var SELECTORS = {
ROOT: "[data-region='calendar']",
DAY: "[data-region='day']",
EVENT_ITEM: "[data-region='event-item']",
EVENT_LINK: "[data-action='view-event']",
NEW_EVENT_BUTTON: "[data-action='new-event-button']",
DAY_CONTENT: "[data-region='day-content']",
LOADING_ICON: '.loading-icon',
......@@ -73,93 +71,6 @@ define([
TODAY: '.today',
};
/**
* Get the event type lang string.
*
* @param {String} eventType The event type.
* @return {promise} The lang string promise.
*/
var getEventType = function(eventType) {
var lang = 'type' + eventType;
return Str.get_string(lang, 'core_calendar').then(function(langStr) {
return langStr;
});
};
/**
* Get the CSS class to apply for the given event type.
*
* @param {String} eventType The calendar event type
* @return {String}
*/
var getEventTypeClassFromType = function(eventType) {
switch (eventType) {
case 'user':
return 'calendar_event_user';
case 'site':
return 'calendar_event_site';
case 'group':
return 'calendar_event_group';
case 'category':
return 'calendar_event_category';
case 'course':
return 'calendar_event_course';
default:
return 'calendar_event_course';
}
};
/**
* Render the event summary modal.
*
* @param {Number} eventId The calendar event id.
*/
var renderEventSummaryModal = function(eventId) {
var typeClass = '';
// Calendar repository promise.
CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
if (!getEventResponse.event) {
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
}
var eventData = getEventResponse.event;
typeClass = getEventTypeClassFromType(eventData.eventtype);
return getEventType(eventData.eventtype).then(function(eventType) {
eventData.eventtype = eventType;
return eventData;
});
}).then(function(eventData) {
// Build the modal parameters from the event data.
var modalParams = {
title: eventData.name,
type: SummaryModal.TYPE,
body: Templates.render('core_calendar/event_summary_body', eventData),
templateContext: {
canedit: eventData.canedit,
candelete: eventData.candelete,
headerclasses: typeClass,
isactionevent: eventData.isactionevent,
url: eventData.url
}
};
// Create the modal.
return ModalFactory.create(modalParams);
}).done(function(modal) {
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
// Finally, render the modal!
modal.show();
}).fail(Notification.exception);
};
/**
* Handler for the drag and drop move event. Provides a loading indicator
* while the request is sent to the server to update the event start date.
......@@ -252,19 +163,7 @@ define([
CalendarViewManager.reloadCurrentMonth(root);
});
eventFormModalPromise
.then(function(modal) {
// When something within the calendar tells us the user wants
// to edit an event then show the event form modal.
body.on(CalendarEvents.editEvent, function(e, eventId) {
var calendarWrapper = root.find(CalendarSelectors.wrapper);
modal.setEventId(eventId);
modal.setContextId(calendarWrapper.data('contextId'));
modal.show();
});
return;
})
.fail(Notification.exception);
CalendarCrud.registerEditListeners(root, eventFormModalPromise);
};
/**
......@@ -273,27 +172,6 @@ define([
* @param {object} root The calendar root element
*/
var registerEventListeners = function(root) {
// Bind click events to event links.
root.on('click', SELECTORS.EVENT_ITEM, function(e) {
e.preventDefault();
// We've handled the event so stop it from bubbling
// and causing the day click handler to fire.
e.stopPropagation();
var target = $(e.target);
var eventId = null;
var eventLink = target.closest(SELECTORS.EVENT_LINK);
if (eventLink.length) {
eventId = eventLink.data('eventId');
} else {
eventId = target.find(SELECTORS.EVENT_LINK).data('eventId');
}
renderEventSummaryModal(eventId);
});
root.on('change', CalendarSelectors.elements.courseSelector, function() {
var selectElement = $(this);
var courseId = selectElement.val();
......
......@@ -51,7 +51,6 @@ define([
var registerEventListeners = function(root, type) {
var body = $('body');
CalendarCrud.registerEventFormModal(root);
CalendarCrud.registerRemove(root);
var reloadFunction = 'reloadCurrent' + type.charAt(0).toUpperCase() + type.slice(1);
......@@ -74,6 +73,11 @@ define([
// We need to get the selector again because the content has changed.
return root.find(CalendarSelectors.courseSelector).val(courseId);
})
.then(function() {
window.history.pushState({}, '', '?view=upcoming&course=' + courseId);
return;
})
.fail(Notification.exception);
});
......@@ -85,6 +89,9 @@ define([
daysWithEvent.removeClass('hidden');
}
});
var eventFormPromise = CalendarCrud.registerEventFormModal(root);
CalendarCrud.registerEditListeners(root, eventFormPromise);
};
return {
......
......@@ -198,6 +198,8 @@ function(
modal.setContextId(calendarWrapper.data('contextId'));
modal.show();
e.stopImmediatePropagation();
return;
}).fail(Notification.exception);
});
......@@ -223,8 +225,36 @@ function(
});
}
/**
* Register the listeners required to edit the event.
*
* @param {jQuery} root
* @param {Promise} eventFormModalPromise
* @returns {Promise}
*/
function registerEditListeners(root, eventFormModalPromise) {
eventFormModalPromise
.then(function(modal) {
// When something within the calendar tells us the user wants
// to edit an event then show the event form modal.
$('body').on(CalendarEvents.editEvent, function(e, eventId) {
var calendarWrapper = root.find(CalendarSelectors.wrapper);
modal.setEventId(eventId);
modal.setContextId(calendarWrapper.data('contextId'));
modal.show();
e.stopImmediatePropagation();
});
return;
})
.fail(Notification.exception);
return eventFormModalPromise;
}
return {
registerRemove: registerRemove,
registerEditListeners: registerEditListeners,
registerEventFormModal: registerEventFormModal
};
});
......@@ -164,13 +164,15 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
*
* @method getCalendarUpcomingData
* @param {Number} courseid The course id.
* @param {Number} categoryid The category id.
* @return {promise} Resolved with the month view data.
*/
var getCalendarUpcomingData = function(courseid) {
var getCalendarUpcomingData = function(courseid, categoryid) {
var request = {
methodname: 'core_calendar_get_calendar_upcoming_view',
args: {
courseid: courseid,
categoryid: categoryid,
}
};
......
......@@ -46,6 +46,7 @@ define([], function() {
create: '[data-action="new-event-button"]',
edit: '[data-action="edit"]',
remove: '[data-action="delete"]',
viewEvent: '[data-action="view-event"]',
},
elements: {
courseSelector: 'select[name="course"]',
......@@ -54,5 +55,12 @@ define([], function() {
day: '[data-region="day"]',
wrapper: '.calendarwrapper',
eventItem: '[data-type="event"]',
links: {
navLink: '.calendarwrapper .arrow_link',
eventLink: "[data-region='event-item']",
},
containers: {
loadingIcon: '[data-region="overlay-icon-container"]',
},
};
});
......@@ -24,24 +24,27 @@
define([
'jquery',
'core/templates',
'core/str',
'core/notification',
'core_calendar/repository',
'core_calendar/events',
'core_calendar/selectors',
'core/modal_factory',
'core/modal_events',
'core_calendar/summary_modal',
], function(
$,
Templates,
Str,
Notification,
CalendarRepository,
CalendarEvents,
CalendarSelectors
CalendarSelectors,
ModalFactory,
ModalEvents,
SummaryModal
) {
var SELECTORS = {
CALENDAR_NAV_LINK: ".calendarwrapper .arrow_link",
LOADING_ICON_CONTAINER: '[data-region="overlay-icon-container"]'
};
/**
* Register event listeners for the module.
*
......@@ -50,7 +53,38 @@ define([
var registerEventListeners = function(root) {
root = $(root);
root.on('click', SELECTORS.CALENDAR_NAV_LINK, function(e) {
// Bind click events to event links.
root.on('click', CalendarSelectors.links.eventLink, function(e) {
var target = $(e.target);
var eventId = null;
var eventLink;
if (target.is(CalendarSelectors.actions.viewEvent)) {
eventLink = target;
} else {
eventLink = target.closest(CalendarSelectors.actions.viewEvent);
}
if (eventLink.length) {
eventId = eventLink.data('eventId');
} else {
eventId = target.find(CalendarSelectors.actions.viewEvent).data('eventId');
}
if (eventId) {
// A link was found. Show the modal.
e.preventDefault();
// We've handled the event so stop it from bubbling
// and causing the day click handler to fire.
e.stopPropagation();
renderEventSummaryModal(eventId);
}
});
root.on('click', CalendarSelectors.links.navLink, function(e) {
var wrapper = root.find(CalendarSelectors.wrapper);
var view = wrapper.data('view');
var courseId = wrapper.data('courseid');
......@@ -249,7 +283,7 @@ define([
* @method startLoading
*/
var startLoading = function(root) {
var loadingIconContainer = root.find(SELECTORS.LOADING_ICON_CONTAINER);
var loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
loadingIconContainer.removeClass('hidden');
};
......@@ -261,7 +295,7 @@ define([
* @method stopLoading
*/
var stopLoading = function(root) {
var loadingIconContainer = root.find(SELECTORS.LOADING_ICON_CONTAINER);
var loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
loadingIconContainer.addClass('hidden');
};
......@@ -271,23 +305,27 @@ define([
*
* @param {object} root The container element.
* @param {Number} courseId The course id.
* @param {Number} categoryId The id of the category whose events are shown
* @return {promise}
*/
var reloadCurrentUpcoming = function(root, courseId) {
var reloadCurrentUpcoming = function(root, courseId, categoryId) {
startLoading(root);
var target = root.find(CalendarSelectors.wrapper);
if (!courseId) {
if (typeof courseId === 'undefined') {
courseId = root.find(CalendarSelectors.wrapper).data('courseid');
}
return CalendarRepository.getCalendarUpcomingData(courseId)
if (typeof categoryId === 'undefined') {
categoryId = root.find(CalendarSelectors.wrapper).data('categoryid');
}
return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)
.then(function(context) {
return Templates.render(root.attr('data-template'), context);
})
.then(function(html, js) {
window.history.replaceState(null, null, '?view=upcoming&course=' + courseId);
return Templates.replaceNode(target, html, js);
})
.then(function() {
......@@ -300,6 +338,93 @@ define([
.fail(Notification.exception);
};
/**
* Get the CSS class to apply for the given event type.
*
* @param {String} eventType The calendar event type
* @return {String}
*/
var getEventTypeClassFromType = function(eventType) {
switch (eventType) {
case 'user':
return 'calendar_event_user';
case 'site':
return 'calendar_event_site';
case 'group':
return 'calendar_event_group';
case 'category':
return 'calendar_event_category';
case 'course':
return 'calendar_event_course';
default:
return 'calendar_event_course';
}
};
/**
* Render the event summary modal.
*
* @param {Number} eventId The calendar event id.
*/
var renderEventSummaryModal = function(eventId) {
var typeClass = '';
// Calendar repository promise.
CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
if (!getEventResponse.event) {
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
}
var eventData = getEventResponse.event;
typeClass = getEventTypeClassFromType(eventData.eventtype);
return getEventType(eventData.eventtype).then(function(eventType) {
eventData.eventtype = eventType;
return eventData;
});
}).then(function(eventData) {
// Build the modal parameters from the event data.
var modalParams = {
title: eventData.name,
type: SummaryModal.TYPE,
body: Templates.render('core_calendar/event_summary_body', eventData),
templateContext: {
canedit: eventData.canedit,
candelete: eventData.candelete,
headerclasses: typeClass,
isactionevent: eventData.isactionevent,
url: eventData.url
}
};
// Create the modal.
return ModalFactory.create(modalParams);
}).done(function(modal) {
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
// Finally, render the modal!
modal.show();
}).fail(Notification.exception);
};
/**
* Get the event type lang string.
*
* @param {String} eventType The event type.
* @return {promise} The lang string promise.
*/
var getEventType = function(eventType) {
var lang = 'type' + eventType;
return Str.get_string(lang, 'core_calendar').then(function(langStr) {
return langStr;
});
};
return {
init: function(root) {
registerEventListeners(root);
......
......@@ -82,6 +82,11 @@ class calendar_upcoming_exporter extends exporter {
'courseid' => [
'type' => PARAM_INT,
],
'categoryid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => 0,
],
];
}
......@@ -127,6 +132,11 @@ class calendar_upcoming_exporter extends exporter {
}
$return['filter_selector'] = $this->get_course_filter_selector($output);
$return['courseid'] = $this->calendar->courseid;
if ($this->calendar->categoryid) {
$return['categoryid'] = $this->calendar->categoryid;
}
return $return;
}
......
......@@ -1154,12 +1154,26 @@ class core_calendar_external extends external_api {
// Parameter validation.
self::validate_parameters(self::get_calendar_upcoming_view_parameters(), [
'courseid' => $courseid,
'categoryid' => $categoryid,
]);
$PAGE->set_url('/calendar/');
$category = null;
if ($courseid != SITEID && !empty($courseid)) {
// Course ID must be valid and existing.
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$courses = [$course->id => $course];
} else if (!empty($categoryid)) {
$course = get_site();
$courses = calendar_get_default_courses();
$category = \coursecat::get($categoryid);
$ids += $category->get_parents();
$categories = \coursecat::get_many($ids);
$courses = array_filter($courses, function($course) use ($categories) {
return array_search($course->category, $categories) !== false;
});
$category = $category->get_db_record();
} else {
$course = get_site();
$courses = calendar_get_default_courses();
......@@ -1169,7 +1183,7 @@ class core_calendar_external extends external_api {
self::validate_context($context);
$calendar = new calendar_information(0, 0, 0, time());
$calendar->set_sources($course, $courses);
$calendar->set_sources($course, $courses, $category);
list($data, $template) = calendar_get_view($calendar, 'upcoming');
......@@ -1185,6 +1199,7 @@ class core_calendar_external extends external_api {
return new external_function_parameters(
[
'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
]
);
}
......
......@@ -3220,7 +3220,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
if ($view == "upcoming") {
$template = 'core_calendar/calendar_upcoming';
} else if ($view == "upcoming_mini") {
$template = 'core_calendar/upcoming_mini';
$template = 'core_calendar/calendar_upcoming_mini';
}
}
......
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_calendar/calendar_upcoming_block
Calendar upcoming view.
The purpose of this template is to render the calendar upcoming view.
Classes required for JS: