Commit c8b6e9ab authored by Andrew Nicols's avatar Andrew Nicols
Browse files

MDL-59392 calendar: Switch to templates in the cal block

This commit adds a set of templates for the calendar month block, and
the events filter. The same data exporters used in the main calendar
view are utilised and the same logic.

Some additional data was added to the exporters, and in the long term
this will be used in the main monthly view so I did not feel it was
prudent to strip it out into a different view of the model.

AMOS BEGIN
 MOV [monthnext,access],[monthnext,calendar]
AMOS END
parent 41fa6a24
......@@ -70,29 +70,26 @@ class block_calendar_month extends block_base {
if ($issite) {
// Being displayed at site level. This will cause the filter to fall back to auto-detecting
// the list of courses it will be grabbing events from.
$filtercourse = calendar_get_default_courses();
$course = get_site();
$courses = calendar_get_default_courses();
} else {
// Forcibly filter events to include only those from the particular course we are in.
$filtercourse = array($courseid => $this->page->course);
$course = $this->page->course;
$courses = [$course->id => $course];
}
list($courses, $group, $user) = calendar_set_filters($filtercourse);
if ($issite) {
// For the front page.
$this->content->text .= calendar_get_mini($courses, $group, $user, false, false,
'frontpage', $courseid, $time);
// No filters for now.
} else {
// For any other course.
$this->content->text .= calendar_get_mini($courses, $group, $user, false, false,
'course', $courseid, $time);
$this->content->text .= '<h3 class="eventskey">'.get_string('eventskey', 'calendar').'</h3>';
$this->content->text .= '<div class="filters calendar_filters">' .
calendar_filter_controls($this->page->url) . '</div>';
$renderer = $this->page->get_renderer('core_calendar');
$calendar = new calendar_information(0, 0, 0, $time);
$calendar->prepare_for_view($course, $courses);
list($data, $template) = calendar_get_view($calendar, 'mini');
$this->content->text .= $renderer->render_from_template($template, $data);
if (!$issite) {
$this->content->text .= $renderer->event_filter();
}
return $this->content;
}
}
......@@ -23,7 +23,7 @@ Feature: Enable the calendar block in a course and test it's functionality
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I add the "Calendar" block
Then I should see "Events key" in the "Calendar" "block"
Then "Calendar" "block" should exist
@javascript
Scenario: View a global event in the calendar block
......@@ -136,9 +136,14 @@ Feature: Enable the calendar block in a course and test it's functionality
And I press "Save and display"
And I turn editing mode on
And I add the "Calendar" block
And I create a calendar event with form data:
And I click on "This month" "link"
And I click on "New event" "button"
And I set the following fields to these values:
| id_eventtype | Group |
| id_name | Group Event |
And I set the following fields to these values:
| Group | Group 1 |
And I press "Save"
And I log out
Then I log in as "student1"
And I am on "Course 1" course homepage
......@@ -150,7 +155,7 @@ Feature: Enable the calendar block in a course and test it's functionality
And I hover over today in the calendar
And I should not see "Group Event"
@javascript
@javascript @arn
Scenario: Filter group events in the calendar block
Given the following "groups" exist:
| name | course | idnumber |
......@@ -173,9 +178,14 @@ Feature: Enable the calendar block in a course and test it's functionality
| id_eventtype | Course |
| id_name | Course Event 1 |
And I am on "Course 1" course homepage
And I create a calendar event with form data:
And I click on "This month" "link"
And I click on "New event" "button"
And I set the following fields to these values:
| id_eventtype | Group |
| id_name | Group Event 1 |
And I set the following fields to these values:
| Group | Group 1 |
And I press "Save"
And I log out
Then I log in as "student1"
And I am on "Course 1" course homepage
......
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.
// 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/>.
/**
* This module is responsible for the calendar filter.
*
* @module core_calendar/calendar_filter
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/selectors',
'core_calendar/events',
'core/str',
'core/templates',
],
function(
$,
CalendarSelectors,
CalendarEvents,
Str,
Templates
) {
var registerEventListeners = function(root) {
root.on('click', CalendarSelectors.eventFilterItem, function(e) {
var target = $(e.currentTarget);
toggleFilter(target);
e.preventDefault();
});
$('body').on(CalendarEvents.viewUpdated, function() {
var filters = root.find(CalendarSelectors.eventFilterItem);
filters.each(function(i, filter) {
filter = $(filter);
if (filter.data('eventtype-hidden')) {
var data = getFilterData(filter);
fireFilterChangedEvent(data);
}
});
});
};
var toggleFilter = function(target) {
var data = getFilterData(target);
// Toggle the hidden. We need to render the template before we change the value.
data.hidden = !data.hidden;
return Str.get_string('eventtype' + data.type, 'calendar')
.then(function(nameStr) {
data.name = nameStr;
return data;
})
.then(function(context) {
return Templates.render('core_calendar/event_filter_key', context);
})
.then(function(html, js) {
return Templates.replaceNode(target, html, js);
})
.then(function() {
fireFilterChangedEvent(data);
return;
});
};
/**
* Fire the filterChanged event for the specified data.
*
* @param {object} data The data to include
*/
var fireFilterChangedEvent = function(data) {
M.util.js_pending("month-mini-filterChanged");
$('body').trigger(CalendarEvents.filterChanged, {
type: data.type,
hidden: data.hidden,
});
M.util.js_complete("month-mini-filterChanged");
};
/**
* Get the filter data for the specified target.
*
* @param {jQuery} target The target node
* @return {Object}
*/
var getFilterData = function(target) {
return {
type: target.data('eventtype'),
hidden: target.data('eventtype-hidden'),
};
};
return {
init: function(root) {
root = $(root);
registerEventListeners(root);
}
};
});
// 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/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module core_calendar/calendar
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/selectors',
'core_calendar/events',
'core_calendar/view_manager',
],
function(
$,
CalendarSelectors,
CalendarEvents,
CalendarViewManager
) {
var registerEventListeners = function(root) {
$('body').on(CalendarEvents.filterChanged, function(e, data) {
var daysWithEvent = root.find(CalendarSelectors.eventType[data.type]);
daysWithEvent.toggleClass('calendar_event_' + data.type, !data.hidden);
});
};
return {
init: function(root) {
root = $(root);
CalendarViewManager.init(root);
registerEventListeners(root);
}
};
});
......@@ -31,6 +31,8 @@ define([], function() {
editActionEvent: 'calendar-events:edit_action_event',
eventMoved: 'calendar-events:event_moved',
monthChanged: 'calendar-events:month_changed',
moveEvent: 'calendar-events:move_event'
moveEvent: 'calendar-events:move_event',
filterChanged: 'calendar-events:filter_changed',
viewUpdated: 'calendar-events:view_updated',
};
});
// 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/>.
/**
* This module is responsible for the calendar filter.
*
* @module core_calendar/calendar_selectors
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
eventFilterItem: "[data-action='filter-event-type']",
eventType: {
site: "[data-eventtype-site]",
course: "[data-eventtype-course]",
group: "[data-eventtype-group]",
user: "[data-eventtype-user]",
},
popoverType: {
site: "[data-popover-eventtype-site]",
course: "[data-popover-eventtype-course]",
group: "[data-popover-eventtype-group]",
user: "[data-popover-eventtype-user]",
},
};
});
......@@ -65,6 +65,10 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito
.then(function(html, js) {
return Templates.replaceNode(root.find(SELECTORS.CALENDAR_MONTH_WRAPPER), html, js);
})
.then(function() {
$('body').trigger(CalendarEvents.viewUpdated);
return;
})
.always(function() {
return stopLoading(root);
})
......
......@@ -47,6 +47,16 @@ class calendar_event_exporter extends event_exporter_base {
$values = parent::define_other_properties();
$values['url'] = ['type' => PARAM_URL];
$values['islastday'] = [
'type' => PARAM_BOOL,
'default' => false,
];
$values['calendareventtype'] = [
'type' => PARAM_TEXT,
];
$values['popupname'] = [
'type' => PARAM_RAW,
];
return $values;
}
......@@ -58,6 +68,8 @@ class calendar_event_exporter extends event_exporter_base {
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
global $CFG;
$values = parent::get_other_values($output);
$eventid = $this->event->get_id();
......@@ -65,6 +77,55 @@ class calendar_event_exporter extends event_exporter_base {
$url = new \moodle_url($this->related['daylink'], [], "event_{$eventid}");
$values['url'] = $url->out(false);
$values['islastday'] = false;
$today = $this->related['type']->timestamp_to_date_array($this->related['today']);
$values['popupname'] = $this->event->get_name();
$times = $this->event->get_times();
if ($duration = $times->get_duration()) {
$enddate = $this->related['type']->timestamp_to_date_array($times->get_end_time()->getTimestamp());
$values['islastday'] = true;
$values['islastday'] = $values['islastday'] && $enddate['year'] == $today['year'];
$values['islastday'] = $values['islastday'] && $enddate['mon'] == $today['mon'];
$values['islastday'] = $values['islastday'] && $enddate['mday'] == $today['mday'];
}
$subscription = $this->event->get_subscription();
if ($subscription && !empty($subscription->get('id')) && $CFG->calendar_showicalsource) {
$a = (object) [
'name' => $values['popupname'],
'source' => $subscription->get('name'),
];
$values['popupname'] = get_string('namewithsource', 'calendar', $a);
} else {
if ($values['islastday']) {
$startdate = $this->related['type']->timestamp_to_date_array($times->get_start_time()->getTimestamp());
$samedate = true;
$samedate = $samedate && $startdate['mon'] == $enddate['mon'];
$samedate = $samedate && $startdate['year'] == $enddate['year'];
$samedate = $samedate && $startdate['mday'] == $enddate['mday'];
if (!$samedate) {
$values['popupname'] = get_string('eventendtimewrapped', 'calendar', $values['popupname']);
}
}
}
// Include course's shortname into the event name, if applicable.
$course = $this->event->get_course();
if ($course && $course->get('id') && $course->get('id') !== SITEID) {
$eventnameparams = (object) [
'name' => $values['popupname'],
'course' => format_string($course->get('shortname'), true, [
'context' => $this->related['context'],
])
];
$values['popupname'] = get_string('eventnameandcourse', 'calendar', $eventnameparams);
}
$values['calendareventtype'] = $this->get_calendar_event_type();
return $values;
}
......@@ -76,7 +137,24 @@ class calendar_event_exporter extends event_exporter_base {
protected static function define_related() {
$related = parent::define_related();
$related['daylink'] = \moodle_url::class;
$related['type'] = '\core_calendar\type_base';
$related['today'] = 'int';
return $related;
}
/**
* Return the normalised event type.
* Activity events are normalised to be course events.
*
* @return string
*/
public function get_calendar_event_type() {
$type = $this->event->get_type();
if ($type == 'open' || $type == 'close') {
$type = 'course';
}
return $type;
}
}
......@@ -87,6 +87,15 @@ class day_exporter extends exporter {
'yday' => [
'type' => PARAM_INT,
],
// These are additional params.
'istoday' => [
'type' => PARAM_BOOL,
'default' => false,
],
'isweekend' => [
'type' => PARAM_BOOL,
'default' => false,
],
];
}
......@@ -110,7 +119,19 @@ class day_exporter extends exporter {
'events' => [
'type' => calendar_event_exporter::read_properties_definition(),
'multiple' => true,
]
],
'calendareventtypes' => [
'type' => PARAM_RAW,
'multiple' => true,
],
'popovertitle' => [
'type' => PARAM_RAW,
'default' => '',
],
'haslastdayofevent' => [
'type' => PARAM_BOOL,
'default' => false,
],
];
}
......@@ -141,22 +162,46 @@ class day_exporter extends exporter {
$url = new moodle_url('/calendar/view.php', [
'view' => 'day',
'time' => $timestamp,
'course' => $this->calendar->course->id,
]);
$return['viewdaylink'] = $url->out(false);
$cache = $this->related['cache'];
$return['events'] = array_map(function($event) use ($cache, $output, $url) {
$eventexporters = array_map(function($event) use ($cache, $output, $url) {
$context = $cache->get_context($event);
$course = $cache->get_course($event);
$exporter = new calendar_event_exporter($event, [
'context' => $context,
'course' => $course,
'daylink' => $url,
'type' => $this->related['type'],
'today' => $this->data[0],
]);
return $exporter->export($output);
return $exporter;
}, $this->related['events']);
$return['events'] = array_map(function($exporter) use ($output) {
return $exporter->export($output);
}, $eventexporters);
if ($popovertitle = $this->get_popover_title()) {
$return['popovertitle'] = $popovertitle;
}
$return['calendareventtypes'] = array_map(function($exporter) {
return $exporter->get_calendar_event_type();
}, $eventexporters);
$return['calendareventtypes'] = array_values(array_unique($return['calendareventtypes']));
$return['haslastdayofevent'] = false;
foreach ($return['events'] as $event) {
if ($event->islastday) {
$return['haslastdayofevent'] = true;
break;
}
}
return $return;
}
......@@ -172,4 +217,26 @@ class day_exporter extends exporter {
'type' => '\core_calendar\type_base',
];
}
/**
* Get the title for this popover.
*
* @return string
*/
protected function get_popover_title() {
$title = null;
$userdate = userdate($this->data[0], get_string('strftimedayshort'));
if (count($this->related['events'])) {
$title = get_string('eventsfor', 'calendar', $userdate);
} else if ($this->data['istoday']) {
$title = $userdate;
}
if ($this->data['istoday']) {
$title = get_string('todayplustitle', 'calendar', $userdate);
}
return $title;
}
}
......@@ -76,7 +76,19 @@ class month_exporter extends exporter {
$related['type'] = $type;
parent::__construct([], $related);
$data = [
'url' => $this->url->out(false),
];
parent::__construct($data, $related);
}
protected static function define_properties() {
return [
'url' => [
'type' => PARAM_URL,
],
];
}
/**
......@@ -106,15 +118,38 @@ class month_exporter extends exporter {
'view' => [
'type' => PARAM_ALPHA,
],
'time' => [
'type' => PARAM_INT,
],
'periodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'previousperiod' => [
'type' => PARAM_INT,
],
'previousperiodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'nextperiod' => [
'type' => PARAM_INT,