Index: lams_monitoring/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -rd19a95db673b7cf90351de4d567299c5fa86b450 -rf4621cf27d17389c7437daf00f3c2b833290ca5a --- lams_monitoring/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision d19a95db673b7cf90351de4d567299c5fa86b450) +++ lams_monitoring/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision f4621cf27d17389c7437daf00f3c2b833290ca5a) @@ -265,9 +265,9 @@ tab.lesson =Lesson tab.sequence =Sequence tab.learners =Learners -force.complete.click =Click on an activity to move learner "[0]" or outside any activity to cancel. -force.complete.end.lesson.confirm =Are you sure you want to move learner "[0]" to the end of lesson? -force.complete.activity.confirm =Are you sure you want to move learner "[0]" to activity "[1]"? +force.complete.click =Click on an activity to move learners or outside any activity to cancel. +force.complete.end.lesson.confirm =Are you sure you want to move [0] to the end of lesson? +force.complete.activity.confirm =Are you sure you want to move [0] to activity "[1]"? force.complete.drop.fail =You have dropped the learner "[0]" on either its current or on its completed activity "[1]". force.complete.end.lesson.tooltip =To move a learner to the end of lesson, drag the learner icon over to this bar. force.complete.learner.command.message =Teacher moved you to another activity. @@ -277,6 +277,7 @@ learner.finished.count =Finished Learners: [0] of [1] learner.finished.dialog.title =End of lesson learner.group.sort.button =Sort +learner.group.multi.select =(ctrl+click to select multiple learners) button.force.complete =Move learner to... button.view.learner =View learner button.close =Close @@ -292,12 +293,12 @@ learner.group.select.all =Select/Unselect all email.notifications.problems.sending.emails =Some problems occurred while sending emails. Please, contact your system administrator. learner.group.remove.progress =You are about to remove student(s) from a lesson. The student(s) will not have access to this lesson any longer. Do you also want to remove the student(s) progress? -force.complete.remove.content =You are moving learner "[0]" to activity "[1]". You can opt to delete the content of activities that have been previously done, so the learner has to enter the content/answers again. +force.complete.remove.content =You are moving [0] to activity "[1]". You can opt to delete the content of activities that have been previously done, so the learner has to enter the content/answers again. force.complete.remove.content.yes =Delete content force.complete.remove.content.no =Keep content label.schedule.gate.activity.completion.based =This gate is based on each learner's previous activity completion time -audit.force.complete =Attempted to force complete learner {0} to activityId:{1}. LessonId:{2}. -audit.force.complete.end.lesson =Attempted to force complete learner {0} to the end of the lesson. LessonId:{2}. +audit.force.complete =Attempted to force complete learners {0} to activityID: {1}. LessonID: {2}. +audit.force.complete.end.lesson =Attempted to force complete learners {0} to the end of the lesson. LessonID: {2}. lesson.task.content.edited =Content being edited lesson.task.attention =This activity requires attention lesson.task.define.later =Define Later Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java =================================================================== diff -u -rf3d98b4271cdc33a7096468adca32cf0213ee42d -rf4621cf27d17389c7437daf00f3c2b833290ca5a --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java (.../MonitoringAction.java) (revision f3d98b4271cdc33a7096468adca32cf0213ee42d) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java (.../MonitoringAction.java) (revision f4621cf27d17389c7437daf00f3c2b833290ca5a) @@ -616,37 +616,43 @@ } long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID); - Integer learnerId = new Integer(WebUtil.readIntParam(request, MonitoringConstants.PARAM_LEARNER_ID)); + String learnerIDs = request.getParameter(MonitoringConstants.PARAM_LEARNER_ID); Integer requesterId = getUserId(); boolean removeLearnerContent = WebUtil.readBooleanParam(request, MonitoringConstants.PARAM_REMOVE_LEARNER_CONTENT, false); + JSONObject jsonCommand = new JSONObject(); + jsonCommand.put("message", getMessageService().getMessage("force.complete.learner.command.message")); + jsonCommand.put("redirectURL", "/lams/learning/learner.do?method=joinLesson&lessonID=" + lessonId); + String command = jsonCommand.toString(); + String message = null; + User learner = null; try { - message = getMonitoringService().forceCompleteActivitiesByUser(learnerId, requesterId, lessonId, activityId, - removeLearnerContent); + for (String learnerIDString : learnerIDs.split(",")) { + Integer learnerID = Integer.valueOf(learnerIDString); + message = getMonitoringService().forceCompleteActivitiesByUser(learnerID, requesterId, lessonId, + activityId, removeLearnerContent); + + learner = (User) getUserManagementService().findById(User.class, learnerID); + getLearnerService().createCommandForLearner(lessonId, learner.getLogin(), command); + } } catch (SecurityException e) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a monitor in the lesson"); return null; } if (LamsDispatchAction.log.isDebugEnabled()) { LamsDispatchAction.log - .debug("Force complete for learner " + learnerId + " lesson " + lessonId + ". " + message); + .debug("Force complete for learners " + learnerIDs + " lesson " + lessonId + ". " + message); } // audit log force completion attempt String messageKey = (activityId == null) ? "audit.force.complete.end.lesson" : "audit.force.complete"; - Object[] args = new Object[] { learnerId, activityId, lessonId }; + Object[] args = new Object[] { learnerIDs, activityId, lessonId }; String auditMessage = getMonitoringService().getMessageService().getMessage(messageKey, args); MonitoringAction.auditService.log(MonitoringConstants.MONITORING_MODULE_NAME, auditMessage + " " + message); - JSONObject jsonCommand = new JSONObject(); - jsonCommand.put("message", getMessageService().getMessage("force.complete.learner.command.message")); - jsonCommand.put("redirectURL", "/lams/learning/learner.do?method=joinLesson&lessonID=" + lessonId); - User learner = (User) getUserManagementService().findById(User.class, learnerId); - getLearnerService().createCommandForLearner(lessonId, learner.getLogin(), jsonCommand.toString()); - PrintWriter writer = response.getWriter(); writer.println(message); return null; Index: lams_monitoring/web/css/monitorLesson.css =================================================================== diff -u -r9aabce20ab39a7639b3ab35a2a9e4dc20cbf4ac3 -rf4621cf27d17389c7437daf00f3c2b833290ca5a --- lams_monitoring/web/css/monitorLesson.css (.../monitorLesson.css) (revision 9aabce20ab39a7639b3ab35a2a9e4dc20cbf4ac3) +++ lams_monitoring/web/css/monitorLesson.css (.../monitorLesson.css) (revision f4621cf27d17389c7437daf00f3c2b833290ca5a) @@ -105,6 +105,9 @@ width: 100px; } +span#learnerGroupMultiSelectLabel { + font-size: 10px; +} /********** LESSON TAB STYLES **********/ div#tabLesson { /* height: 540px;*/ Index: lams_monitoring/web/includes/javascript/monitorLesson.js =================================================================== diff -u -r0ad005fccfb4565f26a51ccd1bb1c24d0666aa6e -rf4621cf27d17389c7437daf00f3c2b833290ca5a --- lams_monitoring/web/includes/javascript/monitorLesson.js (.../monitorLesson.js) (revision 0ad005fccfb4565f26a51ccd1bb1c24d0666aa6e) +++ lams_monitoring/web/includes/javascript/monitorLesson.js (.../monitorLesson.js) (revision f4621cf27d17389c7437daf00f3c2b833290ca5a) @@ -571,22 +571,28 @@ 'id' : 'learnerGroupDialogForceCompleteButton', 'class' : 'learnerGroupDialogSelectableButton', 'click' : function() { - var selectedLearner = $('.dialogList div.dialogListItemSelected', this); - // make sure there is only one selected learner - if (selectedLearner.length == 1) { + var selectedLearners = $('.dialogList div.dialogListItemSelected', this), // go to "force complete" mode, similar to draggin user to an activity - var activityId = $(this).dialog('option', 'ajaxProperties').data.activityID, + activityId = $(this).dialog('option', 'ajaxProperties').data.activityID, dropArea = sequenceCanvas.add('#completedLearnersContainer'); dropArea.css('cursor', 'url(' - + LAMS_URL + 'images/icons/user.png),pointer') + + LAMS_URL + 'images/icons/' + + (selectedLearners.length > 1 ? 'group' : 'user') + + '.png),pointer') .one('click', function(event) { + var learners = []; + selectedLearners.each(function(){ + var learner = $(this); + learners.push({ + 'id' : learner.attr('userId'), + 'name' : learner.text() + }); + }); dropArea.off('click').css('cursor', 'default'); - forceComplete(activityId, selectedLearner.attr('userId'), - selectedLearner.text(), event.pageX, event.pageY); + forceComplete(activityId, learners, event.pageX, event.pageY); }); $(this).dialog('close'); - alert(LABELS.FORCE_COMPLETE_CLICK.replace('[0]', selectedLearner.text())); - } + alert(LABELS.FORCE_COMPLETE_CLICK); } }, { @@ -700,7 +706,7 @@ 'text' : LABELS.FORCE_COMPLETE_REMOVE_CONTENT_NO, 'click' : function() { $(this).dialog('close'); - forceCompleteExecute($(this).dialog('option', 'learnerId'), + forceCompleteExecute($(this).dialog('option', 'learners'), $(this).dialog('option', 'activityId'), false); } @@ -709,7 +715,7 @@ 'text' : LABELS.FORCE_COMPLETE_REMOVE_CONTENT_YES, 'click' : function() { $(this).dialog('close'); - forceCompleteExecute($(this).dialog('option', 'learnerId'), + forceCompleteExecute($(this).dialog('option', 'learners'), $(this).dialog('option', 'activityId'), true); } @@ -913,9 +919,9 @@ /** - * Forces given learner to move to activity indicated on SVG by coordinated (drag-drop) + * Forces given learners to move to activity indicated on SVG by coordinated (drag-drop) */ -function forceComplete(currentActivityId, learnerId, learnerName, x, y) { +function forceComplete(currentActivityId, learners, x, y) { autoRefreshBlocked = true; var foundActivities = [], @@ -964,11 +970,17 @@ var targetActivityId = null, executeForceComplete = false, - isEndLesson = !targetActivity.is('g'); + isEndLesson = !targetActivity.is('g'), + learnerNames = ''; + $.each(learners, function(){ + learnerNames += this.name + ', '; + }); + learnerNames = learnerNames.slice(0, -2); + if (isEndLesson) { executeForceComplete = currentActivityId && confirm(LABELS.FORCE_COMPLETE_END_LESSON_CONFIRM - .replace('[0]',learnerName)); + .replace('[0]',learnerNames)); } else { var targetActivityId = +targetActivity.attr('id'); if (currentActivityId != targetActivityId) { @@ -997,9 +1009,9 @@ if (moveBackwards) { // move the learner backwards $('#forceBackwardsDialog').text(LABELS.FORCE_COMPLETE_REMOVE_CONTENT - .replace('[0]', learnerName).replace('[1]', targetActivityName)) + .replace('[0]', learnerNames).replace('[1]', targetActivityName)) .dialog('option', { - 'learnerId' : learnerId, + 'learners' : learners, 'activityId': targetActivityId }) .dialog('open'); @@ -1008,13 +1020,13 @@ } else { // move the learner forward executeForceComplete = confirm(LABELS.FORCE_COMPLETE_ACTIVITY_CONFIRM - .replace('[0]', learnerName).replace('[1]', targetActivityName)); + .replace('[0]', learnerNames).replace('[1]', targetActivityName)); } } } if (executeForceComplete) { - forceCompleteExecute(learnerId, targetActivityId, false); + forceCompleteExecute(learners, targetActivityId, false); } autoRefreshBlocked = false; @@ -1024,15 +1036,20 @@ /** * Tell server to force complete the learner. */ -function forceCompleteExecute(learnerId, activityId, removeContent) { +function forceCompleteExecute(learners, activityId, removeContent) { + var learnerIds = ''; + $.each(learners, function() { + learnerIds += this.id + ','; + }) + $.ajax({ dataType : 'text', url : LAMS_URL + 'monitoring/monitoring.do', cache : false, data : { 'method' : 'forceComplete', 'lessonID' : lessonId, - 'learnerID' : learnerId, + 'learnerID' : learnerIds.slice(0, -1), 'activityID' : activityId, 'removeContent' : removeContent }, @@ -1160,9 +1177,12 @@ autoRefreshBlocked = true; }, 'stop' : function(event, ui) { + var learners = [{ + 'id' : learner.id, + 'name' : getLearnerDisplayName(learner, true) + }]; // jQuery droppable does not work for SVG, so this is a workaround - forceComplete(activity.id, learner.id, getLearnerDisplayName(learner, true), - ui.offset.left, ui.offset.top); + forceComplete(activity.id, learners, ui.offset.left, ui.offset.top); } }); @@ -1846,6 +1866,7 @@ */ function showLearnerGroupDialog(ajaxProperties, dialogTitle, allowSearch, allowForceComplete, allowView, allowEmail) { var learnerGroupDialog = $('#learnerGroupDialog'), + learnerGroupDialogParent = learnerGroupDialog.parent(), learnerGroupList = $('.dialogList', learnerGroupDialog).empty(), // no parameters provided? just work on what we saved isRefresh = ajaxProperties == null, @@ -1922,14 +1943,33 @@ .appendTo(learnerGroupList); if (allowForceComplete || allowView || allowEmail) { - learnerDiv.click(function(){ - // select a learner - $(this).addClass('dialogListItemSelected') - .siblings('div.dialogListItem') - .removeClass('dialogListItemSelected'); + learnerDiv.click(function(event){ + // select the learner + var learnerDiv = $(this), + selectedSiblings = learnerDiv.siblings('div.dialogListItem.dialogListItemSelected'); // enable buttons - $('button.learnerGroupDialogSelectableButton') - .attr('disabled', null); + $('button.learnerGroupDialogSelectableButton', learnerGroupDialogParent).prop('disabled', false) + .removeClass('ui-state-disabled'); + if (allowForceComplete && event.ctrlKey) { + var isSelected = learnerDiv.hasClass('dialogListItemSelected'); + if (isSelected) { + // do not un-select last learner + if (selectedSiblings.length > 0) { + learnerDiv.removeClass('dialogListItemSelected') + } + } else { + learnerDiv.addClass('dialogListItemSelected'); + } + if (selectedSiblings.length + (isSelected ? 0 : 1) > 1) { + // disable view button - only one learner can be viewed and multiple are selected + $('button#learnerGroupDialogViewButton', learnerGroupDialogParent).prop('disabled', true) + .addClass('ui-state-disabled'); + } + } else { + learnerDiv.addClass('dialogListItemSelected'); + // un-select other learners + selectedSiblings.removeClass('dialogListItemSelected'); + } }); if (allowView){ dblTap(learnerDiv, function(){ @@ -1943,12 +1983,13 @@ colorDialogList(learnerGroupDialog); if (!isRefresh) { - // show buttons depending on parameters - $('button#learnerGroupDialogForceCompleteButton') + + // show buttons and labels depending on parameters + $('span#learnerGroupMultiSelectLabel, button#learnerGroupDialogForceCompleteButton', learnerGroupDialogParent) .css('display', allowForceComplete ? 'inline' : 'none'); - $('button#learnerGroupDialogViewButton') + $('button#learnerGroupDialogViewButton', learnerGroupDialogParent) .css('display', allowView ? 'inline' : 'none'); - $('button#learnerGroupDialogEmailButton') + $('button#learnerGroupDialogEmailButton', learnerGroupDialogParent) .css('display', allowEmail ? 'inline' : 'none'); learnerGroupDialog Index: lams_monitoring/web/monitor.jsp =================================================================== diff -u -rf295ad79de8fe9d221c166fa90587d0be2fdda5b -rf4621cf27d17389c7437daf00f3c2b833290ca5a --- lams_monitoring/web/monitor.jsp (.../monitor.jsp) (revision f295ad79de8fe9d221c166fa90587d0be2fdda5b) +++ lams_monitoring/web/monitor.jsp (.../monitor.jsp) (revision f4621cf27d17389c7437daf00f3c2b833290ca5a) @@ -490,6 +490,7 @@
+