Index: lams_central/.gitignore =================================================================== diff -u -ra512ff7bededcbf3ad3c4faa6b54d7a3f7bab4d8 -rdf411d8446000acfdb5937f46a7a7e8ef4b656e0 --- lams_central/.gitignore (.../.gitignore) (revision a512ff7bededcbf3ad3c4faa6b54d7a3f7bab4d8) +++ lams_central/.gitignore (.../.gitignore) (revision df411d8446000acfdb5937f46a7a7e8ef4b656e0) @@ -10,4 +10,5 @@ /web/css/qb-question.css /web/css/bootstrap-toggle.css /web/css/bootstrap5.custom.css -/web/css/free.ui.jqgrid.custom.css \ No newline at end of file +/web/css/free.ui.jqgrid.custom.css +/web/css/rating.css Index: lams_central/web/WEB-INF/tags/Rating5.tag =================================================================== diff -u -rb311244c55b221fc9f910cf4de25874d13d2296d -rdf411d8446000acfdb5937f46a7a7e8ef4b656e0 --- lams_central/web/WEB-INF/tags/Rating5.tag (.../Rating5.tag) (revision b311244c55b221fc9f910cf4de25874d13d2296d) +++ lams_central/web/WEB-INF/tags/Rating5.tag (.../Rating5.tag) (revision df411d8446000acfdb5937f46a7a7e8ef4b656e0) @@ -16,8 +16,8 @@ <%@ attribute name="itemRatingDto" required="true" rtexprvalue="true" type="org.lamsfoundation.lams.rating.dto.ItemRatingDTO" %> <%-- Optional attribute --%> -<%@ attribute name="disabled" required="false" rtexprvalue="true" %> -<%@ attribute name="isItemAuthoredByUser" required="false" rtexprvalue="true" %> +<%@ attribute name="disabled" required="false" rtexprvalue="true" %><%-- i.e. user has rating/comment rights but rating/comment should be disabled --%> +<%@ attribute name="isDisplayOnly" required="false" rtexprvalue="true" %><%-- i.e. user has no rating/comment rights --%> <%@ attribute name="maxRates" required="false" rtexprvalue="true" %> <%@ attribute name="countRatedItems" required="false" rtexprvalue="true" %> <%@ attribute name="yourRatingLabel" required="false" rtexprvalue="true" %> @@ -36,8 +36,8 @@ - - + + @@ -90,7 +90,7 @@ <%--Rating stars area---------------------------------------%>
-
+
@@ -100,31 +100,71 @@ - - - - -
- ${criteriaDto.ratingCriteria.title} -
-
- + + + + + + + ${isWidgetDisabled && (criteriaDto.averageRating%1) >= 0.5 ? '.5' : ''} + + + 0 + + + + + + + ${criteriaDto.ratingCriteria.title} + + + + - - + + ${legend} + +
+ Rated: ${dataRating} stars +
+ - +
"> + ${legend} + + + + + + + + + + + + + + + + + + + +
- -
" - tabindex="0"> -
- -
+ +
@@ -137,8 +177,8 @@ -
style="visibility: hidden;" +
style="visibility: hidden;" > @@ -167,7 +207,7 @@
- +
Index: lams_central/web/css/components.scss =================================================================== diff -u -r77134138d9765b1d5a21e047cfa51f2bf8edd81e -rdf411d8446000acfdb5937f46a7a7e8ef4b656e0 --- lams_central/web/css/components.scss (.../components.scss) (revision 77134138d9765b1d5a21e047cfa51f2bf8edd81e) +++ lams_central/web/css/components.scss (.../components.scss) (revision df411d8446000acfdb5937f46a7a7e8ef4b656e0) @@ -85,22 +85,23 @@ .jumbotron .h1,.jumbotron h1 { font-size: 63px } - } body.component { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - - @media (width <= 768px) { - font-size: 12px; - } + font-size: 12px; @media (width > 768px) { font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-size: var(--bs-body-font-size); &:not(.no-decoration) { background-color: var(--lams-background-gray); } + + legend { + font-size: var(--bs-body-font-size); + } } } Index: lams_central/web/css/rating.scss =================================================================== diff -u --- lams_central/web/css/rating.scss (revision 0) +++ lams_central/web/css/rating.scss (revision df411d8446000acfdb5937f46a7a7e8ef4b656e0) @@ -0,0 +1,72 @@ +@import 'starability/starability/grow'; + +/** ----- customization of starability library ----- **/ + +.starability-grow .starability-focus-ring { + outline: unset; + box-shadow: 0 0 0 0.2rem rgba(99, 99, 99, 0.5); + border-radius: 1.25rem; +} +.starability-grow > legend ~ .starability-focus-ring { + top: 2rem; +} +.starability-grow > input:focus + label { + outline: unset; + box-shadow: 0 0 0 0.2rem #f5bd23; + border-radius: 1.25rem; +} + +.starability-result { + $star-count-result: $star-count - 1; + + @while $star-count-result >0 { + &[data-rating="#{$star-count-result}.5"]::after { + width: $star-count-result * $star-size + $star-size / 2; + } + + $star-count-result: $star-count-result - 1; + } +} + +/** ----- classes introduced by LAMS ----- **/ + +.extra-controls-inner:after { + content: " "; + display: block; + height: 0; + clear: both; +} + +.starability-holder { + text-align: center; +} + +.starability-caption { + /**width: $star-count * $star-size;**/ + padding-top: 6px; + font-size: 12px; + text-align: center; +} + +.starability { + margin: 0px auto; + min-height: 20px; +} + +.rating-comment { + padding: 15px; + margin: 15px auto; +} + +.rating-info { + margin-top: 0; + margin-bottom: 12px; +} + +/** Remove the following once we move all tools to use Rating.tag. **/ +.rating-stars-div { + float: right; + padding-right: 10px; + margin-top: -8px; + min-height: 45px; +} Index: lams_central/web/includes/javascript/rating5.js =================================================================== diff -u --- lams_central/web/includes/javascript/rating5.js (revision 0) +++ lams_central/web/includes/javascript/rating5.js (revision df411d8446000acfdb5937f46a7a7e8ef4b656e0) @@ -0,0 +1,345 @@ + +//Please, set up LAMS_URL, COUNT_RATED_ITEMS, COMMENTS_MIN_WORDS_LIMIT, MAX_RATES and MIN_RATES, MAX_RATINGS_FOR_ITEM, +//COMMENT_TEXTAREA_TIP_LABEL, WARN_COMMENTS_IS_BLANK_LABEL, WARN_MIN_NUMBER_WORDS_LABEL, SESSION_ID constants in parent document + +//constant indicating there is rating limits set up +var HAS_RATING_LIMITS; + +// Which ones are being rated by the current user? Needed to control what is disabled when the max number of ratings is reached +// when there is a "set" of criteria for each user e.g. one or more criteria and/or a comment. +// Only needed when MAX_RATES > 0 +var idsBeingRated = []; + +$(document).ready(function(){ + HAS_RATING_LIMITS = MAX_RATES!=0 || MIN_RATES!=0; + + initializeStarability(); + + //check minimum rates limit initially + if (MIN_RATES != 0) { + checkMinimumRatesLimit(COUNT_RATED_ITEMS); + } +}); + +//initialize starability and post comment button. Note: we need the quotes around undefined for the typeof ! +function initializeStarability() { + const maxRatingsForItem = (typeof MAX_RATINGS_FOR_ITEM === "undefined" || MAX_RATINGS_FOR_ITEM === undefined) ? "" : MAX_RATINGS_FOR_ITEM; + const ratingLimitsByCriteria = (typeof LIMIT_BY_CRITERIA === "undefined" || LIMIT_BY_CRITERIA === undefined) ? false : LIMIT_BY_CRITERIA; + + // if SESSION_ID is not defined do not allow them to update ratings as the servlet will fail. + // But in monitoring we will do initializeStarability to display the ratings properly so then SESSION_ID is undefined. + const SERVLET_PATH = (typeof SESSION_ID === "undefined" || SESSION_ID === undefined) ? "" : + LAMS_URL + "servlet/rateItem?hasRatingLimits=" + HAS_RATING_LIMITS + "&ratingLimitsByCriteria=" + ratingLimitsByCriteria + + "&maxRatingsForItem=" + maxRatingsForItem + "&toolSessionId=" + SESSION_ID; + + $(".starability-new").each(function() { + const id = $(this).data('id'); // get the id of the box + + if (!$(this).hasClass('starability-disabled')) { + $("input[type=radio][name=" + id + "]").change(function() { + let element = this; + const rate = $(this).val(); + + $.post( + SERVLET_PATH, + { + idBox: id, + rate: rate + }, + function(data) { + if (!data.error) { + $("#user-rating-" + id).html(data.userRating); + $("#average-rating-" + id).html(data.averageRating); + $("#number-of-votes-" + id).html(data.numberOfVotes); + $("#starability-caption-" + id).css("visibility", "visible"); + var parts = id.split('-'); + $("#comment-tick-" + parts[1]).css("visibility", "visible"); + $("#add-comment-area-" + parts[1]).css("visibility", "visible"); + + //handle rating limits if available + handleRatingLimits(data.countRatedItems, id); + + } else { + handleError(element, rate); + } + }, + 'json' + ); + }); + } + }); + + // LDEV-4495 Add an already rated on to stash of already rated ids. Then they won't be disabled when the user has + // already rated as many as they can but they return to the screen to reeerate + if (HAS_RATING_LIMITS && MAX_RATES != 0) { + $(".starability-new").each(function() { + var average = $(this).data("average"); // unrated have average 0 to start + if ( average > 0 ) { + var newItemId = getItemIdFromObjectId($(this).data("id")); + if ( idsBeingRated.indexOf(newItemId) == -1 ) + idsBeingRated.push(newItemId) + } + }); + } + + $(".starability-new").removeClass("starability-new"); + + //addNewComment button handler + $(".add-comment-new").click(function() { + var itemId = $(this).data("item-id"); + var commentsCriteriaId = $(this).data("comment-criteria-id"); + var showAllComments = $(this).data('show-all-comments'); + var refreshOnSubmit = $(this).data('refresh-on-submit'); + + var comment = validComment("comment-textarea-" + itemId, false, false); + if ( ! comment ) + return false; + + if ( ! ( typeof SESSION_ID === "undefined" || SESSION_ID === undefined ) ) { + //add new comment + $.ajax({ + type: "POST", + url: LAMS_URL + 'servlet/rateItem', + data: { + idBox: commentsCriteriaId + '-' + itemId, + comment: comment, + hasRatingLimits: HAS_RATING_LIMITS, + ratingLimitsByCriteria: ratingLimitsByCriteria, + maxRatingsForItem: maxRatingsForItem, + showAllComments : showAllComments, + toolSessionId: SESSION_ID + }, + success: function(data, textStatus) { + if (data.error) { + handleError(); + return; + } + + if (refreshOnSubmit) { + // LDEV-5052 Refresh page and scroll to the given ID on comment submit + + // setting href does not navigate if url contains #, we still need a reload + location.hash = '#' + refreshOnSubmit; + location.reload(); + return false; + } + + + var commentsArea = $('#comments-area-' + itemId); + + if (data.allComments) { + // add all users' comments to HTML + jQuery.each(data.allComments, function(){ + jQuery('
', { + 'class': "rating-comment", + html: this + }).appendTo(commentsArea); + }); + } else { + //add this user's comment to HTML + jQuery('
', { + 'class': "rating-comment", + html: data.comment + }).appendTo(commentsArea); + } + + //hide comments textarea and button + $("#add-comment-area-" + itemId, commentsArea).hide(); + + //handle rating limits if available + if (HAS_RATING_LIMITS) { + handleRatingLimits(data.countRatedItems, itemId); + } + }, + onError : function(){ + handleError(); + } + }); + } + //apply only once + }).removeClass("add-comment-new"); +} + +function createStarability(isDisplayOnly, objectId, averageRating, numberOfVotes, userRating, isWidgetDisabled, criteriaTitle) { + let isCriteriaRatedByUser = userRating != "", + result = '', + legend = ''; + + if (criteriaTitle) { + legend = '' + + criteriaTitle + + ''; + } + + let dataRating; + if (isDisplayOnly || isCriteriaRatedByUser) { + dataRating = Math.floor(averageRating); + //half-round when widget is disabled + if (isWidgetDisabled && (averageRating % 1 >= 0.5)) { + dataRating += 0.5; + } + + } else { + dataRating = 0; + } + + if (isWidgetDisabled) { + result += + legend + + '
' + + 'Rated: ' + dataRating + ' stars' + + '
'; + + } else { + result += + '
' + + legend + + + '' + + + '' + + '' + + + '' + + '' + + + '' + + '' + + + '' + + '' + + + '' + + '' + + + '' + + '
'; + } + + if (isDisplayOnly) { + result += '
' + + AVG_RATING_LABEL.replace("@1@", averageRating).replace("@2@", numberOfVotes) + + '
'; + + } else { + result += '
' + userRating + ''); + temp = temp.replace("@2@", '' + averageRating + ''); + temp = temp.replace("@3@", '' + numberOfVotes + ''); + result += temp; + result += '
'; + } + + return result; +} + +// allowBlankComment is needed for Peer Review, where rating related comments are always checked even if minWords = 0 +// skipMinWordCheckOnBlank is used for the explicit comment type fields may be blank even when minWord > 0 +function validComment(textAreaId, allowBlankComment, skipMinWordCheckOnBlank) { + //replace special characters with HTML tags + var tempTextarea = jQuery('