Skip to content

Commit

Permalink
SAK-46122 Date Manager attempts to load datepickers for all dates on …
Browse files Browse the repository at this point in the history
…page load, becoming unresponsive when there are many dates (sakaiproject#9696)
  • Loading branch information
plukasew authored Aug 27, 2021
1 parent 17781b0 commit 4c8d013
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.sakaiproject.datemanager;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
*
* @author plukasew
*/
@Configuration
@EnableWebMvc
@ComponentScan("org.sakaiproject.datemanager")
public class MvcConfig extends WebMvcConfigurerAdapter
{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.setServletContext(servletContext);
rootContext.register(ThymeleafConfig.class);
rootContext.register(MvcConfig.class);

servletContext.addListener(new ToolListener());
servletContext.addListener(new SakaiContextLoaderListener(rootContext));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
var DTMN = DTMN || {};

DTMN.toolList = [ "assignments", "assessments", "signup", "gradebook", "resources", "calendar", "forums", "announcements", "lessons" ];
DTMN.collapseElements = [ ];
DTMN.nextIndex = -1;

DTMN.initDatePicker = function(updates, notModified)
{
// use an event listener to populate the date pickers on demand instead of populating everything on page load
for (let i = 0; i < DTMN.toolList.length; i++)
{
const collapseId = "#collapse-" + DTMN.toolList[i];
const collapseElement = document.querySelector(collapseId);
if (collapseElement !== null)
{
DTMN.collapseElements.push(collapseElement);
}
const link = document.querySelector("a[href='" + collapseId + "']");
if (link !== null)
{
const selector = collapseId + " .datepicker:not(.hasDatepicker)";
const target = document.querySelector(collapseId);
$(target).on("show.bs.collapse", function()
{
const spinner = link.querySelector(".allocatedSpinPlaceholder");
spinner.classList.add("spinPlaceholder");
window.setTimeout(function()
{
DTMN.attachDatePicker(selector, updates, notModified);
spinner.classList.remove("spinPlaceholder");
}, 25); // delay 25ms to give browser time to render the spinner
});
}
}
};

DTMN.attachDatePicker = function(selector, updates, notModified)
{
$(selector).each(function (idx, elt) {
DTMN.nextIndex = DTMN.nextIndex + 1;
var $td = $(elt).closest('td');
var $hidden = $td.find('input[type=hidden]');

var dataTool = $hidden.data('tool');
var dataField = $hidden.data('field');
var dataIdx = $hidden.data('idx');
$td.attr('id', 'cell_' + dataTool + '_' + dataField + '_' + dataIdx);

$hidden.on('change', function () {
var idx = $(this).data('idx');
var field = $(this).data('field');
var tool = $(this).data('tool');

updates[tool][idx][field] = $(this).val().split('+')[0];
updates[tool][idx][field + '_label'] = $(this).siblings('input.datepicker').val();
// Show day of the week in case there is a date selected
if ($(this).parent().find('.datepicker').val() !== '') {
updates[tool][idx][field + '_day_of_week'] = moment(updates[tool][idx][field]).locale(sakai.locale.userLocale).format('dddd');
$(this).parent().find('.day-of-week').text(updates[tool][idx][field + '_day_of_week']);
}

if (notModified.includes(tool + idx + field)) {
updates[tool][idx].idx = idx;
updates[tool + 'Upd'][idx] = updates[tool][idx];
$('#submit-form-button').prop('disabled', false);
}
notModified.push(tool + idx + field);
});

$hidden.attr('id', 'hidden_datepicker_' + DTMN.nextIndex);
var dateFormat = 'YYYY-MM-DD HH:mm:ss';
var toolTime = 1;
if(dataTool === 'gradebookItems') {
dateFormat = 'YYYY-MM-DD';
toolTime = 0;
}
var datepickerOpts = {
input: elt,
useTime: toolTime,
parseFormat: dateFormat,
allowEmptyDate: false,
ashidden: {
iso8601: 'hidden_datepicker_' + DTMN.nextIndex,
}
};
if ($hidden.val() !== '') datepickerOpts.val = $hidden.val();
localDatePicker(datepickerOpts);

// Disable accept_until date input if no late submissions (assessments) allowed
if (dataTool === 'assessments' && dataField === 'accept_until') {
var disabled = !updates[dataTool][dataIdx].late_handling;
$(elt).prop('disabled', disabled);
$td.find('.ui-datepicker-trigger').prop('disabled', disabled);
}
// Disable feedback start and end date inputs if feedback on date not used (assessments)
if (dataTool === 'assessments' && (dataField === 'feedback_start' || dataField === 'feedback_end')) {
var disabled = !updates[dataTool][dataIdx].feedback_by_date;
$(elt).prop('disabled', disabled);
$td.find('.ui-datepicker-trigger').prop('disabled', disabled);
}
if (dataTool === 'forums' && (dataField === 'open_date' || dataField === 'due_date')) {
var disabled = !updates[dataTool][dataIdx].restricted;
$(elt).prop('disabled', disabled);
$td.find('.ui-datepicker-trigger').prop('disabled', disabled);
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/*]]>*/
</script>
<script type="text/javascript" src="/library/js/lang-datepicker/lang-datepicker.js"></script>
<script th:src="@{/js/initDatePicker.js}"></script>
<meta http-equiv="Content-Style-Type" content="text/css" />

<div class="page-header">
Expand All @@ -37,71 +38,71 @@ <h1 th:text="#{page.instructions}">Add/Edit Options in batch</h1>
<div th:if="${assignments != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-assignments" class="collapsed" aria-expanded="false"><span th:text="${assignmentsToolTitle}">Assignments</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-assignments" class="collapsed" aria-expanded="false"><span th:text="${assignmentsToolTitle}">Assignments</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${assignments}, toolId='assignments', collapseId='collapse-assignments')"></div>
</div>
<div th:if="${assessments != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-assessments" class="collapsed" aria-expanded="false"><span th:text="${assessmentsToolTitle}">Tests & Quizzes</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-assessments" class="collapsed" aria-expanded="false"><span th:text="${assessmentsToolTitle}">Tests & Quizzes</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${assessments}, toolId='assessments', collapseId='collapse-assessments')"></div>
</div>
<div th:if="${signupMeetings != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-signup" class="collapsed" aria-expanded="false"><span th:text="${signupMeetingsToolTitle}">Sign up</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-signup" class="collapsed" aria-expanded="false"><span th:text="${signupMeetingsToolTitle}">Sign up</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${signupMeetings}, toolId='signupMeetings', collapseId='collapse-signup')"></div>
</div>
<div th:if="${gradebookItems != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-gradebook" class="collapsed" aria-expanded="false"><span th:text="${gradebookItemsToolTitle}">Gradebook</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-gradebook" class="collapsed" aria-expanded="false"><span th:text="${gradebookItemsToolTitle}">Gradebook</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${gradebookItems}, toolId='gradebookItems', collapseId='collapse-gradebook')"></div>
</div>
<div th:if="${resources != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-resources" class="collapsed" aria-expanded="false"><span th:text="${resourcesToolTitle}">Resources</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-resources" class="collapsed" aria-expanded="false"><span th:text="${resourcesToolTitle}">Resources</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${resources}, toolId='resources', collapseId='collapse-resources')"></div>
</div>
<div th:if="${calendarEvents != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-calendar" class="collapsed" aria-expanded="false"><span th:text="${calendarEventsToolTitle}">Calendar</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-calendar" class="collapsed" aria-expanded="false"><span th:text="${calendarEventsToolTitle}">Calendar</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${calendarEvents}, toolId='calendarEvents', collapseId='collapse-calendar')"></div>
</div>
<div th:if="${forums != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-forums" class="collapsed" aria-expanded="false"><span th:text="${forumsToolTitle}">Forums</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-forums" class="collapsed" aria-expanded="false"><span th:text="${forumsToolTitle}">Forums</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${forums}, toolId='forums', collapseId='collapse-forums')"></div>
</div>
<div th:if="${announcements != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-announcements" class="collapsed" aria-expanded="false"><span th:text="${announcementsToolTitle}">Announcements</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-announcements" class="collapsed" aria-expanded="false"><span th:text="${announcementsToolTitle}">Announcements</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${announcements}, toolId='announcements', collapseId='collapse-announcements')"></div>
</div>
<div th:if="${lessons != null}" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-lessons" class="collapsed" aria-expanded="false"><span th:text="${lessonsToolTitle}">Lessons</span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-lessons" class="collapsed" aria-expanded="false"><span th:text="${lessonsToolTitle}">Lessons</span><span class="allocatedSpinPlaceholder"></span></a>
</h4>
</div>
<div th:replace="tool_fragment :: tool_fragment(items=${lessons}, toolId='lessons', collapseId='collapse-lessons')"></div>
Expand Down Expand Up @@ -232,73 +233,7 @@ <h4 th:text="#{modal.confirm}" class="modal-title">Confirm</h4>

var notModified = [];

$('.datepicker').each(function (idx, elt) {
var $td = $(elt).closest('td');
var $hidden = $td.find('input[type=hidden]');

var dataTool = $hidden.data('tool');
var dataField = $hidden.data('field');
var dataIdx = $hidden.data('idx');
$td.attr('id', 'cell_' + dataTool + '_' + dataField + '_' + dataIdx);

$hidden.on('change', function () {
var idx = $(this).data('idx');
var field = $(this).data('field');
var tool = $(this).data('tool');

updates[tool][idx][field] = $(this).val().split('+')[0];
updates[tool][idx][field + '_label'] = $(this).siblings('input.datepicker').val();
// Show day of the week in case there is a date selected
if ($(this).parent().find('.datepicker').val() !== '') {
updates[tool][idx][field + '_day_of_week'] = moment(updates[tool][idx][field]).locale(sakai.locale.userLocale).format('dddd');
$(this).parent().find('.day-of-week').text(updates[tool][idx][field + '_day_of_week']);
}

if (notModified.includes(tool + idx + field)) {
updates[tool][idx].idx = idx;
updates[tool + 'Upd'][idx] = updates[tool][idx];
$('#submit-form-button').prop('disabled', false);
}
notModified.push(tool + idx + field);
});

$hidden.attr('id', 'hidden_datepicker_' + idx);
var dateFormat = 'YYYY-MM-DD HH:mm:ss';
var toolTime = 1;
if(dataTool == 'gradebookItems') {
dateFormat = 'YYYY-MM-DD';
toolTime = 0;
}
var datepickerOpts = {
input: elt,
useTime: toolTime,
parseFormat: dateFormat,
allowEmptyDate: false,
ashidden: {
iso8601: 'hidden_datepicker_' + idx,
}
};
if ($hidden.val() != '') datepickerOpts.val = $hidden.val();
localDatePicker(datepickerOpts);

// Disable accept_until date input if no late submissions (assessments) allowed
if (dataTool == 'assessments' && dataField == 'accept_until') {
var disabled = !updates[dataTool][dataIdx].late_handling;
$(elt).prop('disabled', disabled);
$td.find('.ui-datepicker-trigger').prop('disabled', disabled);
}
// Disable feedback start and end date inputs if feedback on date not used (assessments)
if (dataTool === 'assessments' && (dataField === 'feedback_start' || dataField === 'feedback_end')) {
var disabled = !updates[dataTool][dataIdx].feedback_by_date;
$(elt).prop('disabled', disabled);
$td.find('.ui-datepicker-trigger').prop('disabled', disabled);
}
if (dataTool == 'forums' && (dataField == 'open_date' || dataField == 'due_date')) {
var disabled = !updates[dataTool][dataIdx].restricted;
$(elt).prop('disabled', disabled);
$td.find('.ui-datepicker-trigger').prop('disabled', disabled);
}
});
DTMN.initDatePicker(updates, notModified);

function printErrors() {
$('.errors').empty();
Expand Down

0 comments on commit 4c8d013

Please sign in to comment.