mirror of
https://github.com/moodle/moodle.git
synced 2025-08-06 01:16:44 +02:00
MDL-59382 calendar: add modal to create and update events
This commit is contained in:
parent
6103fd2efe
commit
aa0912258d
23 changed files with 2392 additions and 157 deletions
2
calendar/amd/build/calendar.min.js
vendored
2
calendar/amd/build/calendar.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_factory","core_calendar/summary_modal","core/modal_events","core_calendar/calendar_repository"],function(a,b,c,d,e,f,g,h,i,j){var k={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']"},l=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},m=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?'<a href="'+a.url+'">'+b+"</a>":b})},n=function(b){j.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=l(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=m(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:h.TYPE,body:d.render("core_calendar/event_summary_body",a)};return a.caneditevent||(b.footer=""),g.create(b)}).done(function(a){a.getRoot().on(i.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},o=function(){a(k.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");n(c)})};return{init:function(){o()}}});
|
||||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},n=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},o=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?'<a href="'+a.url+'">'+b+"</a>":b})},p=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=n(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=o(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a)};return a.caneditevent||(b.footer=""),h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},q=function(a){var b=a.find(m.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},b)},r=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){window.location.reload()}),d.on(l.updated,function(){window.location.reload()}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},s=function(){var b=a(m.ROOT);a(m.EVENT_LINK).click(function(b){b.preventDefault();var c=a(this).attr("data-event-id");p(c)});var c=q(b);r(b,c)};return{init:function(){s()}}});
|
1
calendar/amd/build/event_form.min.js
vendored
Normal file
1
calendar/amd/build/event_form.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(["jquery","core/templates"],function(a,b){var c={EVENT_TYPE:'[name="eventtype"]',EVENT_COURSE_ID:'[name="courseid"]',EVENT_GROUP_COURSE_ID:'[name="groupcourseid"]',EVENT_GROUP_ID:'[name="groupid"]',FORM_GROUP:".form-group",SELECT_OPTION:"option",ADVANCED_ELEMENT:".fitem.advanced",FIELDSET_ADVANCED_ELEMENTS:"fieldset.containsadvancedelements",MORELESS_TOGGLE:".moreless-actions"},d={USER:"user",SITE:"site",COURSE:"course",GROUP:"group"},e={SHOW_ADVANCED:"event_form-show-advanced",HIDE_ADVANCED:"event_form-hide-advanced",ADVANCED_SHOWN:"event_form-advanced-shown",ADVANCED_HIDDEN:"event_form-advanced-hidden"},f=function(a){a.find(c.FIELDSET_ADVANCED_ELEMENTS).removeClass("containsadvancedelements");var d=a.find(c.MORELESS_TOGGLE);b.replaceNode(d,"","")},g=function(a){a.find(c.ADVANCED_ELEMENT).removeClass("hidden"),a.trigger(e.ADVANCED_SHOWN)},h=function(a){a.find(c.ADVANCED_ELEMENT).addClass("hidden"),a.trigger(e.ADVANCED_HIDDEN)},i=function(a){a.on(e.SHOW_ADVANCED,function(){g(a)}),a.on(e.HIDE_ADVANCED,function(){h(a)})},j=function(b){b.find(c.EVENT_GROUP_ID).find(c.SELECT_OPTION).each(function(b,c){var c=a(c),d=c.attr("value"),e=d.split("-"),f=e[0];c.attr("data-course-id",f)})},k=function(a){var b=a.find(c.EVENT_TYPE),e=b.val(),f=a.find(c.EVENT_COURSE_ID).closest(c.FORM_GROUP).removeClass("hidden"),g=a.find(c.EVENT_GROUP_COURSE_ID).closest(c.FORM_GROUP).removeClass("hidden"),h=a.find(c.EVENT_GROUP_ID).closest(c.FORM_GROUP).removeClass("hidden");switch(e){case d.COURSE:g.addClass("hidden"),h.addClass("hidden");break;case d.GROUP:f.addClass("hidden");break;default:f.addClass("hidden"),g.addClass("hidden"),h.addClass("hidden")}},l=function(a){var b=a.find(c.EVENT_TYPE);b.on("change",function(){k(a)})},m=function(b){var d=b.find(c.EVENT_GROUP_COURSE_ID),e=b.find(c.EVENT_GROUP_ID),f=e.find(c.SELECT_OPTION),g=function(){var b=d.val(),c=null;f.each(function(d,e){e=a(e),e.attr("data-course-id")==b?(e.removeClass("hidden"),e.prop("disabled",!1),null===c&&(c=d)):(e.addClass("hidden"),e.prop("disabled",!0))}),e.prop("selectedIndex",c)};d.on("change",g),g()},n=function(b,c){var d=a("#"+b);i(d),f(d),k(d),j(d),l(d),m(d),c?g(d):h(d)};return{init:n,events:e}});
|
1
calendar/amd/build/modal_event_form.min.js
vendored
Normal file
1
calendar/amd/build/modal_event_form.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(["jquery","core/event","core/str","core/notification","core/templates","core/custom_interaction_events","core/modal","core/modal_registry","core/fragment","core_calendar/events","core_calendar/repository","core_calendar/event_form"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m=!1,n={MORELESS_BUTTON:'[data-action="more-less-toggle"]',SAVE_BUTTON:'[data-action="save"]',LOADING_ICON_CONTAINER:'[data-region="loading-icon-container"]'},o=function(a){g.call(this,a),this.eventId=null,this.reloadingBody=!1,this.reloadingTitle=!1,this.saveButton=this.getFooter().find(n.SAVE_BUTTON),this.moreLessButton=this.getFooter().find(n.MORELESS_BUTTON)};return o.TYPE="core_calendar-modal_event_form",o.prototype=Object.create(g.prototype),o.prototype.constructor=o,o.prototype.setEventId=function(a){this.eventId=a},o.prototype.getEventId=function(){return this.eventId},o.prototype.hasEventId=function(){return null!==this.eventId},o.prototype.getForm=function(){return this.getBody().find("form")},o.prototype.disableButtons=function(){this.saveButton.prop("disabled",!0),this.moreLessButton.prop("disabled",!0)},o.prototype.enableButtons=function(){this.saveButton.prop("disabled",!1),this.moreLessButton.prop("disabled",!1)},o.prototype.setMoreButton=function(){this.moreLessButton.attr("data-collapsed","true"),c.get_string("more","calendar").then(function(a){this.moreLessButton.text(a)}.bind(this))},o.prototype.setLessButton=function(){this.moreLessButton.attr("data-collapsed","false"),c.get_string("less","calendar").then(function(a){this.moreLessButton.text(a)}.bind(this))},o.prototype.toggleMoreLessButton=function(){var a=this.getForm();"true"==this.moreLessButton.attr("data-collapsed")?(a.trigger(l.events.SHOW_ADVANCED),this.setLessButton()):(a.trigger(l.events.HIDE_ADVANCED),this.setMoreButton())},o.prototype.reloadTitleContent=function(){return this.reloadingTitle?this.titlePromise:(this.reloadingTitle=!0,this.hasEventId()?this.titlePromise=c.get_string("editevent","calendar"):this.titlePromise=c.get_string("newevent","calendar"),this.titlePromise.then(function(a){return this.setTitle(a),a}.bind(this)).always(function(){this.reloadingTitle=!1}.bind(this)),this.titlePromise)},o.prototype.reloadBodyContent=function(a,b){if(this.reloadingBody)return this.bodyPromise;this.reloadingBody=!0,this.disableButtons();var c=this.saveButton.attr("data-context-id"),e={};return this.hasEventId()&&(e.eventid=this.getEventId()),"undefined"!=typeof a&&(e.formdata=a),e.haserror="undefined"!=typeof b&&b,this.bodyPromise=i.loadFragment("calendar","event_form",c,e),this.setBody(this.bodyPromise),this.bodyPromise.then(function(){this.enableButtons()}.bind(this))["catch"](d.exception).always(function(){this.reloadingBody=!1}.bind(this)),this.bodyPromise},o.prototype.reloadAllContent=function(){return a.when(this.reloadTitleContent(),this.reloadBodyContent())},o.prototype.show=function(){this.reloadAllContent(),g.prototype.show.call(this)},o.prototype.hide=function(){g.prototype.hide.call(this),this.setEventId(null)},o.prototype.getFormData=function(){return this.getForm().serialize()},o.prototype.save=function(){var b=this.saveButton.find(n.LOADING_ICON_CONTAINER);b.removeClass("hidden"),this.disableButtons();var c=this.getFormData();return k.submitCreateUpdateForm(c).then(function(b){return b.validationerror?this.reloadBodyContent(c,!0):(this.hide(),void(this.hasEventId()?a("body").trigger(j.updated,[b.event]):a("body").trigger(j.created,[b.event])))}.bind(this)).always(function(){b.addClass("hidden"),this.enableButtons()}.bind(this))["catch"](d.exception)},o.prototype.registerEventListeners=function(){g.prototype.registerEventListeners.call(this),this.getModal().on(f.events.activate,n.SAVE_BUTTON,function(a,b){this.getForm().submit(),b.originalEvent.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on("submit",function(a){this.save(),a.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on(f.events.activate,n.MORELESS_BUTTON,function(a,b){this.toggleMoreLessButton(),b.originalEvent.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on(l.events.ADVANCED_SHOWN,function(){this.setLessButton()}.bind(this)),this.getModal().on(l.events.ADVANCED_HIDDEN,function(){this.setMoreButton()}.bind(this))},m||(h.register(o.TYPE,o,"calendar/modal_event_form"),m=!0),o});
|
2
calendar/amd/build/summary_modal.min.js
vendored
2
calendar/amd/build/summary_modal.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/calendar_repository","core_calendar/calendar_events"],function(a,b,c,d,e,f,g,h,i,j){var k=!1,l={ROOT:"[data-region='summary-modal-container']",EDIT_BUTTON:'[data-action="edit"]',DELETE_BUTTON:'[data-action="delete"]',EVENT_LINK:'[data-action="event-link"]'},m=function(a){e.call(this,a),this.getFooter().find(l.EDIT_BUTTON).length||c.exception({message:"No edit button found"}),this.getFooter().find(l.DELETE_BUTTON).length||c.exception({message:"No delete button found"})};return m.TYPE="core_calendar-event_summary",m.prototype=Object.create(e.prototype),m.prototype.constructor=m,m.prototype.registerEventListeners=function(){e.prototype.registerEventListeners.call(this);var a=g.create({type:g.types.CONFIRM},this.getFooter().find(l.DELETE_BUTTON)).then(function(a){return b.get_string("confirm").then(function(b){a.setTitle(b)}.bind(this))["catch"](c.exception),a.getRoot().on(h.yes,function(){var b=this.getBody().find(l.ROOT).attr("data-event-id");i.deleteEvent(b).done(function(){a.getRoot().trigger(j.deleted,b),window.location.reload()}).fail(c.exception)}.bind(this)),a}.bind(this));this.getRoot().on(h.bodyRendered,function(){var c=this.getBody().find(l.ROOT).attr("data-event-title");a.then(function(a){a.setBody(b.get_string("confirmeventdelete","core_calendar",c))})}.bind(this))},k||(f.register(m.TYPE,m,"core_calendar/event_summary_modal"),k=!0),m});
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e,f,g,h,i,j){var k=!1,l={ROOT:"[data-region='summary-modal-container']",EDIT_BUTTON:'[data-action="edit"]',DELETE_BUTTON:'[data-action="delete"]'},m=function(a){e.call(this,a),this.getEditButton().length||c.exception({message:"No edit button found"}),this.getDeleteButton().length||c.exception({message:"No delete button found"})};return m.TYPE="core_calendar-event_summary",m.prototype=Object.create(e.prototype),m.prototype.constructor=m,m.prototype.getEditButton=function(){return"undefined"==typeof this.editButton&&(this.editButton=this.getFooter().find(l.EDIT_BUTTON)),this.editButton},m.prototype.getDeleteButton=function(){return"undefined"==typeof this.deleteButton&&(this.deleteButton=this.getFooter().find(l.DELETE_BUTTON)),this.deleteButton},m.prototype.getEventId=function(){return this.getBody().find(l.ROOT).attr("data-event-id")},m.prototype.registerEventListeners=function(){e.prototype.registerEventListeners.call(this);var f=g.create({type:g.types.CONFIRM},this.getDeleteButton()).then(function(b){return b.getRoot().on(h.yes,function(){var b=this.getEventId();i.deleteEvent(b).then(function(){a("body").trigger(j.deleted,[b]),this.hide()}.bind(this))["catch"](c.exception)}.bind(this)),b}.bind(this));this.getRoot().on(h.bodyRendered,function(){var a=this.getBody().find(l.ROOT).attr("data-event-title");f.then(function(c){c.setBody(b.get_string("confirmeventdelete","core_calendar",a))})}.bind(this)),d.define(this.getEditButton(),[d.events.activate]),this.getEditButton().on(d.events.activate,function(b,c){a("body").trigger(j.editEvent,[this.getEventId()]),this.hide(),b.preventDefault(),b.stopPropagation(),c.originalEvent.preventDefault(),c.originalEvent.stopPropagation()}.bind(this))},k||(f.register(m.TYPE,m,"core_calendar/event_summary_modal"),k=!0),m});
|
|
@ -14,128 +14,215 @@
|
|||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* A javascript module to calendar events.
|
||||
* 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 Simey Lameze <simey@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/ajax', 'core/str', 'core/templates', 'core/notification', 'core/custom_interaction_events',
|
||||
'core/modal_factory', 'core_calendar/summary_modal', 'core/modal_events', 'core_calendar/calendar_repository'],
|
||||
function($, Ajax, Str, Templates, Notification, CustomEvents, ModalFactory, SummaryModal, ModalEvents, CalendarRepository) {
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/str',
|
||||
'core/templates',
|
||||
'core/notification',
|
||||
'core/custom_interaction_events',
|
||||
'core/modal_events',
|
||||
'core/modal_factory',
|
||||
'core_calendar/modal_event_form',
|
||||
'core_calendar/summary_modal',
|
||||
'core_calendar/repository',
|
||||
'core_calendar/events'
|
||||
],
|
||||
function(
|
||||
$,
|
||||
Ajax,
|
||||
Str,
|
||||
Templates,
|
||||
Notification,
|
||||
CustomEvents,
|
||||
ModalEvents,
|
||||
ModalFactory,
|
||||
ModalEventForm,
|
||||
SummaryModal,
|
||||
CalendarRepository,
|
||||
CalendarEvents
|
||||
) {
|
||||
|
||||
var SELECTORS = {
|
||||
ROOT: "[data-region='calendar']",
|
||||
EVENT_LINK: "[data-action='view-event']",
|
||||
};
|
||||
var SELECTORS = {
|
||||
ROOT: "[data-region='calendar']",
|
||||
EVENT_LINK: "[data-action='view-event']",
|
||||
NEW_EVENT_BUTTON: "[data-action='new-event-button']"
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 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 event source.
|
||||
*
|
||||
* @param {Object} subscription The event subscription object.
|
||||
* @return {promise} The lang string promise.
|
||||
*/
|
||||
var getEventSource = function(subscription) {
|
||||
return Str.get_string('subsource', 'core_calendar', subscription).then(function(langStr) {
|
||||
if (subscription.url) {
|
||||
return '<a href="' + subscription.url + '">' + langStr + '</a>';
|
||||
}
|
||||
return langStr;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Get the event source.
|
||||
*
|
||||
* @param {Object} subscription The event subscription object.
|
||||
* @return {promise} The lang string promise.
|
||||
*/
|
||||
var getEventSource = function(subscription) {
|
||||
return Str.get_string('subsource', 'core_calendar', subscription).then(function(langStr) {
|
||||
if (subscription.url) {
|
||||
return '<a href="' + subscription.url + '">' + langStr + '</a>';
|
||||
}
|
||||
return langStr;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the event summary modal.
|
||||
*
|
||||
* @param {Number} eventId The calendar event id.
|
||||
*/
|
||||
var renderEventSummaryModal = function(eventId) {
|
||||
// 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;
|
||||
var eventTypePromise = getEventType(eventData.eventtype);
|
||||
/**
|
||||
* Render the event summary modal.
|
||||
*
|
||||
* @param {Number} eventId The calendar event id.
|
||||
*/
|
||||
var renderEventSummaryModal = function(eventId) {
|
||||
// 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;
|
||||
var eventTypePromise = getEventType(eventData.eventtype);
|
||||
|
||||
// If the calendar event has event source, get the source's language string/link.
|
||||
if (eventData.displayeventsource) {
|
||||
eventData.subscription = JSON.parse(eventData.subscription);
|
||||
var eventSourceParams = {
|
||||
url: eventData.subscription.url,
|
||||
name: eventData.subscription.name
|
||||
};
|
||||
var eventSourcePromise = getEventSource(eventSourceParams);
|
||||
// If the calendar event has event source, get the source's language string/link.
|
||||
if (eventData.displayeventsource) {
|
||||
eventData.subscription = JSON.parse(eventData.subscription);
|
||||
var eventSourceParams = {
|
||||
url: eventData.subscription.url,
|
||||
name: eventData.subscription.name
|
||||
};
|
||||
var eventSourcePromise = getEventSource(eventSourceParams);
|
||||
|
||||
// Return event data with event type and event source info.
|
||||
return $.when(eventTypePromise, eventSourcePromise).then(function(eventType, eventSource) {
|
||||
eventData.eventtype = eventType;
|
||||
eventData.source = eventSource;
|
||||
return eventData;
|
||||
});
|
||||
}
|
||||
|
||||
// Return event data with event type info.
|
||||
return eventTypePromise.then(function(eventType) {
|
||||
// Return event data with event type and event source info.
|
||||
return $.when(eventTypePromise, eventSourcePromise).then(function(eventType, eventSource) {
|
||||
eventData.eventtype = eventType;
|
||||
eventData.source = eventSource;
|
||||
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)
|
||||
};
|
||||
if (!eventData.caneditevent) {
|
||||
modalParams.footer = '';
|
||||
}
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners for the module.
|
||||
*/
|
||||
var registerEventListeners = function() {
|
||||
// Bind click events to event links.
|
||||
$(SELECTORS.EVENT_LINK).click(function(e) {
|
||||
e.preventDefault();
|
||||
var eventId = $(this).attr('data-event-id');
|
||||
renderEventSummaryModal(eventId);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
registerEventListeners();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Return event data with event type info.
|
||||
return eventTypePromise.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)
|
||||
};
|
||||
if (!eventData.caneditevent) {
|
||||
modalParams.footer = '';
|
||||
}
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the event form modal for creating new events and
|
||||
* editing existing events.
|
||||
*
|
||||
* @method registerEventFormModal
|
||||
* @param {object} root The calendar root element
|
||||
* @return {object} The create modal promise
|
||||
*/
|
||||
var registerEventFormModal = function(root) {
|
||||
var newEventButton = root.find(SELECTORS.NEW_EVENT_BUTTON);
|
||||
var contextId = newEventButton.attr('data-context-id');
|
||||
|
||||
return ModalFactory.create(
|
||||
{
|
||||
type: ModalEventForm.TYPE,
|
||||
large: true,
|
||||
templateContext: {
|
||||
contextid: contextId
|
||||
}
|
||||
},
|
||||
newEventButton
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen to and handle any calendar events fired by the calendar UI.
|
||||
*
|
||||
* @method registerCalendarEventListeners
|
||||
* @param {object} root The calendar root element
|
||||
* @param {object} eventFormModalPromise A promise reolved with the event form modal
|
||||
*/
|
||||
var registerCalendarEventListeners = function(root, eventFormModalPromise) {
|
||||
var body = $('body');
|
||||
|
||||
// TODO: Replace these with actual logic to update
|
||||
// the UI without having to force a page reload.
|
||||
body.on(CalendarEvents.created, function() { window.location.reload(); });
|
||||
body.on(CalendarEvents.deleted, function() { window.location.reload(); });
|
||||
body.on(CalendarEvents.updated, function() { window.location.reload(); });
|
||||
|
||||
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) {
|
||||
modal.setEventId(eventId);
|
||||
modal.show();
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners for the module.
|
||||
*/
|
||||
var registerEventListeners = function() {
|
||||
var root = $(SELECTORS.ROOT);
|
||||
|
||||
// Bind click events to event links.
|
||||
$(SELECTORS.EVENT_LINK).click(function(e) {
|
||||
e.preventDefault();
|
||||
var eventId = $(this).attr('data-event-id');
|
||||
renderEventSummaryModal(eventId);
|
||||
});
|
||||
|
||||
var eventFormPromise = registerEventFormModal(root);
|
||||
registerCalendarEventListeners(root, eventFormPromise);
|
||||
};
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
registerEventListeners();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
282
calendar/amd/src/event_form.js
Normal file
282
calendar/amd/src/event_form.js
Normal file
|
@ -0,0 +1,282 @@
|
|||
// 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/>.
|
||||
|
||||
/**
|
||||
* A javascript module to enhance the event form.
|
||||
*
|
||||
* @module core_calendar/event_form
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/templates'], function($, Templates) {
|
||||
|
||||
var SELECTORS = {
|
||||
EVENT_TYPE: '[name="eventtype"]',
|
||||
EVENT_COURSE_ID: '[name="courseid"]',
|
||||
EVENT_GROUP_COURSE_ID: '[name="groupcourseid"]',
|
||||
EVENT_GROUP_ID: '[name="groupid"]',
|
||||
FORM_GROUP: '.form-group',
|
||||
SELECT_OPTION: 'option',
|
||||
ADVANCED_ELEMENT: '.fitem.advanced',
|
||||
FIELDSET_ADVANCED_ELEMENTS: 'fieldset.containsadvancedelements',
|
||||
MORELESS_TOGGLE: '.moreless-actions'
|
||||
};
|
||||
|
||||
var EVENT_TYPES = {
|
||||
USER: 'user',
|
||||
SITE: 'site',
|
||||
COURSE: 'course',
|
||||
GROUP: 'group'
|
||||
};
|
||||
|
||||
var EVENTS = {
|
||||
SHOW_ADVANCED: 'event_form-show-advanced',
|
||||
HIDE_ADVANCED: 'event_form-hide-advanced',
|
||||
ADVANCED_SHOWN: 'event_form-advanced-shown',
|
||||
ADVANCED_HIDDEN: 'event_form-advanced-hidden',
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the old show more / show less toggle added by the mform and destroy it.
|
||||
* We are handling the visibility of the advanced fields with the more/less button
|
||||
* in the footer of the modal that this form is rendered within.
|
||||
*
|
||||
* @method destroyOldMoreLessToggle
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var destroyOldMoreLessToggle = function(formElement) {
|
||||
formElement.find(SELECTORS.FIELDSET_ADVANCED_ELEMENTS).removeClass('containsadvancedelements');
|
||||
var element = formElement.find(SELECTORS.MORELESS_TOGGLE);
|
||||
Templates.replaceNode(element, '', '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Find each of the advanced form elements and make them visible.
|
||||
*
|
||||
* This function triggers the ADVANCED_SHOWN event for any other
|
||||
* component to handle (e.g. the event form modal).
|
||||
*
|
||||
* @method destroyOldMoreLessToggle
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var showAdvancedElements = function(formElement) {
|
||||
formElement.find(SELECTORS.ADVANCED_ELEMENT).removeClass('hidden');
|
||||
formElement.trigger(EVENTS.ADVANCED_SHOWN);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find each of the advanced form elements and hide them.
|
||||
*
|
||||
* This function triggers the ADVANCED_HIDDEN event for any other
|
||||
* component to handle (e.g. the event form modal).
|
||||
*
|
||||
* @method hideAdvancedElements
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var hideAdvancedElements = function(formElement) {
|
||||
formElement.find(SELECTORS.ADVANCED_ELEMENT).addClass('hidden');
|
||||
formElement.trigger(EVENTS.ADVANCED_HIDDEN);
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for any events telling this module to show or hide it's
|
||||
* advanced elements.
|
||||
*
|
||||
* This function listens for SHOW_ADVANCED and HIDE_ADVANCED.
|
||||
*
|
||||
* @method listenForShowHideEvents
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var listenForShowHideEvents = function(formElement) {
|
||||
formElement.on(EVENTS.SHOW_ADVANCED, function() {
|
||||
showAdvancedElements(formElement);
|
||||
});
|
||||
|
||||
formElement.on(EVENTS.HIDE_ADVANCED, function() {
|
||||
hideAdvancedElements(formElement);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the group id select element in the event form and pull out
|
||||
* the course id from the value to allow us to toggle other select
|
||||
* elements based on the course id for the group a user selects.
|
||||
*
|
||||
* This is a little hacky but I couldn't find a better way to pass
|
||||
* the course id for each group id with the limitations of mforms.
|
||||
*
|
||||
* The group id options are rendered with a value like:
|
||||
* "<courseid>-<groupid>"
|
||||
* E.g.
|
||||
* For a group with id 10 in a course with id 3 the value of the
|
||||
* option will be 3-10.
|
||||
*
|
||||
* @method parseGroupSelect
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var parseGroupSelect = function(formElement) {
|
||||
formElement.find(SELECTORS.EVENT_GROUP_ID)
|
||||
.find(SELECTORS.SELECT_OPTION)
|
||||
.each(function(index, element) {
|
||||
var element = $(element);
|
||||
var value = element.attr('value');
|
||||
var splits = value.split('-');
|
||||
var courseId = splits[0];
|
||||
|
||||
element.attr('data-course-id', courseId);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the secondary select elements based on
|
||||
* the event type the user has selected.
|
||||
*
|
||||
* There are 3 secondary select elements within the form:
|
||||
* - course: a list of all courses a user can add course events to
|
||||
* - group course: a list of all courses a user can add group events to.
|
||||
* this list can be different from the course list above.
|
||||
* - group: a list of all groups a user can add an event to. This list will
|
||||
* be filtered further based on the group course selected.
|
||||
*
|
||||
* There are 4 event types:
|
||||
* - user: none of the secondary selects should be visible.
|
||||
* - site: none of the secondary selects should be visible.
|
||||
* - course: "course" select should be visible and both "group course"
|
||||
* and "group" should be hidden.
|
||||
* - group: "group course" and "group" should be visible and "course"
|
||||
* should be hidden.
|
||||
*
|
||||
* @method hideTypeSubSelects
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var hideTypeSubSelects = function(formElement) {
|
||||
var typeSelect = formElement.find(SELECTORS.EVENT_TYPE);
|
||||
var eventType = typeSelect.val();
|
||||
var courseIdSelect = formElement.find(SELECTORS.EVENT_COURSE_ID)
|
||||
.closest(SELECTORS.FORM_GROUP)
|
||||
.removeClass('hidden');
|
||||
var groupCourseIdSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID)
|
||||
.closest(SELECTORS.FORM_GROUP)
|
||||
.removeClass('hidden');
|
||||
var groupIdSelect = formElement.find(SELECTORS.EVENT_GROUP_ID)
|
||||
.closest(SELECTORS.FORM_GROUP)
|
||||
.removeClass('hidden');
|
||||
|
||||
// Hide the unreleated selectors for the given event type.
|
||||
switch (eventType) {
|
||||
case EVENT_TYPES.COURSE:
|
||||
groupCourseIdSelect.addClass('hidden');
|
||||
groupIdSelect.addClass('hidden');
|
||||
break;
|
||||
case EVENT_TYPES.GROUP:
|
||||
courseIdSelect.addClass('hidden');
|
||||
break;
|
||||
default:
|
||||
courseIdSelect.addClass('hidden');
|
||||
groupCourseIdSelect.addClass('hidden');
|
||||
groupIdSelect.addClass('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for when the user changes the event type select in the
|
||||
* form and then toggle the visibility of the appropriate secondary
|
||||
* select elements.
|
||||
*
|
||||
* See: hideTypeSubSelects.
|
||||
*
|
||||
* @method addTypeSelectListeners
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var addTypeSelectListeners = function(formElement) {
|
||||
var typeSelect = formElement.find(SELECTORS.EVENT_TYPE);
|
||||
|
||||
typeSelect.on('change', function() {
|
||||
hideTypeSubSelects(formElement);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for when the user changes the group course when configuring
|
||||
* a group event and filter the options in the group select to only
|
||||
* show the groups available within the course the user has selected.
|
||||
*
|
||||
* @method addCourseGroupSelectListeners
|
||||
* @param {object} formElement The root form element
|
||||
*/
|
||||
var addCourseGroupSelectListeners = function(formElement) {
|
||||
var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);
|
||||
var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID);
|
||||
var groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION);
|
||||
var filterGroupSelectOptions = function() {
|
||||
var selectedCourseId = courseGroupSelect.val();
|
||||
var selectedIndex = null;
|
||||
|
||||
groupSelectOptions.each(function(index, element) {
|
||||
element = $(element);
|
||||
|
||||
if (element.attr('data-course-id') == selectedCourseId) {
|
||||
element.removeClass('hidden');
|
||||
element.prop('disabled', false);
|
||||
|
||||
if (selectedIndex === null) {
|
||||
selectedIndex = index;
|
||||
}
|
||||
} else {
|
||||
element.addClass('hidden');
|
||||
element.prop('disabled', true);
|
||||
}
|
||||
});
|
||||
|
||||
groupSelect.prop('selectedIndex', selectedIndex);
|
||||
};
|
||||
|
||||
courseGroupSelect.on('change', filterGroupSelectOptions);
|
||||
filterGroupSelectOptions();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise all of the form enhancementds.
|
||||
*
|
||||
* @method init
|
||||
* @param {string} formId The value of the form's id attribute
|
||||
* @param {bool} hasError If the form has errors rendered form the server.
|
||||
*/
|
||||
var init = function(formId, hasError) {
|
||||
var formElement = $('#' + formId);
|
||||
|
||||
listenForShowHideEvents(formElement);
|
||||
destroyOldMoreLessToggle(formElement);
|
||||
hideTypeSubSelects(formElement);
|
||||
parseGroupSelect(formElement);
|
||||
addTypeSelectListeners(formElement);
|
||||
addCourseGroupSelectListeners(formElement);
|
||||
|
||||
// If we know that the form has been rendered with server side
|
||||
// errors then we need to display all of the elements in the form
|
||||
// in case one of those elements has the error.
|
||||
if (hasError) {
|
||||
showAdvancedElements(formElement);
|
||||
} else {
|
||||
hideAdvancedElements(formElement);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
init: init,
|
||||
events: EVENTS,
|
||||
};
|
||||
});
|
429
calendar/amd/src/modal_event_form.js
Normal file
429
calendar/amd/src/modal_event_form.js
Normal file
|
@ -0,0 +1,429 @@
|
|||
// 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/>.
|
||||
|
||||
/**
|
||||
* Contain the logic for the quick add or update event modal.
|
||||
*
|
||||
* @module calendar/modal_quick_add_event
|
||||
* @class modal_quick_add_event
|
||||
* @package core
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/event',
|
||||
'core/str',
|
||||
'core/notification',
|
||||
'core/templates',
|
||||
'core/custom_interaction_events',
|
||||
'core/modal',
|
||||
'core/modal_registry',
|
||||
'core/fragment',
|
||||
'core_calendar/events',
|
||||
'core_calendar/repository',
|
||||
'core_calendar/event_form'
|
||||
],
|
||||
function(
|
||||
$,
|
||||
Event,
|
||||
Str,
|
||||
Notification,
|
||||
Templates,
|
||||
CustomEvents,
|
||||
Modal,
|
||||
ModalRegistry,
|
||||
Fragment,
|
||||
CalendarEvents,
|
||||
Repository,
|
||||
EventForm
|
||||
) {
|
||||
|
||||
var registered = false;
|
||||
var SELECTORS = {
|
||||
MORELESS_BUTTON: '[data-action="more-less-toggle"]',
|
||||
SAVE_BUTTON: '[data-action="save"]',
|
||||
LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for the Modal.
|
||||
*
|
||||
* @param {object} root The root jQuery element for the modal
|
||||
*/
|
||||
var ModalEventForm = function(root) {
|
||||
Modal.call(this, root);
|
||||
this.eventId = null;
|
||||
this.reloadingBody = false;
|
||||
this.reloadingTitle = false;
|
||||
this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
|
||||
this.moreLessButton = this.getFooter().find(SELECTORS.MORELESS_BUTTON);
|
||||
};
|
||||
|
||||
ModalEventForm.TYPE = 'core_calendar-modal_event_form';
|
||||
ModalEventForm.prototype = Object.create(Modal.prototype);
|
||||
ModalEventForm.prototype.constructor = ModalEventForm;
|
||||
|
||||
/**
|
||||
* Set the event id to the given value.
|
||||
*
|
||||
* @method setEventId
|
||||
* @param {int} id The event id
|
||||
*/
|
||||
ModalEventForm.prototype.setEventId = function(id) {
|
||||
this.eventId = id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the current event id, if any.
|
||||
*
|
||||
* @method getEventId
|
||||
* @return {int|null} The event id
|
||||
*/
|
||||
ModalEventForm.prototype.getEventId = function() {
|
||||
return this.eventId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the modal has an event id.
|
||||
*
|
||||
* @method hasEventId
|
||||
* @return {bool}
|
||||
*/
|
||||
ModalEventForm.prototype.hasEventId = function() {
|
||||
return this.eventId !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the form element from the modal.
|
||||
*
|
||||
* @method getForm
|
||||
* @return {object}
|
||||
*/
|
||||
ModalEventForm.prototype.getForm = function() {
|
||||
return this.getBody().find('form');
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable the buttons in the footer.
|
||||
*
|
||||
* @method disableButtons
|
||||
*/
|
||||
ModalEventForm.prototype.disableButtons = function() {
|
||||
this.saveButton.prop('disabled', true);
|
||||
this.moreLessButton.prop('disabled', true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable the buttons in the footer.
|
||||
*
|
||||
* @method enableButtons
|
||||
*/
|
||||
ModalEventForm.prototype.enableButtons = function() {
|
||||
this.saveButton.prop('disabled', false);
|
||||
this.moreLessButton.prop('disabled', false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the more/less button in the footer to the "more"
|
||||
* state.
|
||||
*
|
||||
* @method setMoreButton
|
||||
*/
|
||||
ModalEventForm.prototype.setMoreButton = function() {
|
||||
this.moreLessButton.attr('data-collapsed', 'true');
|
||||
Str.get_string('more', 'calendar').then(function(string) {
|
||||
this.moreLessButton.text(string);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the more/less button in the footer to the "less"
|
||||
* state.
|
||||
*
|
||||
* @method setLessButton
|
||||
*/
|
||||
ModalEventForm.prototype.setLessButton = function() {
|
||||
this.moreLessButton.attr('data-collapsed', 'false');
|
||||
Str.get_string('less', 'calendar').then(function(string) {
|
||||
this.moreLessButton.text(string);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the more/less button in the footer from the current
|
||||
* state to it's opposite state.
|
||||
*
|
||||
* @method toggleMoreLessButton
|
||||
*/
|
||||
ModalEventForm.prototype.toggleMoreLessButton = function() {
|
||||
var form = this.getForm();
|
||||
|
||||
if (this.moreLessButton.attr('data-collapsed') == 'true') {
|
||||
form.trigger(EventForm.events.SHOW_ADVANCED);
|
||||
this.setLessButton();
|
||||
} else {
|
||||
form.trigger(EventForm.events.HIDE_ADVANCED);
|
||||
this.setMoreButton();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reload the title for the modal to the appropriate value
|
||||
* depending on whether we are creating a new event or
|
||||
* editing an existing event.
|
||||
*
|
||||
* @method reloadTitleContent
|
||||
* @return {object} A promise resolved with the new title text
|
||||
*/
|
||||
ModalEventForm.prototype.reloadTitleContent = function() {
|
||||
if (this.reloadingTitle) {
|
||||
return this.titlePromise;
|
||||
}
|
||||
|
||||
this.reloadingTitle = true;
|
||||
|
||||
if (this.hasEventId()) {
|
||||
this.titlePromise = Str.get_string('editevent', 'calendar');
|
||||
} else {
|
||||
this.titlePromise = Str.get_string('newevent', 'calendar');
|
||||
}
|
||||
|
||||
this.titlePromise.then(function(string) {
|
||||
this.setTitle(string);
|
||||
return string;
|
||||
}.bind(this))
|
||||
.always(function() {
|
||||
this.reloadingTitle = false;
|
||||
return;
|
||||
}.bind(this));
|
||||
|
||||
return this.titlePromise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a request to the server to get the event_form in a fragment
|
||||
* and render the result in the body of the modal.
|
||||
*
|
||||
* If serialised form data is provided then it will be sent in the
|
||||
* request to the server to have the form rendered with the data. This
|
||||
* is used when the form had a server side error and we need the server
|
||||
* to re-render it for us to display the error to the user.
|
||||
*
|
||||
* @method reloadBodyContent
|
||||
* @param {string} formData The serialised form data
|
||||
* @param {bool} hasError True if we know the form data is erroneous
|
||||
* @return {object} A promise resolved with the fragment html and js from
|
||||
*/
|
||||
ModalEventForm.prototype.reloadBodyContent = function(formData, hasError) {
|
||||
if (this.reloadingBody) {
|
||||
return this.bodyPromise;
|
||||
}
|
||||
|
||||
this.reloadingBody = true;
|
||||
this.disableButtons();
|
||||
|
||||
var contextId = this.saveButton.attr('data-context-id');
|
||||
var args = {};
|
||||
|
||||
if (this.hasEventId()) {
|
||||
args.eventid = this.getEventId();
|
||||
}
|
||||
|
||||
if (typeof formData !== 'undefined') {
|
||||
args.formdata = formData;
|
||||
}
|
||||
|
||||
args.haserror = (typeof hasError == 'undefined') ? false : hasError;
|
||||
|
||||
this.bodyPromise = Fragment.loadFragment('calendar', 'event_form', contextId, args);
|
||||
|
||||
this.setBody(this.bodyPromise);
|
||||
|
||||
this.bodyPromise.then(function() {
|
||||
this.enableButtons();
|
||||
return;
|
||||
}.bind(this))
|
||||
.catch(Notification.exception)
|
||||
.always(function() {
|
||||
this.reloadingBody = false;
|
||||
return;
|
||||
}.bind(this));
|
||||
|
||||
return this.bodyPromise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reload both the title and body content.
|
||||
*
|
||||
* @method reloadAllContent
|
||||
* @return {object} promise
|
||||
*/
|
||||
ModalEventForm.prototype.reloadAllContent = function() {
|
||||
return $.when(this.reloadTitleContent(), this.reloadBodyContent());
|
||||
};
|
||||
|
||||
/**
|
||||
* Kick off a reload the modal content before showing it. This
|
||||
* is to allow us to re-use the same modal for creating and
|
||||
* editing different events within the page.
|
||||
*
|
||||
* We do the reload when showing the modal rather than hiding it
|
||||
* to save a request to the server if the user closes the modal
|
||||
* and never re-opens it.
|
||||
*
|
||||
* @method show
|
||||
*/
|
||||
ModalEventForm.prototype.show = function() {
|
||||
this.reloadAllContent();
|
||||
Modal.prototype.show.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the event id from the modal when it's closed so
|
||||
* that it is loaded fresh next time it's displayed.
|
||||
*
|
||||
* The event id will be set by the calling code if it wants
|
||||
* to edit a specific event.
|
||||
*
|
||||
* @method hide
|
||||
*/
|
||||
ModalEventForm.prototype.hide = function() {
|
||||
Modal.prototype.hide.call(this);
|
||||
this.setEventId(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the serialised form data.
|
||||
*
|
||||
* @method getFormData
|
||||
* @return {string} serialised form data
|
||||
*/
|
||||
ModalEventForm.prototype.getFormData = function() {
|
||||
return this.getForm().serialize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the form data to the server to create or update
|
||||
* an event.
|
||||
*
|
||||
* If there is a server side validation error then we re-request the
|
||||
* rendered form (with the data) from the server in order to get the
|
||||
* server side errors to display.
|
||||
*
|
||||
* On success the modal is hidden and the page is reloaded so that the
|
||||
* new event will display.
|
||||
*
|
||||
* @method save
|
||||
* @return {object} A promise
|
||||
*/
|
||||
ModalEventForm.prototype.save = function() {
|
||||
var loadingContainer = this.saveButton.find(SELECTORS.LOADING_ICON_CONTAINER);
|
||||
|
||||
loadingContainer.removeClass('hidden');
|
||||
this.disableButtons();
|
||||
|
||||
var formData = this.getFormData();
|
||||
// Send the form data to the server for processing.
|
||||
return Repository.submitCreateUpdateForm(formData)
|
||||
.then(function(response) {
|
||||
if (response.validationerror) {
|
||||
// If there was a server side validation error then
|
||||
// we need to re-request the rendered form from the server
|
||||
// in order to display the error for the user.
|
||||
return this.reloadBodyContent(formData, true);
|
||||
} else {
|
||||
// No problemo! Our work here is done.
|
||||
this.hide();
|
||||
|
||||
// Trigger the appropriate calendar event so that the view can
|
||||
// be updated.
|
||||
if (this.hasEventId()) {
|
||||
$('body').trigger(CalendarEvents.updated, [response.event]);
|
||||
} else {
|
||||
$('body').trigger(CalendarEvents.created, [response.event]);
|
||||
}
|
||||
}
|
||||
}.bind(this))
|
||||
.always(function() {
|
||||
// Regardless of success or error we should always stop
|
||||
// the loading icon and re-enable the buttons.
|
||||
loadingContainer.addClass('hidden');
|
||||
this.enableButtons();
|
||||
}.bind(this))
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up all of the event handling for the modal.
|
||||
*
|
||||
* @method registerEventListeners
|
||||
*/
|
||||
ModalEventForm.prototype.registerEventListeners = function() {
|
||||
// Apply parent event listeners.
|
||||
Modal.prototype.registerEventListeners.call(this);
|
||||
|
||||
// When the user clicks the save button we trigger the form submission. We need to
|
||||
// trigger an actual submission because there is some JS code in the form that is
|
||||
// listening for this event and doing some stuff (e.g. saving draft areas etc).
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, function(e, data) {
|
||||
this.getForm().submit();
|
||||
data.originalEvent.preventDefault();
|
||||
e.stopPropagation();
|
||||
}.bind(this));
|
||||
|
||||
// Catch the submit event before it is actually processed by the browser and
|
||||
// prevent the submission. We'll take it from here.
|
||||
this.getModal().on('submit', function(e) {
|
||||
this.save();
|
||||
|
||||
// Stop the form from actually submitting and prevent it's
|
||||
// propagation because we have already handled the event.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}.bind(this));
|
||||
|
||||
// Toggle the state of the more/less button in the footer.
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.MORELESS_BUTTON, function(e, data) {
|
||||
this.toggleMoreLessButton();
|
||||
|
||||
data.originalEvent.preventDefault();
|
||||
e.stopPropagation();
|
||||
}.bind(this));
|
||||
|
||||
// When the event form tells us that the advanced fields are shown
|
||||
// then the more/less button should be set to less to allow the user
|
||||
// to hide the advanced fields.
|
||||
this.getModal().on(EventForm.events.ADVANCED_SHOWN, function() {
|
||||
this.setLessButton();
|
||||
}.bind(this));
|
||||
|
||||
// When the event form tells us that the advanced fields are hidden
|
||||
// then the more/less button should be set to more to allow the user
|
||||
// to show the advanced fields.
|
||||
this.getModal().on(EventForm.events.ADVANCED_HIDDEN, function() {
|
||||
this.setMoreButton();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Automatically register with the modal registry the first time this module is imported so that you can create modals
|
||||
// of this type using the modal factory.
|
||||
if (!registered) {
|
||||
ModalRegistry.register(ModalEventForm.TYPE, ModalEventForm, 'calendar/modal_event_form');
|
||||
registered = true;
|
||||
}
|
||||
|
||||
return ModalEventForm;
|
||||
});
|
|
@ -22,8 +22,8 @@
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_events', 'core/modal',
|
||||
'core/modal_registry', 'core/modal_factory', 'core/modal_events', 'core_calendar/calendar_repository',
|
||||
'core_calendar/calendar_events'],
|
||||
'core/modal_registry', 'core/modal_factory', 'core/modal_events', 'core_calendar/repository',
|
||||
'core_calendar/events'],
|
||||
function($, Str, Notification, CustomEvents, Modal, ModalRegistry, ModalFactory, ModalEvents, CalendarRepository,
|
||||
CalendarEvents) {
|
||||
|
||||
|
@ -32,7 +32,6 @@ define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_even
|
|||
ROOT: "[data-region='summary-modal-container']",
|
||||
EDIT_BUTTON: '[data-action="edit"]',
|
||||
DELETE_BUTTON: '[data-action="delete"]',
|
||||
EVENT_LINK: '[data-action="event-link"]'
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -43,11 +42,11 @@ define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_even
|
|||
var ModalEventSummary = function(root) {
|
||||
Modal.call(this, root);
|
||||
|
||||
if (!this.getFooter().find(SELECTORS.EDIT_BUTTON).length) {
|
||||
if (!this.getEditButton().length) {
|
||||
Notification.exception({message: 'No edit button found'});
|
||||
}
|
||||
|
||||
if (!this.getFooter().find(SELECTORS.DELETE_BUTTON).length) {
|
||||
if (!this.getDeleteButton().length) {
|
||||
Notification.exception({message: 'No delete button found'});
|
||||
}
|
||||
};
|
||||
|
@ -56,6 +55,48 @@ define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_even
|
|||
ModalEventSummary.prototype = Object.create(Modal.prototype);
|
||||
ModalEventSummary.prototype.constructor = ModalEventSummary;
|
||||
|
||||
/**
|
||||
* Get the edit button element from the footer. The button is cached
|
||||
* as it's not expected to change.
|
||||
*
|
||||
* @method getEditButton
|
||||
* @return {object} button element
|
||||
*/
|
||||
ModalEventSummary.prototype.getEditButton = function() {
|
||||
if (typeof this.editButton == 'undefined') {
|
||||
this.editButton = this.getFooter().find(SELECTORS.EDIT_BUTTON);
|
||||
}
|
||||
|
||||
return this.editButton;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the delete button element from the footer. The button is cached
|
||||
* as it's not expected to change.
|
||||
*
|
||||
* @method getDeleteButton
|
||||
* @return {object} button element
|
||||
*/
|
||||
ModalEventSummary.prototype.getDeleteButton = function() {
|
||||
if (typeof this.deleteButton == 'undefined') {
|
||||
this.deleteButton = this.getFooter().find(SELECTORS.DELETE_BUTTON);
|
||||
}
|
||||
|
||||
return this.deleteButton;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id for the event being shown in this modal. This value is
|
||||
* not cached because it will change depending on which event is
|
||||
* being displayed.
|
||||
*
|
||||
* @method getEventId
|
||||
* @return {int}
|
||||
*/
|
||||
ModalEventSummary.prototype.getEventId = function() {
|
||||
return this.getBody().find(SELECTORS.ROOT).attr('data-event-id');
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up all of the event handling for the modal.
|
||||
*
|
||||
|
@ -64,28 +105,51 @@ define(['jquery', 'core/str', 'core/notification', 'core/custom_interaction_even
|
|||
ModalEventSummary.prototype.registerEventListeners = function() {
|
||||
// Apply parent event listeners.
|
||||
Modal.prototype.registerEventListeners.call(this);
|
||||
var confirmPromise = ModalFactory.create({
|
||||
type: ModalFactory.types.CONFIRM,
|
||||
}, this.getFooter().find(SELECTORS.DELETE_BUTTON)).then(function(modal) {
|
||||
Str.get_string('confirm').then(function(languagestring) {
|
||||
modal.setTitle(languagestring);
|
||||
}.bind(this)).catch(Notification.exception);
|
||||
|
||||
var confirmPromise = ModalFactory.create(
|
||||
{ type: ModalFactory.types.CONFIRM },
|
||||
this.getDeleteButton()
|
||||
).then(function(modal) {
|
||||
modal.getRoot().on(ModalEvents.yes, function() {
|
||||
var eventId = this.getBody().find(SELECTORS.ROOT).attr('data-event-id');
|
||||
CalendarRepository.deleteEvent(eventId).done(function() {
|
||||
modal.getRoot().trigger(CalendarEvents.deleted, eventId);
|
||||
window.location.reload();
|
||||
}).fail(Notification.exception);
|
||||
var eventId = this.getEventId();
|
||||
|
||||
CalendarRepository.deleteEvent(eventId)
|
||||
.then(function() {
|
||||
$('body').trigger(CalendarEvents.deleted, [eventId]);
|
||||
this.hide();
|
||||
}.bind(this))
|
||||
.catch(Notification.exception);
|
||||
}.bind(this));
|
||||
|
||||
return modal;
|
||||
}.bind(this));
|
||||
|
||||
// We have to wait for the modal to finish rendering in order to ensure that
|
||||
// the data-event-title property is available to use as the modal title.
|
||||
this.getRoot().on(ModalEvents.bodyRendered, function() {
|
||||
var eventTitle = this.getBody().find(SELECTORS.ROOT).attr('data-event-title');
|
||||
confirmPromise.then(function(modal) {
|
||||
modal.setBody(Str.get_string('confirmeventdelete', 'core_calendar', eventTitle));
|
||||
});
|
||||
}.bind(this));
|
||||
|
||||
CustomEvents.define(this.getEditButton(), [
|
||||
CustomEvents.events.activate
|
||||
]);
|
||||
|
||||
this.getEditButton().on(CustomEvents.events.activate, function(e, data) {
|
||||
// When the edit button is clicked we fire an event for the calendar UI to handle.
|
||||
// We don't care how the UI chooses to handle it.
|
||||
$('body').trigger(CalendarEvents.editEvent, [this.getEventId()]);
|
||||
// There is nothing else for us to do so let's hide.
|
||||
this.hide();
|
||||
|
||||
// We've handled this event so no need to propagate it.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
data.originalEvent.preventDefault();
|
||||
data.originalEvent.stopPropagation();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Automatically register with the modal registry the first time this module is imported so that you can create modals
|
||||
|
|
283
calendar/classes/local/event/forms/create.php
Normal file
283
calendar/classes/local/event/forms/create.php
Normal file
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* The mform for creating a calendar event. Based on the
|
||||
* old event form.
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @package calendar
|
||||
*/
|
||||
namespace core_calendar\local\event\forms;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/lib/formslib.php');
|
||||
|
||||
/**
|
||||
* The mform class for creating a calendar event.
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class create extends \moodleform {
|
||||
/**
|
||||
* The form definition
|
||||
*/
|
||||
public function definition () {
|
||||
global $PAGE;
|
||||
|
||||
$mform = $this->_form;
|
||||
$haserror = !empty($this->_customdata['haserror']);
|
||||
$eventtypes = calendar_get_all_allowed_types();
|
||||
|
||||
$mform->setDisableShortforms();
|
||||
$mform->disable_form_change_checker();
|
||||
|
||||
// Empty string so that the element doesn't get rendered.
|
||||
$mform->addElement('header', 'general', '');
|
||||
|
||||
$this->add_default_hidden_elements($mform);
|
||||
|
||||
// Event name field.
|
||||
$mform->addElement('text', 'name', get_string('eventname','calendar'), 'size="50"');
|
||||
$mform->addRule('name', get_string('required'), 'required', null, 'client');
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
|
||||
// Event time start field.
|
||||
$mform->addElement('date_time_selector', 'timestart', get_string('date'));
|
||||
|
||||
// Add the select elements for the available event types.
|
||||
$this->add_event_type_elements($mform, $eventtypes);
|
||||
|
||||
// ********* START OF ADVANCED ELEMENTS *********.
|
||||
// Advanced elements are not visible to the user by default. They are
|
||||
// displayed through the user of a show more / less button.
|
||||
$mform->addElement('editor', 'description', get_string('eventdescription','calendar'), ['rows' => 3]);
|
||||
$mform->setType('description', PARAM_RAW);
|
||||
$mform->setAdvanced('description');
|
||||
|
||||
// Add the variety of elements allowed for selecting event duration.
|
||||
$this->add_event_duration_elements($mform);
|
||||
|
||||
// Add the form elements for repeating events.
|
||||
$this->add_event_repeat_elements($mform);
|
||||
|
||||
// Add the javascript required to enhance this mform. Including the show/hide of advanced elements
|
||||
// and the display of the correct select elements for chosen event types.
|
||||
$PAGE->requires->js_call_amd('core_calendar/event_form', 'init', [$mform->getAttribute('id'), $haserror]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A bit of custom validation for this form
|
||||
*
|
||||
* @param array $data An assoc array of field=>value
|
||||
* @param array $files An array of files
|
||||
* @return array
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
global $DB, $CFG;
|
||||
|
||||
$errors = parent::validation($data, $files);
|
||||
$coursekey = isset($data['groupcourseid']) ? 'groupcourseid' : 'courseid';
|
||||
|
||||
if (isset($data[$coursekey]) && $data[$coursekey] > 0) {
|
||||
if ($course = $DB->get_record('course', ['id' => $data[$coursekey]])) {
|
||||
if ($data['timestart'] < $course->startdate) {
|
||||
$errors['timestart'] = get_string('errorbeforecoursestart', 'calendar');
|
||||
}
|
||||
} else {
|
||||
$errors[$coursekey] = get_string('invalidcourse', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['duration'] == 1 && $data['timestart'] > $data['timedurationuntil']) {
|
||||
$errors['durationgroup'] = get_string('invalidtimedurationuntil', 'calendar');
|
||||
} else if ($data['duration'] == 2 && (trim($data['timedurationminutes']) == '' || $data['timedurationminutes'] < 1)) {
|
||||
$errors['durationgroup'] = get_string('invalidtimedurationminutes', 'calendar');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the list of hidden elements that should appear in this form each
|
||||
* time. These elements will never be visible to the user.
|
||||
*
|
||||
* @method add_default_hidden_elements
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
protected function add_default_hidden_elements($mform) {
|
||||
global $USER;
|
||||
|
||||
// Add some hidden fields
|
||||
$mform->addElement('hidden', 'id');
|
||||
$mform->setType('id', PARAM_INT);
|
||||
$mform->setDefault('id', 0);
|
||||
|
||||
$mform->addElement('hidden', 'userid');
|
||||
$mform->setType('userid', PARAM_INT);
|
||||
$mform->setDefault('userid', $USER->id);
|
||||
|
||||
$mform->addElement('hidden', 'modulename');
|
||||
$mform->setType('modulename', PARAM_INT);
|
||||
$mform->setDefault('modulename', '');
|
||||
|
||||
$mform->addElement('hidden', 'instance');
|
||||
$mform->setType('instance', PARAM_INT);
|
||||
$mform->setDefault('instance', 0);
|
||||
|
||||
$mform->addElement('hidden', 'visible');
|
||||
$mform->setType('visible', PARAM_INT);
|
||||
$mform->setDefault('visible', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the appropriate elements for the available event types.
|
||||
*
|
||||
* If the only event type available is 'user' then we add a hidden
|
||||
* element because there is nothing for the user to choose.
|
||||
*
|
||||
* If more than one type is available then we add the elements as
|
||||
* follows:
|
||||
* - Always add the event type selector
|
||||
* - Elements per type:
|
||||
* - course: add an additional select element with each
|
||||
* course as an option.
|
||||
* - group: add a select element for the course (different
|
||||
* from the above course select) and a select
|
||||
* element for the group.
|
||||
*
|
||||
* @method add_event_type_elements
|
||||
* @param MoodleQuickForm $mform
|
||||
* @param array $eventtypes The available event types for the user
|
||||
*/
|
||||
protected function add_event_type_elements($mform, $eventtypes) {
|
||||
$options = [];
|
||||
|
||||
if (isset($eventtypes['user'])) {
|
||||
$options['user'] = get_string('user');
|
||||
}
|
||||
if (isset($eventtypes['group'])) {
|
||||
$options['group'] = get_string('group');
|
||||
}
|
||||
if (isset($eventtypes['course'])) {
|
||||
$options['course'] = get_string('course');
|
||||
}
|
||||
if (isset($eventtypes['site'])) {
|
||||
$options['site'] = get_string('site');
|
||||
}
|
||||
|
||||
// If we only have one event type and it's 'user' event then don't bother
|
||||
// rendering the select boxes because there is no choice for the user to
|
||||
// make.
|
||||
if (count(array_keys($eventtypes)) == 1 && isset($eventtypes['user'])) {
|
||||
$mform->addElement('hidden', 'eventtype');
|
||||
$mform->setType('eventtype', PARAM_TEXT);
|
||||
$mform->setDefault('eventtype', 'user');
|
||||
|
||||
// Render a static element to tell the user what type of event will
|
||||
// be created.
|
||||
$mform->addElement('static', 'staticeventtype', get_string('eventkind', 'calendar'), $options['user']);
|
||||
return;
|
||||
} else {
|
||||
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
|
||||
}
|
||||
|
||||
if (isset($eventtypes['course'])) {
|
||||
$courseoptions = [];
|
||||
foreach ($eventtypes['course'] as $course) {
|
||||
$courseoptions[$course->id] = format_string($course->fullname, true,
|
||||
['context' => \context_course::instance($course->id)]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'courseid', get_string('course'), $courseoptions);
|
||||
$mform->disabledIf('courseid', 'eventtype', 'noteq', 'course');
|
||||
}
|
||||
|
||||
if (isset($eventtypes['group'])) {
|
||||
$courseoptions = [];
|
||||
foreach ($eventtypes['groupcourses'] as $course) {
|
||||
$courseoptions[$course->id] = format_string($course->fullname, true,
|
||||
['context' => \context_course::instance($course->id)]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'groupcourseid', get_string('course'), $courseoptions);
|
||||
$mform->disabledIf('groupcourseid', 'eventtype', 'noteq', 'group');
|
||||
|
||||
$groupoptions = [];
|
||||
foreach ($eventtypes['group'] as $group) {
|
||||
// We are formatting it this way in order to provide the javascript both
|
||||
// the course and group ids so that it can enhance the form for the user.
|
||||
$index = "{$group->courseid}-{$group->id}";
|
||||
$groupoptions[$index] = format_string($group->name, true,
|
||||
['context' => \context_course::instance($group->courseid)]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'groupid', get_string('group'), $groupoptions);
|
||||
$mform->disabledIf('groupid', 'eventtype', 'noteq', 'group');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the various elements to express the duration options available
|
||||
* for an event.
|
||||
*
|
||||
* @method add_event_duration_elements
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
protected function add_event_duration_elements($mform) {
|
||||
$group = [];
|
||||
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationnone', 'calendar'), 0);
|
||||
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
|
||||
$group[] = $mform->createElement('date_time_selector', 'timedurationuntil', '');
|
||||
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
|
||||
$group[] = $mform->createElement('text', 'timedurationminutes', get_string('durationminutes', 'calendar'));
|
||||
|
||||
$mform->addGroup($group, 'durationgroup', get_string('eventduration', 'calendar'), '<br />', false);
|
||||
$mform->setAdvanced('durationgroup');
|
||||
|
||||
$mform->disabledIf('timedurationuntil', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[day]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[month]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[year]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[hour]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[minute]', 'duration', 'noteq', 1);
|
||||
|
||||
$mform->setType('timedurationminutes', PARAM_INT);
|
||||
$mform->disabledIf('timedurationminutes','duration','noteq', 2);
|
||||
|
||||
$mform->setDefault('duration', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the repeat elements for the form when creating a new event.
|
||||
*
|
||||
* @method add_event_repeat_elements
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
protected function add_event_repeat_elements($mform) {
|
||||
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
|
||||
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
|
||||
$mform->setType('repeats', PARAM_INT);
|
||||
$mform->setDefault('repeats', 1);
|
||||
$mform->disabledIf('repeats','repeat','notchecked');
|
||||
$mform->setAdvanced('repeat');
|
||||
$mform->setAdvanced('repeats');
|
||||
}
|
||||
}
|
60
calendar/classes/local/event/forms/update.php
Normal file
60
calendar/classes/local/event/forms/update.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* The mform for updating a calendar event. Based on the
|
||||
* old event form.
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @package calendar
|
||||
*/
|
||||
namespace core_calendar\local\event\forms;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/lib/formslib.php');
|
||||
|
||||
/**
|
||||
* The mform class for updating a calendar event.
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class update extends create {
|
||||
/**
|
||||
* Add the repeat elements for the form when editing an existing event.
|
||||
*
|
||||
* @method add_event_repeat_elements
|
||||
* @param MoodleQuickForm $mform
|
||||
* @param stdClass $event The event properties
|
||||
*/
|
||||
protected function add_event_repeat_elements($mform) {
|
||||
$event = $this->_customdata['event'];
|
||||
|
||||
$mform->addElement('hidden', 'repeatid');
|
||||
$mform->setType('repeatid', PARAM_INT);
|
||||
|
||||
$group = [];
|
||||
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditall', 'calendar', $event->eventrepeats), 1);
|
||||
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditthis', 'calendar'), 0);
|
||||
$mform->addGroup($group, 'repeatgroup', get_string('repeatedevents', 'calendar'), '<br />', false);
|
||||
|
||||
$mform->setDefault('repeateditall', 1);
|
||||
$mform->setAdvanced('repeatgroup');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Event create form and update form mapper.
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_calendar\local\event\mappers;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
/**
|
||||
* Event create form and update form mapper class.
|
||||
*
|
||||
* This class will perform the necessary data transformations to take
|
||||
* a legacy event and build the appropriate data structure for both the
|
||||
* create and update event forms.
|
||||
*
|
||||
* It will also do the reverse transformation
|
||||
* and take the returned form data and provide a data structure that can
|
||||
* be used to set legacy event properties.
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class create_update_form_mapper implements create_update_form_mapper_interface {
|
||||
|
||||
/**
|
||||
* Generate the appropriate data for the form from a legacy event.
|
||||
*
|
||||
* @method from_legacy_event_to_data
|
||||
* @param calendar_event $legacyevent
|
||||
* @return stdClass
|
||||
*/
|
||||
public function from_legacy_event_to_data(\calendar_event $legacyevent) {
|
||||
$legacyevent->count_repeats();
|
||||
$data = $legacyevent->properties(true);
|
||||
$data->timedurationuntil = $legacyevent->timestart + $legacyevent->timeduration;
|
||||
$data->duration = (empty($legacyevent->timeduration)) ? 0 : 1;
|
||||
|
||||
if ($legacyevent->eventtype == 'group') {
|
||||
// Set up the correct value for the to display on the form.
|
||||
$data->groupid = "{$legacyevent->courseid}-{$legacyevent->groupid}";
|
||||
$data->groupcourseid = $legacyevent->courseid;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the appropriate calendar_event properties from the form data.
|
||||
*
|
||||
* @method from_data_to_event_properties
|
||||
* @param stdClass $data
|
||||
* @return stdClass
|
||||
*/
|
||||
public function from_data_to_event_properties(\stdClass $data) {
|
||||
$properties = clone($data);
|
||||
|
||||
// Undo the form definition work around to allow us to have two different
|
||||
// course selectors present depending on which event type the user selects.
|
||||
if (isset($data->groupcourseid)) {
|
||||
$properties->courseid = $data->groupcourseid;
|
||||
unset($properties->groupcourseid);
|
||||
}
|
||||
|
||||
// Pull the group id back out of the value. The form saves the value
|
||||
// as "<courseid>-<groupid>" to allow the javascript to work correctly.
|
||||
if (isset($data->groupid)) {
|
||||
list($courseid, $groupid) = explode('-', $data->groupid);
|
||||
$properties->groupid = $groupid;
|
||||
}
|
||||
|
||||
// Default course id if none is set.
|
||||
if (!isset($data->courseid)) {
|
||||
$properties->courseid = 0;
|
||||
}
|
||||
|
||||
// Decode the form fields back into valid event property.
|
||||
$properties->timeduration = $this->get_time_duration_from_form_data($data);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to calculate the time duration for an event based on
|
||||
* the event_form data.
|
||||
*
|
||||
* @method get_time_duration_from_form_data
|
||||
* @param \stdClass $data event_form data
|
||||
* @return int
|
||||
*/
|
||||
private function get_time_duration_from_form_data(\stdClass $data) {
|
||||
if ($data->duration == 1) {
|
||||
return $data->timedurationuntil- $data->timestart;
|
||||
} else if ($data->duration == 2) {
|
||||
return $data->timedurationminutes * MINSECS;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Create update form mapper interface.
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_calendar\local\event\mappers;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
/**
|
||||
* Interface for a create_update_form_mapper class
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface create_update_form_mapper_interface {
|
||||
/**
|
||||
* Generate the appropriate data for the form from a legacy event.
|
||||
*
|
||||
* @method from_legacy_event_to_data
|
||||
* @param calendar_event $legacyevent
|
||||
* @return stdClass
|
||||
*/
|
||||
public function from_legacy_event_to_data(\calendar_event $legacyevent);
|
||||
|
||||
/**
|
||||
* Generate the appropriate calendar_event properties from the form data.
|
||||
*
|
||||
* @method from_data_to_event_properties
|
||||
* @param stdClass $data
|
||||
* @return stdClass
|
||||
*/
|
||||
public function from_data_to_event_properties(\stdClass $data);
|
||||
}
|
|
@ -30,6 +30,11 @@ defined('MOODLE_INTERNAL') || die;
|
|||
require_once("$CFG->libdir/externallib.php");
|
||||
|
||||
use \core_calendar\local\api as local_api;
|
||||
use \core_calendar\local\event\container as event_container;
|
||||
use \core_calendar\local\event\forms\create as create_event_form;
|
||||
use \core_calendar\local\event\forms\update as update_event_form;
|
||||
use \core_calendar\local\event\mappers\create_update_form_mapper;
|
||||
use \core_calendar\external\event_exporter;
|
||||
use \core_calendar\external\events_exporter;
|
||||
use \core_calendar\external\events_grouped_by_course_exporter;
|
||||
use \core_calendar\external\events_related_objects_cache;
|
||||
|
@ -779,4 +784,90 @@ class core_calendar_external extends external_api {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters.
|
||||
*
|
||||
* @return external_function_parameters.
|
||||
*/
|
||||
public static function submit_create_update_form_parameters() {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'formdata' => new external_value(PARAM_RAW, 'The data from the event form'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event form submission.
|
||||
*
|
||||
* @param string $formdata The event form data in a URI encoded param string
|
||||
* @return array The created or modified event
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function submit_create_update_form($formdata) {
|
||||
global $CFG, $USER, $PAGE;
|
||||
require_once($CFG->dirroot."/calendar/lib.php");
|
||||
|
||||
// Parameter validation.
|
||||
$params = self::validate_parameters(self::submit_create_update_form_parameters(), ['formdata' => $formdata]);
|
||||
$context = \context_user::instance($USER->id);
|
||||
$data = [];
|
||||
|
||||
self::validate_context($context);
|
||||
parse_str($params['formdata'], $data);
|
||||
|
||||
if (!empty($data['id'])) {
|
||||
$eventid = clean_param($data['id'], PARAM_INT);
|
||||
$legacyevent = calendar_event::load($eventid);
|
||||
$legacyevent->count_repeats();
|
||||
$formoptions = ['event' => $legacyevent];
|
||||
$mform = new update_event_form(null, $formoptions, 'post', '', null, true, $data);
|
||||
} else {
|
||||
$legacyevent = null;
|
||||
$mform = new create_event_form(null, null, 'post', '', null, true, $data);
|
||||
}
|
||||
|
||||
if ($validateddata = $mform->get_data()) {
|
||||
$formmapper = new create_update_form_mapper();
|
||||
$properties = $formmapper->from_data_to_event_properties($validateddata);
|
||||
|
||||
if (is_null($legacyevent)) {
|
||||
$legacyevent = new \calendar_event($properties);
|
||||
}
|
||||
|
||||
$legacyevent->update($properties);
|
||||
|
||||
$eventmapper = event_container::get_event_mapper();
|
||||
$event = $eventmapper->from_legacy_event_to_event($legacyevent);
|
||||
$cache = new events_related_objects_cache([$event]);
|
||||
$relatedobjects = [
|
||||
'context' => $cache->get_context($event),
|
||||
'course' => $cache->get_course($event),
|
||||
];
|
||||
$exporter = new event_exporter($event, $relatedobjects);
|
||||
$renderer = $PAGE->get_renderer('core_calendar');
|
||||
|
||||
return [ 'event' => $exporter->export($renderer) ];
|
||||
} else {
|
||||
return [ 'validationerror' => true ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value.
|
||||
*
|
||||
* @return external_description.
|
||||
*/
|
||||
public static function submit_create_update_form_returns() {
|
||||
$eventstructure = event_exporter::get_read_structure();
|
||||
$eventstructure->required = VALUE_OPTIONAL;
|
||||
|
||||
return new external_single_structure(
|
||||
array(
|
||||
'event' => $eventstructure,
|
||||
'validationerror' => new external_value(PARAM_BOOL, 'Invalid form data', VALUE_DEFAULT, false),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
174
calendar/lib.php
174
calendar/lib.php
|
@ -2686,8 +2686,9 @@ function calendar_set_event_type_display($type, $display = null, $user = null) {
|
|||
*
|
||||
* @param stdClass $allowed list of allowed edit for event type
|
||||
* @param stdClass|int $course object of a course or course id
|
||||
* @param array $groups array of groups for the given course
|
||||
*/
|
||||
function calendar_get_allowed_types(&$allowed, $course = null) {
|
||||
function calendar_get_allowed_types(&$allowed, $course = null, $groups = null) {
|
||||
global $USER, $DB;
|
||||
|
||||
$allowed = new \stdClass();
|
||||
|
@ -2695,6 +2696,23 @@ function calendar_get_allowed_types(&$allowed, $course = null) {
|
|||
$allowed->groups = false;
|
||||
$allowed->courses = false;
|
||||
$allowed->site = has_capability('moodle/calendar:manageentries', \context_course::instance(SITEID));
|
||||
$getgroupsfunc = function($course, $context, $user) use ($groups) {
|
||||
if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
|
||||
if (has_capability('moodle/site:accessallgroups', $context)) {
|
||||
return is_null($groups) ? groups_get_all_groups($course->id) : $groups;
|
||||
} else {
|
||||
if (is_null($groups)) {
|
||||
return groups_get_all_groups($course->id, $user->id);
|
||||
} else {
|
||||
return array_filter($groups, function($group) use ($user) {
|
||||
return isset($group->members[$user->id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!empty($course)) {
|
||||
if (!is_object($course)) {
|
||||
|
@ -2706,27 +2724,84 @@ function calendar_get_allowed_types(&$allowed, $course = null) {
|
|||
|
||||
if (has_capability('moodle/calendar:manageentries', $coursecontext)) {
|
||||
$allowed->courses = array($course->id => 1);
|
||||
|
||||
if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
|
||||
if (has_capability('moodle/site:accessallgroups', $coursecontext)) {
|
||||
$allowed->groups = groups_get_all_groups($course->id);
|
||||
} else {
|
||||
$allowed->groups = groups_get_all_groups($course->id, $USER->id);
|
||||
}
|
||||
}
|
||||
$allowed->groups = $getgroupsfunc($course, $coursecontext, $USER);
|
||||
} else if (has_capability('moodle/calendar:managegroupentries', $coursecontext)) {
|
||||
if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
|
||||
if (has_capability('moodle/site:accessallgroups', $coursecontext)) {
|
||||
$allowed->groups = groups_get_all_groups($course->id);
|
||||
} else {
|
||||
$allowed->groups = groups_get_all_groups($course->id, $USER->id);
|
||||
}
|
||||
}
|
||||
$allowed->groups = $getgroupsfunc($course, $coursecontext, $USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the allowed types for all of the courses and groups
|
||||
* the logged in user belongs to.
|
||||
*
|
||||
* The returned array will optionally have 5 keys:
|
||||
* 'user' : true if the logged in user can create user events
|
||||
* 'site' : true if the logged in user can create site events
|
||||
* 'course' : array of courses that the user can create events for
|
||||
* 'group': array of groups that the user can create events for
|
||||
* 'groupcourses' : array of courses that the groups belong to (can
|
||||
* be different from the list in 'course'.
|
||||
*
|
||||
* @param array The available types for the logged in user
|
||||
*/
|
||||
function calendar_get_all_allowed_types() {
|
||||
global $CFG, $USER;
|
||||
|
||||
require_once($CFG->libdir . '/enrollib.php');
|
||||
|
||||
$types = [];
|
||||
|
||||
calendar_get_allowed_types($allowed);
|
||||
|
||||
if ($allowed->user) {
|
||||
$types['user'] = true;
|
||||
}
|
||||
|
||||
if ($allowed->site) {
|
||||
$types['site'] = true;
|
||||
}
|
||||
|
||||
// This function warms the context cache for the course so the calls
|
||||
// to load the course context in calendar_get_allowed_types don't result
|
||||
// in additional DB queries.
|
||||
$courses = enrol_get_users_courses($USER->id, true);
|
||||
// We want to pre-fetch all of the groups for each course in a single
|
||||
// query to avoid calendar_get_allowed_types from hitting the DB for
|
||||
// each separate course.
|
||||
$groups = groups_get_all_groups_for_courses($courses);
|
||||
|
||||
foreach ($courses as $course) {
|
||||
$coursegroups = isset($groups[$course->id]) ? $groups[$course->id] : null;
|
||||
calendar_get_allowed_types($allowed, $course, $coursegroups);
|
||||
|
||||
if (!empty($allowed->courses)) {
|
||||
if (!isset($types['course'])) {
|
||||
$types['course'] = [$course];
|
||||
} else {
|
||||
$types['course'][] = $course;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($allowed->groups)) {
|
||||
if (!isset($types['groupcourses'])) {
|
||||
$types['groupcourses'] = [$course];
|
||||
} else {
|
||||
$types['groupcourses'][] = $course;
|
||||
}
|
||||
|
||||
if (!isset($types['group'])) {
|
||||
$types['group'] = array_values($allowed->groups);
|
||||
} else {
|
||||
$types['group'] = array_merge($types['group'], array_values($allowed->groups));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if user can add calendar entries at all used to print the "New Event" button.
|
||||
*
|
||||
|
@ -3340,3 +3415,70 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $
|
|||
return $carry + [$event->get_id() => $mapper->from_event_to_stdclass($event)];
|
||||
}, []);
|
||||
}
|
||||
|
||||
function calendar_output_fragment_event_form($args) {
|
||||
global $CFG, $OUTPUT;
|
||||
require_once($CFG->dirroot.'/calendar/event_form.php');
|
||||
|
||||
$html = '';
|
||||
$data = null;
|
||||
$eventid = isset($args['eventid']) ? clean_param($args['eventid'], PARAM_INT) : null;
|
||||
$event = null;
|
||||
$hasformdata = isset($args['formdata']) && !empty($args['formdata']);
|
||||
$formoptions = [];
|
||||
|
||||
if ($hasformdata) {
|
||||
parse_str(clean_param($args['formdata'], PARAM_TEXT), $data);
|
||||
}
|
||||
|
||||
if (isset($args['haserror'])) {
|
||||
$formoptions['haserror'] = clean_param($args['haserror'], PARAM_BOOL);
|
||||
}
|
||||
|
||||
if (is_null($eventid)) {
|
||||
$mform = new \core_calendar\local\event\forms\create(
|
||||
null,
|
||||
$formoptions,
|
||||
'post',
|
||||
'',
|
||||
null,
|
||||
true,
|
||||
$data
|
||||
);
|
||||
} else {
|
||||
$event = calendar_event::load($eventid);
|
||||
$event->count_repeats();
|
||||
$formoptions['event'] = $event;
|
||||
$mform = new \core_calendar\local\event\forms\update(
|
||||
null,
|
||||
$formoptions,
|
||||
'post',
|
||||
'',
|
||||
null,
|
||||
true,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
if ($hasformdata) {
|
||||
$mform->is_validated();
|
||||
} else if (!is_null($event)) {
|
||||
$mapper = new \core_calendar\local\event\mappers\create_update_form_mapper();
|
||||
$data = $mapper->from_legacy_event_to_data($event);
|
||||
$mform->set_data($data);
|
||||
|
||||
// Check to see if this event is part of a subscription or import.
|
||||
// If so display a warning on edit.
|
||||
if (isset($event->subscriptionid) && ($event->subscriptionid != null)) {
|
||||
$renderable = new \core\output\notification(
|
||||
get_string('eventsubscriptioneditwarning', 'calendar'),
|
||||
\core\output\notification::NOTIFY_INFO
|
||||
);
|
||||
|
||||
$html .= $OUTPUT->render($renderable);
|
||||
}
|
||||
}
|
||||
|
||||
$html .= $mform->render();
|
||||
return $html;
|
||||
}
|
||||
|
|
|
@ -142,18 +142,13 @@ class core_calendar_renderer extends plugin_renderer_base {
|
|||
$time = time();
|
||||
}
|
||||
|
||||
$output = html_writer::start_tag('div', array('class'=>'buttons'));
|
||||
$output .= html_writer::start_tag('form', array('action' => CALENDAR_URL . 'event.php', 'method' => 'get'));
|
||||
$output .= html_writer::start_tag('div');
|
||||
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name' => 'action', 'value' => 'new'));
|
||||
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name' => 'course', 'value' => $courseid));
|
||||
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name' => 'time', 'value' => $time));
|
||||
$attributes = array('type' => 'submit', 'value' => get_string('newevent', 'calendar'), 'class' => 'btn btn-secondary');
|
||||
$output .= html_writer::empty_tag('input', $attributes);
|
||||
$output .= html_writer::end_tag('div');
|
||||
$output .= html_writer::end_tag('form');
|
||||
$output .= html_writer::end_tag('div');
|
||||
return $output;
|
||||
$coursecontext = \context_course::instance($courseid);
|
||||
$attributes = [
|
||||
'class' => 'btn btn-secondary pull-xs-right pull-right',
|
||||
'data-context-id' => $coursecontext->id,
|
||||
'data-action' => 'new-event-button'
|
||||
];
|
||||
return html_writer::tag('button', get_string('newevent', 'calendar'), $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
61
calendar/templates/modal_event_form.mustache
Normal file
61
calendar/templates/modal_event_form.mustache
Normal file
|
@ -0,0 +1,61 @@
|
|||
{{!
|
||||
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 calendar/modal_event_form
|
||||
|
||||
Moodle modal template with save and cancel buttons.
|
||||
|
||||
The purpose of this template is to render a modal.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* title A cleaned string (use clean_text()) to display.
|
||||
* body HTML content for the boday
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"title": "Example save cancel modal",
|
||||
"body": "Some example content for the body"
|
||||
}
|
||||
}}
|
||||
|
||||
{{< core/modal }}
|
||||
{{$footer}}
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
data-collapsed="true"
|
||||
data-action="more-less-toggle">
|
||||
|
||||
{{#str}} more, calendar {{/str}}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
data-context-id="{{contextid}}"
|
||||
data-action="save">
|
||||
|
||||
{{#str}} save {{/str}}
|
||||
<span class="hidden" data-region="loading-icon-container">
|
||||
{{> core/loading }}
|
||||
</span>
|
||||
</button>
|
||||
{{/footer}}
|
||||
{{/ core/modal }}
|
|
@ -409,4 +409,267 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
|||
$events = calendar_get_legacy_events($timestart, $timeend, true, true, true);
|
||||
$this->assertCount(3, $events);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_no_types() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$systemcontext = context_system::instance();
|
||||
$sitecontext = context_course::instance(SITEID);
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->role_assign($roleid, $user->id, $systemcontext->id);
|
||||
$generator->role_assign($roleid, $user->id, $sitecontext->id);
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $sitecontext, true);
|
||||
assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $systemcontext, true);
|
||||
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$this->assertEmpty($types);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_user() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$context = context_system::instance();
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
|
||||
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$this->assertTrue($types['user']);
|
||||
|
||||
assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
|
||||
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$this->assertArrayNotHasKey('user', $types);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_site() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$context = context_course::instance(SITEID);
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$this->assertTrue($types['site']);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
|
||||
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$this->assertArrayNotHasKey('site', $types);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_course() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course1 = $generator->create_course(); // Has capability.
|
||||
$course2 = $generator->create_course(); // Doesn't have capability.
|
||||
$course3 = $generator->create_course(); // Not enrolled.
|
||||
$context1 = context_course::instance($course1->id);
|
||||
$context2 = context_course::instance($course2->id);
|
||||
$context3 = context_course::instance($course3->id);
|
||||
$roleid = $generator->create_role();
|
||||
$contexts = [$context1, $context2, $context3];
|
||||
$enrolledcourses = [$course1, $course2];
|
||||
|
||||
foreach ($enrolledcourses as $course) {
|
||||
$generator->enrol_user($user->id, $course->id, 'student');
|
||||
}
|
||||
|
||||
foreach ($contexts as $context) {
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
}
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context1, true);
|
||||
assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context2, true);
|
||||
|
||||
// The user only has the correct capability in course 1 so that is the only
|
||||
// one that should be in the results.
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$typecourses = $types['course'];
|
||||
$this->assertCount(1, $typecourses);
|
||||
$this->assertEquals($course1->id, $typecourses[0]->id);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context2, true);
|
||||
|
||||
// The user only now has the correct capability in both course 1 and 2 so we
|
||||
// expect both to be in the results.
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$typecourses = $types['course'];
|
||||
// Sort the results by id ascending to ensure the test is consistent
|
||||
// and repeatable.
|
||||
usort($typecourses, function($a, $b) {
|
||||
$aid = $a->id;
|
||||
$bid = $b->id;
|
||||
|
||||
if ($aid == $bid) {
|
||||
return 0;
|
||||
}
|
||||
return ($aid < $bid) ? -1 : 1;
|
||||
});
|
||||
|
||||
$this->assertCount(2, $typecourses);
|
||||
$this->assertEquals($course1->id, $typecourses[0]->id);
|
||||
$this->assertEquals($course2->id, $typecourses[1]->id);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_group_no_groups() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, 'student');
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||
|
||||
// The user has the correct capability in the course but there are
|
||||
// no groups so we shouldn't see a group type.
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$typecourses = $types['course'];
|
||||
$this->assertCount(1, $typecourses);
|
||||
$this->assertEquals($course->id, $typecourses[0]->id);
|
||||
$this->assertArrayNotHasKey('group', $types);
|
||||
$this->assertArrayNotHasKey('groupcourses', $types);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_group_no_acces_to_diff_groups() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
$group1 = $generator->create_group(array('courseid' => $course->id));
|
||||
$group2 = $generator->create_group(array('courseid' => $course->id));
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, 'student');
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
|
||||
|
||||
// The user has the correct capability in the course but they aren't a member
|
||||
// of any of the groups and don't have the accessallgroups capability.
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$typecourses = $types['course'];
|
||||
$this->assertCount(1, $typecourses);
|
||||
$this->assertEquals($course->id, $typecourses[0]->id);
|
||||
$this->assertArrayNotHasKey('group', $types);
|
||||
$this->assertArrayNotHasKey('groupcourses', $types);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_group_access_all_groups() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course1 = $generator->create_course();
|
||||
$course2 = $generator->create_course();
|
||||
$context1 = context_course::instance($course1->id);
|
||||
$context2 = context_course::instance($course2->id);
|
||||
$group1 = $generator->create_group(array('courseid' => $course1->id));
|
||||
$group2 = $generator->create_group(array('courseid' => $course1->id));
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->enrol_user($user->id, $course1->id, 'student');
|
||||
$generator->enrol_user($user->id, $course2->id, 'student');
|
||||
$generator->role_assign($roleid, $user->id, $context1->id);
|
||||
$generator->role_assign($roleid, $user->id, $context2->id);
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context1, true);
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context2, true);
|
||||
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context1, true);
|
||||
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context2, true);
|
||||
|
||||
// The user has the correct capability in the course and has
|
||||
// the accessallgroups capability.
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$typecourses = $types['course'];
|
||||
$typegroups = $types['group'];
|
||||
$typegroupcourses = $types['groupcourses'];
|
||||
$idascfunc = function($a, $b) {
|
||||
$aid = $a->id;
|
||||
$bid = $b->id;
|
||||
|
||||
if ($aid == $bid) {
|
||||
return 0;
|
||||
}
|
||||
return ($aid < $bid) ? -1 : 1;
|
||||
};
|
||||
// Sort the results by id ascending to ensure the test is consistent
|
||||
// and repeatable.
|
||||
usort($typecourses, $idascfunc);
|
||||
usort($typegroups, $idascfunc);
|
||||
|
||||
$this->assertCount(2, $typecourses);
|
||||
$this->assertEquals($course1->id, $typecourses[0]->id);
|
||||
$this->assertEquals($course2->id, $typecourses[1]->id);
|
||||
$this->assertCount(1, $typegroupcourses);
|
||||
$this->assertEquals($course1->id, $typegroupcourses[0]->id);
|
||||
$this->assertCount(2, $typegroups);
|
||||
$this->assertEquals($group1->id, $typegroups[0]->id);
|
||||
$this->assertEquals($group2->id, $typegroups[1]->id);
|
||||
}
|
||||
|
||||
public function test_calendar_get_all_allowed_types_group_no_access_all_groups() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
$group1 = $generator->create_group(array('courseid' => $course->id));
|
||||
$group2 = $generator->create_group(array('courseid' => $course->id));
|
||||
$group3 = $generator->create_group(array('courseid' => $course->id));
|
||||
$roleid = $generator->create_role();
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, 'student');
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
$generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user->id));
|
||||
$generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user->id));
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
|
||||
|
||||
// The user has the correct capability in the course but can't access
|
||||
// groups that they are not a member of.
|
||||
$types = calendar_get_all_allowed_types();
|
||||
$typegroups = $types['group'];
|
||||
$typegroupcourses = $types['groupcourses'];
|
||||
$idascfunc = function($a, $b) {
|
||||
$aid = $a->id;
|
||||
$bid = $b->id;
|
||||
|
||||
if ($aid == $bid) {
|
||||
return 0;
|
||||
}
|
||||
return ($aid < $bid) ? -1 : 1;
|
||||
};
|
||||
// Sort the results by id ascending to ensure the test is consistent
|
||||
// and repeatable.
|
||||
usort($typegroups, $idascfunc);
|
||||
|
||||
$this->assertCount(1, $typegroupcourses);
|
||||
$this->assertEquals($course->id, $typegroupcourses[0]->id);
|
||||
$this->assertCount(2, $typegroups);
|
||||
$this->assertEquals($group1->id, $typegroups[0]->id);
|
||||
$this->assertEquals($group2->id, $typegroups[1]->id);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue