Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java =================================================================== diff -u -r870519c0831ff30665916aa3ff9c54f51ea2e2fc -rc4c8b58265254ce0f4b8e9b5b7ab2070353bc302 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java (.../MonitoringAction.java) (revision 870519c0831ff30665916aa3ff9c54f51ea2e2fc) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java (.../MonitoringAction.java) (revision c4c8b58265254ce0f4b8e9b5b7ab2070353bc302) @@ -644,28 +644,30 @@ } 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); String message = null; try { - message = getMonitoringService().forceCompleteActivitiesByUser(learnerId, requesterId, lessonId, activityId, - removeLearnerContent); + for (String learnerID : learnerIDs.split(",")) { + message = getMonitoringService().forceCompleteActivitiesByUser(Integer.valueOf(learnerID), requesterId, + lessonId, activityId, removeLearnerContent); + } } 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); Index: lams_monitoring/web/css/monitorLesson.css =================================================================== diff -u -rfa2ad14fcf969120bc068f9b22e1fcebf62ce39d -rc4c8b58265254ce0f4b8e9b5b7ab2070353bc302 --- lams_monitoring/web/css/monitorLesson.css (.../monitorLesson.css) (revision fa2ad14fcf969120bc068f9b22e1fcebf62ce39d) +++ lams_monitoring/web/css/monitorLesson.css (.../monitorLesson.css) (revision c4c8b58265254ce0f4b8e9b5b7ab2070353bc302) @@ -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 -ra7d60b0e8528107dca422600d795227865462a22 -rc4c8b58265254ce0f4b8e9b5b7ab2070353bc302 --- lams_monitoring/web/includes/javascript/monitorLesson.js (.../monitorLesson.js) (revision a7d60b0e8528107dca422600d795227865462a22) +++ lams_monitoring/web/includes/javascript/monitorLesson.js (.../monitorLesson.js) (revision c4c8b58265254ce0f4b8e9b5b7ab2070353bc302) @@ -596,22 +596,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) { - // go to "force complete" mode, similar to draggin user to an activity - var activityId = $(this).dialog('option', 'ajaxProperties').data.activityID, - dropArea = sequenceCanvas.add('#completedLearnersContainer'); - dropArea.css('cursor', 'url(' - + LAMS_URL + 'images/icons/user.png),pointer') - .one('click', function(event) { - dropArea.off('click').css('cursor', 'default'); - forceComplete(activityId, selectedLearner.attr('userId'), - selectedLearner.text(), event.pageX, event.pageY); - }); - $(this).dialog('close'); - alert(LABELS.FORCE_COMPLETE_CLICK.replace('[0]', selectedLearner.text())); - } + var selectedLearners = $('.dialogList div.dialogListItemSelected', this), + // go to "force complete" mode, similar to draggin user to an activity + activityId = $(this).dialog('option', 'ajaxProperties').data.activityID, + dropArea = sequenceCanvas.add('#completedLearnersContainer'); + dropArea.css('cursor', 'url(' + + 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, learners, event.pageX, event.pageY); + }); + $(this).dialog('close'); + alert(LABELS.FORCE_COMPLETE_CLICK); } }, { @@ -725,7 +731,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); } @@ -734,7 +740,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); } @@ -931,9 +937,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 = [], @@ -983,11 +989,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) { @@ -1013,9 +1025,9 @@ if (!currentActivityId || precedingActivityId) { // 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'); @@ -1024,13 +1036,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; @@ -1040,15 +1052,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 }, @@ -1175,9 +1192,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); } }); @@ -1913,6 +1933,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, @@ -1989,14 +2010,33 @@ .appendTo(learnerGroupList); if (allowForceComplete || allowView || allowEmail) { - learnerDiv.click(function(){ - // select a learner - $(this).addClass('dialogListItemSelected') - .siblings('div.dialogListItem') - .removeClass('dialogListItemSelected'); - // enable buttons - $('button.learnerGroupDialogSelectableButton') - .attr('disabled', null); + learnerDiv.click(function(event){ + // select the learner + var learnerDiv = $(this), + selectedSiblings = learnerDiv.siblings('div.dialogListItem.dialogListItemSelected'); + // enable buttons + $('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(){ @@ -2010,14 +2050,15 @@ 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 .dialog('option', { Index: lams_monitoring/web/monitor.jsp =================================================================== diff -u -rfa2ad14fcf969120bc068f9b22e1fcebf62ce39d -rc4c8b58265254ce0f4b8e9b5b7ab2070353bc302 --- lams_monitoring/web/monitor.jsp (.../monitor.jsp) (revision fa2ad14fcf969120bc068f9b22e1fcebf62ce39d) +++ lams_monitoring/web/monitor.jsp (.../monitor.jsp) (revision c4c8b58265254ce0f4b8e9b5b7ab2070353bc302) @@ -518,6 +518,7 @@
+