Index: lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/web/controller/GradebookMonitoringController.java =================================================================== diff -u -ra9c5e4550ac78c7dde3dc3796d6cf8dc03d455e8 -ra8e04b385dbb553c581bd705426a86ab92e85f9a --- lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/web/controller/GradebookMonitoringController.java (.../GradebookMonitoringController.java) (revision a9c5e4550ac78c7dde3dc3796d6cf8dc03d455e8) +++ lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/web/controller/GradebookMonitoringController.java (.../GradebookMonitoringController.java) (revision a8e04b385dbb553c581bd705426a86ab92e85f9a) @@ -26,14 +26,17 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.lamsfoundation.lams.events.IEventNotificationService; import org.lamsfoundation.lams.gradebook.dto.GradebookGridRowDTO; @@ -66,6 +69,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -251,8 +256,7 @@ */ @RequestMapping(path = "/toggleReleaseMarks", method = RequestMethod.POST) @ResponseBody - public String toggleReleaseMarks(HttpServletRequest request, HttpServletResponse response) throws IOException { - Long lessonID = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); + public String toggleReleaseMarks(@RequestParam long lessonID, HttpServletResponse response) throws IOException { if (!securityService.isLessonMonitor(lessonID, getUser().getUserID(), "toggle release marks", false)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); } @@ -263,7 +267,7 @@ } @RequestMapping("/displayReleaseMarksPanel") - public String displayReleaseMarksPanel(@RequestParam long lessonID) { + public String displayReleaseMarksPanel() { return "releaseLessonMarks"; } @@ -316,17 +320,50 @@ @RequestMapping("/sendReleaseMarksEmails") @ResponseBody - public String sendReleaseMarksEmails(@RequestParam long lessonID) { + public String sendReleaseMarksEmails(@RequestParam long lessonID, + @RequestParam(name = "includedLearners", required = false) String includedLearnersString, + @RequestParam(name = "excludedLearners", required = false) String excludedLearnersString) + throws JsonProcessingException, IOException { + ArrayNode includedLearners = StringUtils.isBlank(includedLearnersString) ? null + : JsonUtil.readArray(includedLearnersString); + ArrayNode excludedLearners = StringUtils.isBlank(excludedLearnersString) ? null + : JsonUtil.readArray(excludedLearnersString); + if (includedLearners == null && excludedLearners == null) { + throw new IllegalArgumentException( + "Neither included nor excluded learners found when sending an email with marks."); + } + try { Lesson lesson = lessonService.getLesson(lessonID); - List learners = lessonService.getActiveLessonLearners(lessonID); - String emailSubject = new StringBuilder("Results of lesson \"").append(lesson.getLessonName()).append("\"") + Set recipients = new HashSet<>(); + if (excludedLearners == null) { + // we send emails only to selected learners + for (int learnerIndex = 0; learnerIndex < includedLearners.size(); learnerIndex++) { + recipients.add(includedLearners.get(learnerIndex).asInt()); + } + } else { + List learners = lessonService.getActiveLessonLearners(lessonID); + // we send emails to all lesson learners, excluding one who got deselected + for (User learner : learners) { + boolean excludedLearnerFound = false; + for (int learnerIndex = 0; learnerIndex < excludedLearners.size(); learnerIndex++) { + if (learner.getUserId().equals(excludedLearners.get(learnerIndex).asInt())) { + excludedLearnerFound = true; + break; + } + } + if (!excludedLearnerFound) { + recipients.add(learner.getUserId()); + } + } + } + + String emailSubject = new StringBuilder("Results of LAMS lesson \"").append(lesson.getLessonName()).append("\"") .toString(); - for (User learner : learners) { - eventNotificationService.sendMessage(null, learner.getUserId(), - IEventNotificationService.DELIVERY_METHOD_MAIL, emailSubject, - getReleaseMarksEmailContent(lessonID, learner.getUserId()), true); + for (Integer recipientId : recipients) { + eventNotificationService.sendMessage(null, recipientId, IEventNotificationService.DELIVERY_METHOD_MAIL, + emailSubject, getReleaseMarksEmailContent(lessonID, recipientId), true); } } catch (Exception e) { return e.getMessage(); Index: lams_gradebook/web/gradebookMonitor.jsp =================================================================== diff -u -r67eb08b1f83112e23be227fbf6923a46d0dc95ba -ra8e04b385dbb553c581bd705426a86ab92e85f9a --- lams_gradebook/web/gradebookMonitor.jsp (.../gradebookMonitor.jsp) (revision 67eb08b1f83112e23be227fbf6923a46d0dc95ba) +++ lams_gradebook/web/gradebookMonitor.jsp (.../gradebookMonitor.jsp) (revision a8e04b385dbb553c581bd705426a86ab92e85f9a) @@ -93,7 +93,7 @@ jQuery(document).ready(function(){ var jqgridWidth = $(window).width() - 100; - // Create the user view grid with sub grid for activities + // Create the user view grid with sub grid for activities jQuery("#userView").jqGrid({ guiStyle: "bootstrap", iconSet: 'fontAwesome', Index: lams_gradebook/web/releaseLessonMarks.jsp =================================================================== diff -u -r67eb08b1f83112e23be227fbf6923a46d0dc95ba -ra8e04b385dbb553c581bd705426a86ab92e85f9a --- lams_gradebook/web/releaseLessonMarks.jsp (.../releaseLessonMarks.jsp) (revision 67eb08b1f83112e23be227fbf6923a46d0dc95ba) +++ lams_gradebook/web/releaseLessonMarks.jsp (.../releaseLessonMarks.jsp) (revision a8e04b385dbb553c581bd705426a86ab92e85f9a) @@ -47,7 +47,7 @@ {name:'name',index:'name', sortable: false, sorttype: 'text'} ], rowList:[10,20,30,40,50,100], - rowNum:10, + rowNum:1, pager: true, sortname: 'name', multiselect: true, @@ -79,8 +79,94 @@ }); } return false; - } - }); + }, + onSelectRow : function(id, status, event) { + var grid = $(this), + included = grid.data('included'), + excluded = grid.data('excluded'), + selectAllChecked = grid.closest('.ui-jqgrid-view').find('.jqgh_cbox .cbox').prop('checked'); + if (selectAllChecked) { + var index = excluded.indexOf(+id); + // if row is deselected, add it to excluded array + if (index < 0) { + if (!status) { + excluded.push(+id); + } + } else if (status) { + excluded.splice(index, 1); + } + } else { + var index = included.indexOf(+id); + // if row is selected, add it to included array + if (index < 0) { + if (status) { + included.push(+id); + } + } else if (!status) { + included.splice(index, 1); + } + } + }, + gridComplete : function(){ + let grid = $(this), + included = grid.data('included'), + // cell containing "(de)select all" button + selectAllCell = grid.closest('.ui-jqgrid-view').find('.jqgh_cbox > div'); + // remove the default button provided by jqGrid + $('.cbox', selectAllCell).remove(); + // create own button which follows own rules + var selectAllCheckbox = $('') + .prop('checked', included === null) + .prependTo(selectAllCell) + .change(function(){ + // start with deselecting everyone on current page + grid.resetSelection(); + if ($(this).prop('checked')){ + // on select all change mode and select all on current page + grid.data('included', null); + grid.data('excluded', []); + $('[role="row"]', grid).each(function(){ + grid.jqGrid('setSelection', +$(this).attr('id'), false); + }); + } else { + // on deselect all just change mode + grid.data('excluded', null); + grid.data('included', []); + } + }); + + grid.resetSelection(); + if (selectAllCheckbox.prop('checked')) { + var excluded = grid.data('excluded'); + // go through each loaded row + $('[role="row"]', grid).each(function(){ + var id = +$(this).attr('id'), + selected = $(this).hasClass('success'); + // if row is not selected and is not excluded, select it + if (!selected && (!excluded || !excluded.includes(id))) { + // select without triggering onSelectRow + grid.jqGrid('setSelection', id, false); + } + }); + } else { + // go through each loaded row + $('[role="row"]', grid).each(function(){ + var id = +$(this).attr('id'), + selected = $(this).hasClass('success'); + // if row is not selected and is included, select it + if (!selected && included.includes(id)) { + // select without triggering onSelectRow + grid.jqGrid('setSelection', id, false); + } + }); + } + + // empty email preview on grid page change + $('#release-marks-email-preview').slideUp(function(){ + $('#release-marks-email-preview-content', this).empty(); + }); + }}).data({'included' : null, + 'excluded' : []}); }); function toggleMarksRelease() { @@ -116,10 +202,16 @@ } function sendReleaseMarksEmails(){ + let grid = $("#release-marks-learner-list"), + includedLearners = grid.data('included'), + excludedLearners = grid.data('excluded'); + $.ajax({ 'url' : 'gradebook/gradebookMonitoring/sendReleaseMarksEmails.do', 'data' : { - 'lessonID' : releaseMarksLessonID + 'lessonID' : releaseMarksLessonID, + 'includedLearners' : includedLearners === null ? null : JSON.stringify(includedLearners), + 'excludedLearners' : excludedLearners === null ? null : JSON.stringify(excludedLearners) }, 'dataType' : 'text', 'cache' : false,