diff --git a/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java b/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java index 3ab4b93..0d9efdb 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/CrosslistConstants.java @@ -54,6 +54,7 @@ public interface CrosslistConstants { String LOOKUP_FAILURE_COURSE_NOT_CROSSLISTED_MESSAGE = "This course has not been crosslisted"; String LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE = "Not found in Canvas"; String LOOKUP_FAILURE_NOT_FOUND_IN_SIS_MESSAGE = "Not found in SIS"; + String LOOKUP_FAILURE_MISSING_SIS_ID = "SIS Section ID is required."; String LOOKUP_SUCCESS_CSS = "rvt-color-green rvt-bg-green-100"; String LOOKUP_FAILURE_CSS = "rvt-orange-green rvt-bg-orange-100"; diff --git a/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java b/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java index 64698d8..4259cf9 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java @@ -60,6 +60,7 @@ import uk.ac.ox.ctl.lti13.security.oauth2.client.lti.authentication.OidcAuthenticationToken; import javax.servlet.http.HttpSession; +import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -102,10 +103,33 @@ public String lookupSearchBySisId(@ModelAttribute FindParentModel findParentMode String sisIdSearch = findParentModel.getSisIdSearch().trim(); findParentResult = crosslistService.processSisLookup(sisIdSearch); + List
unavailableToDecrosslistSectionsList = new ArrayList<>(); + List
availableToDecrosslistSectionsList = new ArrayList<>(); + if (findParentResult != null) { + List
fullSectionList = findParentResult.getSectionList(); + + if (fullSectionList != null) { + for (Section section : fullSectionList) { + if (section.getSis_section_id() == null || section.getNonxlist_course_id() == null) { + unavailableToDecrosslistSectionsList.add(section); + } else { + availableToDecrosslistSectionsList.add(section); + } + } + } + + model.addAttribute("unavailableToDecrosslistSectionsList", unavailableToDecrosslistSectionsList); + model.addAttribute("availableToDecrosslistSectionsList", availableToDecrosslistSectionsList); + model.addAttribute("findParentResult", findParentResult); // add canvasCourseId to be used in audit log purposes later model.addAttribute("canvasCourseId", findParentResult.getCanvasCourseId()); + + // If we aren't showing the course info, there must be an error + if (!findParentResult.isShowCourseInfo()){ + model.addAttribute("errorMsg", findParentResult.getStatusMessage()); + } } return "findParentCourse"; @@ -181,6 +205,23 @@ public String decrossListSections(@RequestParam("section-checkboxes") List fullSectionList = findParentResult.getSectionList(); + List
unavailableToDecrosslistSectionsList = new ArrayList<>(); + List
availableToDecrosslistSectionsList = new ArrayList<>(); + + if (fullSectionList != null) { + for (Section section : fullSectionList) { + if (section.getSis_section_id() == null || section.getNonxlist_course_id() == null) { + unavailableToDecrosslistSectionsList.add(section); + } else { + availableToDecrosslistSectionsList.add(section); + } + } + } + + model.addAttribute("unavailableToDecrosslistSectionsList", unavailableToDecrosslistSectionsList); + model.addAttribute("availableToDecrosslistSectionsList", availableToDecrosslistSectionsList); } return "findParentCourse"; diff --git a/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java b/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java index 5ae842e..ee14bb6 100644 --- a/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java +++ b/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java @@ -395,9 +395,7 @@ public FindParentResult processSisLookup(String sisSectionId) { if (sisSectionId == null || sisSectionId.isEmpty()) { findParentResult.setShowCourseInfo(false); - findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_SIS_MESSAGE); - findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); - findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); + findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_MISSING_SIS_ID); return findParentResult; } @@ -406,8 +404,6 @@ public FindParentResult processSisLookup(String sisSectionId) { if (section == null) { findParentResult.setShowCourseInfo(false); findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE); - findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); - findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); return findParentResult; } @@ -416,8 +412,6 @@ public FindParentResult processSisLookup(String sisSectionId) { if (course == null) { findParentResult.setShowCourseInfo(false); findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE); - findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); - findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); return findParentResult; } @@ -429,8 +423,6 @@ public FindParentResult processSisLookup(String sisSectionId) { if (sectionsList == null) { findParentResult.setShowCourseInfo(false); findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE); - findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS); - findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME); return findParentResult; } diff --git a/src/main/resources/static/css/crosslisting.css b/src/main/resources/static/css/crosslisting.css index 09cc97d..9ccdf74 100644 --- a/src/main/resources/static/css/crosslisting.css +++ b/src/main/resources/static/css/crosslisting.css @@ -102,4 +102,8 @@ .checkboxColumn { width: 1%; line-height: inherit; +} + +#results-message:focus { + outline: none; } \ No newline at end of file diff --git a/src/main/resources/static/js/decrosslisting.js b/src/main/resources/static/js/decrosslisting.js new file mode 100644 index 0000000..ec95dd4 --- /dev/null +++ b/src/main/resources/static/js/decrosslisting.js @@ -0,0 +1,134 @@ +/*- + * #%L + * lms-lti-crosslist + * %% + * Copyright (C) 2015 - 2022 Indiana University + * %% + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the Indiana University nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +jQuery(document).ready(function() { + let resultsMessage = document.getElementById('results-message'); + + if (resultsMessage != null) { + resultsMessage.focus(); + } + + $("#select-all-button").click(function() { + $('input:checkbox').prop('checked', true); + updateCounter(); + }); + + $("#deselect-all-button").click(function() { + $('input:checkbox').prop('checked', false); + updateCounter(); + }); + + checkboxCounter(); + + // This will move focus to the invalid search if it fails the server-side validation + $('#sisid-search[aria-invalid="true"]').focus(); +}); + +function checkboxCounter() { + // Get all the selected checkboxes, except the "select-all" one up in the table header + let $checkboxes = $('#decrosslist-form input[type="checkbox"]'); + + // update the count + $checkboxes.change(function() { + let countCheckedCheckboxes = $checkboxes.filter(':checked').length; + $('#sections-selected').text(countCheckedCheckboxes + ' selected'); + }); +} + +function updateCounter() { + let $checkboxes = $('#decrosslist-form input[type="checkbox"]'); + let countCheckedCheckboxes = $checkboxes.filter(':checked').length; + $('#sections-selected').text(countCheckedCheckboxes + ' selected'); +} + +function submitSisIdForm(button) { + let searchInput = document.getElementById('sisid-search'); + if (searchInput.value.trim() === '') { + let validationText = document.getElementById('sisid-error-text'); + validationText.innerText = 'SIS Section ID is required.'; + + let validationMessage = document.getElementById('sisid-error-message'); + validationMessage.classList.remove('rvt-display-none'); + + searchInput.setAttribute('aria-invalid', true); + searchInput.setAttribute('aria-describedby', 'sisid-error-text sisid-search-description'); + searchInput.focus(); + + return false; + } else { + buttonLoading(button); + document.getElementById('sisIdForm').submit(); + } +} + +function validateCheckboxForm(button) { + // Get all the selected checkboxes, except the "select-all" one up in the table header + let checkboxes = $('#decrosslist-form input[type="checkbox"]').not("#checkbox-select-all").filter(':checked').length; + + if (checkboxes > 0) { + buttonLoading(button); + document.getElementById('decrosslist-form').submit(); + } else { + let checkboxValidationMessage = document.getElementById('checkbox-validation-message'); + checkboxValidationMessage.classList.remove('rvt-display-none'); + + let firstCheckbox = $('#decrosslist-form input[type="checkbox"]').not("#checkbox-select-all").first(); + firstCheckbox.attr('aria-invalid', true); + firstCheckbox.attr('aria-describedby', 'checkbox-validation-message'); + firstCheckbox.focus(); + + return false; + } +} + +function buttonLoading(button) { + if (button.dataset.action != null) { + document.getElementById("submitValue").value = button.dataset.action; + } + button.setAttribute("aria-busy", true); + let buttonsToDisable = document.getElementsByTagName('button'); + for(let i = 0; i < buttonsToDisable.length; i++) { + buttonsToDisable[i].disabled = true; + } + button.classList.add("rvt-button--loading"); + + let spinners = button.getElementsByClassName('rvt-loader'); + if (spinners && spinners.length > 0) { + spinners[0].classList.remove("rvt-display-none"); + } + + let srText = button.getElementsByClassName('sr-loader-text'); + if (srText && srText.length > 0) { + srText[0].classList.remove("rvt-display-none"); + } +} \ No newline at end of file diff --git a/src/main/resources/templates/decrosslistlayout.html b/src/main/resources/templates/decrosslistlayout.html index 66245a5..05dafb3 100644 --- a/src/main/resources/templates/decrosslistlayout.html +++ b/src/main/resources/templates/decrosslistlayout.html @@ -58,6 +58,7 @@ +