Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -r6a91a91a93da80d0aa0dc5dcce8fda6cb2f92df8 -r42cd8d7da9fa6615dbd82d376984000024b4b0fe Binary files differ Index: lams_central/src/java/org/lamsfoundation/lams/web/RatingServlet.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_central/src/java/org/lamsfoundation/lams/web/RatingServlet.java (.../RatingServlet.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_central/src/java/org/lamsfoundation/lams/web/RatingServlet.java (.../RatingServlet.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -81,6 +81,7 @@ // get rating value as either float or comment String try { boolean doSave = true; + boolean ratingLimitsByCriteria = WebUtil.readBooleanParam(request, "ratingLimitsByCriteria", false); Long maxRatingsForItem = WebUtil.readLongParam(request, "maxRatingsForItem", true); log.debug("RatingServlet: Check Max rates for an item reached. Item " + itemId + " criteria id " @@ -93,8 +94,10 @@ ToolActivityRatingCriteria toolCriteria = (ToolActivityRatingCriteria) criteria; List itemIds = new LinkedList(); itemIds.add(itemId); - Map itemIdToRatedUsersCountMap = ratingService - .countUsersRatedEachItem(toolCriteria.getToolContentId(), itemIds, userId.intValue()); + Map itemIdToRatedUsersCountMap = ratingLimitsByCriteria ? + ratingService.countUsersRatedEachItemByCriteria(ratingCriteriaId, itemIds, userId) : + ratingService.countUsersRatedEachItem(toolCriteria.getToolContentId(), itemIds, userId); + Long currentRatings = itemIdToRatedUsersCountMap.get(itemId); if (currentRatings != null && maxRatingsForItem.compareTo(currentRatings) <= 0) { JSONObject.put("error", true); @@ -109,11 +112,16 @@ if (doSave) { if (criteria.isCommentsEnabled()) { - String comment = WebUtil.readStrParam(request, "comment"); - ratingService.commentItem(criteria, userId, itemId, comment); - JSONObject.put("comment", StringEscapeUtils.escapeCsv(comment)); - - } else { + // can have but do not have to have comment + String comment = WebUtil.readStrParam(request, "comment", true); + if ( comment != null ) { + ratingService.commentItem(criteria, userId, itemId, comment); + JSONObject.put("comment", StringEscapeUtils.escapeCsv(comment)); + } + } + + String floatString = request.getParameter("rate"); + if ( floatString != null && floatString.length() > 0 ) { float rating = Float.parseFloat(request.getParameter("rate")); ItemRatingCriteriaDTO averageRatingDTO = ratingService.rateItem(criteria, userId, itemId, rating); @@ -128,12 +136,15 @@ boolean hasRatingLimits = WebUtil.readBooleanParam(request, "hasRatingLimits", false); // refresh countRatedItems in case there is rating limit set + // Preview tool counts rated items on a criteria basis, other tools on a set of criteria basis! if (hasRatingLimits) { // as long as this can be requested only for LEARNER_ITEM_CRITERIA_TYPE type, cast Criteria LearnerItemRatingCriteria learnerItemRatingCriteria = (LearnerItemRatingCriteria) criteria; Long toolContentId = learnerItemRatingCriteria.getToolContentId(); - int countRatedItems = ratingService.getCountItemsRatedByUser(toolContentId, userId); + int countRatedItems = ratingLimitsByCriteria ? + ratingService.getCountItemsRatedByUserByCriteria(ratingCriteriaId, userId) : + ratingService.getCountItemsRatedByUser(toolContentId, userId); JSONObject.put("countRatedItems", countRatedItems); } } Index: lams_central/web/css/defaultHTML_learner.css =================================================================== diff -u -r295c0e97b45fe367406607d0deb6819692411aca -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_central/web/css/defaultHTML_learner.css (.../defaultHTML_learner.css) (revision 295c0e97b45fe367406607d0deb6819692411aca) +++ lams_central/web/css/defaultHTML_learner.css (.../defaultHTML_learner.css) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -25,6 +25,23 @@ padding-right: 5px; padding-left: 5px; } + .rating-criteria-tag { + width: 100%; + } + .rating-criteria-tag input[type="text"] { + width: 100%; + } + .rating-criteria-tag .ui-widget input{ + font-family:"Helvetica Neue",Helvetica,Arial,sans-serif; + font-size:11px; + } + #criterias-table { + margin-left: 0px; + border-bottom: inherit; + } + #criterias-table td { + vertical-align:middle; + } .navbar-login { margin-bottom: 5px; } @@ -152,10 +169,6 @@ .panel-admin-title { color:#337ab7 !important; } -.panel-title { - font-size: inherit; - font-weight: bolder; -} body.stripes { background: url('/lams/images/css/light-fabric.jpg'); } @@ -347,8 +360,16 @@ .rating-criteria-tag input[type="text"] { width: 100%; } +.rating-criteria-tag .ui-widget input{ + font-family:"Helvetica Neue",Helvetica,Arial,sans-serif; + font-size:12px; + line-height:1.42857143; + padding: 2px; +} + #criterias-table { margin-left: 0px; + border-bottom: inherit; } #criterias-table td { vertical-align:middle; @@ -493,14 +514,4 @@ } section{ padding-left: 15px; -} - -/* Login page */ - -.footer { - width: 100%; - /* Set the fixed height of the footer here */ - background-color: #f5f5f5; - font-size: 75%; - border-top: 1px solid#e7e7e7; -} +} \ No newline at end of file Index: lams_central/web/includes/javascript/interact.min.js =================================================================== diff -u --- lams_central/web/includes/javascript/interact.min.js (revision 0) +++ lams_central/web/includes/javascript/interact.min.js (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -0,0 +1,124 @@ +/* interact.js v1.2.6 | https://raw.github.com/taye/interact.js/master/LICENSE */ (function(F){function ma(){}function t(a){if(!a||"object"!==typeof a)return!1;var b=V(a)||q;return/object|function/.test(typeof b.Element)?a instanceof b.Element:1===a.nodeType&&"string"===typeof a.nodeName}function Ba(a){return a===q||!(!a||!a.Window)&&a instanceof a.Window}function da(a){return z(a)&&void 0!==typeof a.length&&A(a.splice)}function z(a){return!!a&&"object"===typeof a}function A(a){return"function"===typeof a}function K(a){return"number"===typeof a}function H(a){return"boolean"=== +typeof a}function N(a){return"string"===typeof a}function ea(a){if(!N(a))return!1;Q.querySelector(a);return!0}function x(a,b){for(var c in b)a[c]=b[c];return a}function ra(a,b){for(var c in b){var d=!1,e;for(e in Ca)if(0===c.indexOf(e)&&Ca[e].test(c)){d=!0;break}d||(a[c]=b[c])}return a}function sa(a,b){a.page=a.page||{};a.page.x=b.page.x;a.page.y=b.page.y;a.client=a.client||{};a.client.x=b.client.x;a.client.y=b.client.y;a.timeStamp=b.timeStamp}function Ta(a,b,c){a.page.x=c.page.x-b.page.x;a.page.y= +c.page.y-b.page.y;a.client.x=c.client.x-b.client.x;a.client.y=c.client.y-b.client.y;a.timeStamp=(new Date).getTime()-b.timeStamp;b=Math.max(a.timeStamp/1E3,.001);a.page.speed=fa(a.page.x,a.page.y)/b;a.page.vx=a.page.x/b;a.page.vy=a.page.y/b;a.client.speed=fa(a.client.x,a.page.y)/b;a.client.vx=a.client.x/b;a.client.vy=a.client.y/b}function Ua(a){return a instanceof q.Event||ga&&q.Touch&&a instanceof q.Touch}function ta(a,b,c){c=c||{};a=a||"page";c.x=b[a+"X"];c.y=b[a+"Y"];return c}function Da(a,b){b= +b||{};Va&&Ua(a)?(ta("screen",a,b),b.x+=q.scrollX,b.y+=q.scrollY):ta("page",a,b);return b}function Wa(a,b){b=b||{};Va&&Ua(a)?ta("screen",a,b):ta("client",a,b);return b}function O(a){return K(a.pointerId)?a.pointerId:a.identifier}function Ea(a){return a instanceof nb?a.correspondingUseElement:a}function V(a){if(Ba(a))return a;a=a.ownerDocument||a;return a.defaultView||a.parentWindow||q}function Fa(a){return(a=a instanceof Xa?a.getBoundingClientRect():a.getClientRects()[0])&&{left:a.left,right:a.right, +top:a.top,bottom:a.bottom,width:a.width||a.right-a.left,height:a.height||a.bottom-a.top}}function ua(a){var b,c=Fa(a);!ob&&c&&(b=(b=V(a))||q,a=b.scrollX||b.document.documentElement.scrollLeft,b=b.scrollY||b.document.documentElement.scrollTop,c.left+=a,c.right+=a,c.top+=b,c.bottom+=b);return c}function Ga(a){var b=[];da(a)?(b[0]=a[0],b[1]=a[1]):"touchend"===a.type?1===a.touches.length?(b[0]=a.touches[0],b[1]=a.changedTouches[0]):0===a.touches.length&&(b[0]=a.changedTouches[0],b[1]=a.changedTouches[1]): +(b[0]=a.touches[0],b[1]=a.touches[1]);return b}function Ya(a){for(var b={pageX:0,pageY:0,clientX:0,clientY:0,screenX:0,screenY:0},c,d=0;db?d+=360+d/360|0:-135>b&&(d+=180+d/360|0));return d}function na(a,b){var c=a?a.options.origin:D.origin;"parent"===c?c=L(b):"self"===c?c=a.getRect(b):ea(c)&&(c=Ka(b,c)||{x:0,y:0});A(c)&&(c=c(a&&b));t(c)&&(c=ua(c));c.x="x"in c?c.x:c.left;c.y="y"in +c?c.y:c.top;return c}function Za(a,b,c,d){var e=1-a;return e*e*b+2*e*a*c+a*a*d}function Y(a,b){for(;b;){if(b===a)return!0;b=b.parentNode}return!1}function Ka(a,b){for(var c=L(a);t(c);){if(R(c,b))return c;c=L(c)}return null}function L(a){if((a=a.parentNode)&&a instanceof $a)for(;(a=a.host)&&a&&a instanceof $a;);return a}function va(a,b){return a._context===b.ownerDocument||Y(a._context,b)}function Z(a,b,c){return(a=a.options.ignoreFrom)&&t(c)?N(a)?La(c,a,b):t(a)?Y(a,c):!1:!1}function aa(a,b,c){return(a= +a.options.allowFrom)?t(c)?N(a)?La(c,a,b):t(a)?Y(a,c):!1:!1:!0}function ab(a,b){if(!b)return!1;var c=b.options.drag.axis;return"xy"===a||"xy"===c||c===a}function Ma(a,b){var c=a.options;/^resize/.test(b)&&(b="resize");return c[b].snap&&c[b].snap.enabled}function Na(a,b){var c=a.options;/^resize/.test(b)&&(b="resize");return c[b].restrict&&c[b].restrict.enabled}function ha(a,b,c){for(var d=a.options,e=d[c.name].max,d=d[c.name].maxPerElement,h=0,f=0,g=0,k=0,y=r.length;k=wa||n.target===a&&(f+=u===c.name|0,f>=e||n.element===b&&(g++,u!==c.name||g>=d))))return!1}return 0(new Date).getTime()-db)return}if(c=bb(b,b.type,d))c._updateEventTargets(d,e),c[a](b,b,d,e)}}}function G(a,b,c,d,e,h){var f,g,k=a.target,y=a.snapStatus,n=a.restrictStatus,u=a.pointers,E=(k&&k.options||D).deltaSource,eb=E+"X",l=E+"Y",ia=k?k.options:D,w=na(k,e),m="start"===d,p="end"===d;f=m?a.startCoords:a.curCoords;e=e||a.element;g=x({},f.page);f=x({},f.client);g.x-=w.x;g.y-=w.y;f.x-=w.x;f.y-=w.y;var I=ia[c].snap&&ia[c].snap.relativePoints;!Ma(k,c)||m&&I&&I.length||(this.snap={range:y.range, +locked:y.locked,x:y.snappedX,y:y.snappedY,realX:y.realX,realY:y.realY,dx:y.dx,dy:y.dy},y.locked&&(g.x+=y.dx,g.y+=y.dy,f.x+=y.dx,f.y+=y.dy));!Na(k,c)||m&&ia[c].restrict.elementRect||!n.restricted||(g.x+=n.dx,g.y+=n.dy,f.x+=n.dx,f.y+=n.dy,this.restrict={dx:n.dx,dy:n.dy});this.pageX=g.x;this.pageY=g.y;this.clientX=f.x;this.clientY=f.y;this.x0=a.startCoords.page.x-w.x;this.y0=a.startCoords.page.y-w.y;this.clientX0=a.startCoords.client.x-w.x;this.clientY0=a.startCoords.client.y-w.y;this.ctrlKey=b.ctrlKey; +this.altKey=b.altKey;this.shiftKey=b.shiftKey;this.metaKey=b.metaKey;this.button=b.button;this.buttons=b.buttons;this.target=e;this.t0=a.downTimes[0];this.type=c+(d||"");this.interaction=a;this.interactable=k;e=a.inertiaStatus;e.active&&(this.detail="inertia");h&&(this.relatedTarget=h);p?"client"===E?(this.dx=f.x-a.startCoords.client.x,this.dy=f.y-a.startCoords.client.y):(this.dx=g.x-a.startCoords.page.x,this.dy=g.y-a.startCoords.page.y):m?this.dy=this.dx=0:"inertiastart"===d?(this.dx=a.prevEvent.dx, +this.dy=a.prevEvent.dy):"client"===E?(this.dx=f.x-a.prevEvent.clientX,this.dy=f.y-a.prevEvent.clientY):(this.dx=g.x-a.prevEvent.pageX,this.dy=g.y-a.prevEvent.pageY);a.prevEvent&&"inertia"===a.prevEvent.detail&&!e.active&&ia[c].inertia&&ia[c].inertia.zeroResumeDelta&&(e.resumeDx+=this.dx,e.resumeDy+=this.dy,this.dx=this.dy=0);"resize"===c&&a.resizeAxes?ia.resize.square?("y"===a.resizeAxes?this.dx=this.dy:this.dy=this.dx,this.axes="xy"):(this.axes=a.resizeAxes,"x"===a.resizeAxes?this.dy=0:"y"===a.resizeAxes&& +(this.dx=0)):"gesture"===c&&(this.touches=[u[0],u[1]],m?(this.distance=Ia(u,E),this.box=Ha(u),this.scale=1,this.ds=0,this.angle=Ja(u,void 0,E),this.da=0):p||b instanceof G?(this.distance=a.prevEvent.distance,this.box=a.prevEvent.box,this.scale=a.prevEvent.scale,this.ds=this.scale-1,this.angle=a.prevEvent.angle,this.da=this.angle-a.gesture.startAngle):(this.distance=Ia(u,E),this.box=Ha(u),this.scale=this.distance/a.gesture.startDistance,this.angle=Ja(u,a.gesture.prevAngle,E),this.ds=this.scale-a.gesture.prevScale, +this.da=this.angle-a.gesture.prevAngle));m?(this.timeStamp=a.downTimes[0],this.velocityY=this.velocityX=this.speed=this.duration=this.dt=0):"inertiastart"===d?(this.timeStamp=a.prevEvent.timeStamp,this.dt=a.prevEvent.dt,this.duration=a.prevEvent.duration,this.speed=a.prevEvent.speed,this.velocityX=a.prevEvent.velocityX,this.velocityY=a.prevEvent.velocityY):(this.timeStamp=(new Date).getTime(),this.dt=this.timeStamp-a.prevEvent.timeStamp,this.duration=this.timeStamp-a.downTimes[0],b instanceof G?(b= +this[eb]-a.prevEvent[eb],l=this[l]-a.prevEvent[l],c=this.dt/1E3,this.speed=fa(b,l)/c,this.velocityX=b/c,this.velocityY=l/c):(this.speed=a.pointerDelta[E].speed,this.velocityX=a.pointerDelta[E].vx,this.velocityY=a.pointerDelta[E].vy));(p||"inertiastart"===d)&&600this.timeStamp-a.prevEvent.timeStamp&&(d=180*Math.atan2(a.prevEvent.velocityY,a.prevEvent.velocityX)/Math.PI,0>d&&(d+=360),p=112.5<=d&&247.5>d,l=202.5<=d&&337.5>d,this.swipe={up:l,down:!l&&22.5<=d&&157.5>d,left:p,right:!p&& +(292.5<=d||67.5>d),angle:d,speed:a.prevEvent.speed,velocity:{x:a.prevEvent.velocityX,y:a.prevEvent.velocityY}})}function fb(){this.originalEvent.preventDefault()}function gb(a){var b="";"drag"===a.name&&(b=za.drag);if("resize"===a.name)if(a.axis)b=za[a.name+a.axis];else if(a.edges){for(var b="resize",c=["top","bottom","left","right"],d=0;4>d;d++)a.edges[c[d]]&&(b+=c[d]);b=za[b]}return b}function hb(a,b,c){a=this.getRect(c);var d=!1,e=null,h=null,f,g=x({},b.curCoords.page),e=this.options;if(!a)return null; +if(S.resize&&e.resize.enabled)if(d=e.resize,f={left:!1,right:!1,top:!1,bottom:!1},z(d.edges)){for(var k in f){var y=f,n=k,u;a:{u=k;var E=d.edges[k],l=g,m=b._eventTarget,p=c,w=a,ya=d.margin||pa;if(E){if(!0===E){var q=K(w.width)?w.width:w.right-w.left,I=K(w.height)?w.height:w.bottom-w.top;0>q&&("left"===u?u="right":"right"===u&&(u="left"));0>I&&("top"===u?u="bottom":"bottom"===u&&(u="top"));if("left"===u){u=l.x<(0<=q?w.left:w.right)+ya;break a}if("top"===u){u=l.y<(0<=I?w.top:w.bottom)+ya;break a}if("right"=== +u){u=l.x>(0<=q?w.right:w.left)-ya;break a}if("bottom"===u){u=l.y>(0<=I?w.bottom:w.top)-ya;break a}}u=t(m)?t(E)?E===m:La(m,E,p):!1}else u=!1}y[n]=u}f.left=f.left&&!f.right;f.top=f.top&&!f.bottom;d=f.left||f.right||f.top||f.bottom}else c="y"!==e.resize.axis&&g.x>a.right-pa,a="x"!==e.resize.axis&&g.y>a.bottom-pa,d=c||a,h=(c?"x":"")+(a?"y":"");e=d?"resize":S.drag&&e.drag.enabled?"drag":null;S.gesture&&2<=b.pointerIds.length&&!b.dragging&&!b.resizing&&(e="gesture");return e?{name:e,axis:h,edges:f}:null} +function W(a,b){if(!z(a))return null;var c=a.name,d=b.options;return("resize"===c&&d.resize.enabled||"drag"===c&&d.drag.enabled||"gesture"===c&&d.gesture.enabled)&&S[c]?a:null}function qa(a,b){var c={},d=P[a.type],e=Ea(a.path?a.path[0]:a.target),h=e;b=b?!0:!1;for(var f in a)c[f]=a[f];c.originalEvent=a;for(c.preventDefault=fb;t(h);){for(f=0;fthis.pointerIds.length&&(f=null),this.prepared.name=f.name,this.prepared.axis=f.axis,this.prepared.edges=f.edges,this.snapStatus.snappedX=this.snapStatus.snappedY=this.restrictStatus.restrictedX=this.restrictStatus.restrictedY=NaN,this.downTimes[h]=(new Date).getTime(),this.downTargets[h]=c,ra(this.downPointer,a),sa(this.prevCoords,this.startCoords),this.pointerWasMoved=!1,this.checkAndPreventDefault(b,g,this.element)))}},setModifications:function(a, +b){var c=this.target,d=!0,e=Ma(c,this.prepared.name)&&(!c.options[this.prepared.name].snap.endOnly||b),c=Na(c,this.prepared.name)&&(!c.options[this.prepared.name].restrict.endOnly||b);e?this.setSnapping(a):this.snapStatus.locked=!1;c?this.setRestriction(a):this.restrictStatus.restricted=!1;e&&this.snapStatus.locked&&!this.snapStatus.changed?d=c&&this.restrictStatus.restricted&&this.restrictStatus.changed:c&&this.restrictStatus.restricted&&!this.restrictStatus.changed&&(d=!1);return d},setStartOffsets:function(a, +b,c){a=b.getRect(c);var d=na(b,c);c=b.options[this.prepared.name].snap;b=b.options[this.prepared.name].restrict;var e,h;a?(this.startOffset.left=this.startCoords.page.x-a.left,this.startOffset.top=this.startCoords.page.y-a.top,this.startOffset.right=a.right-this.startCoords.page.x,this.startOffset.bottom=a.bottom-this.startCoords.page.y,e="width"in a?a.width:a.right-a.left,h="height"in a?a.height:a.bottom-a.top):this.startOffset.left=this.startOffset.top=this.startOffset.right=this.startOffset.bottom= +0;this.snapOffsets.splice(0);d=c&&"startCoords"===c.offset?{x:this.startCoords.page.x-d.x,y:this.startCoords.page.y-d.y}:c&&c.offset||{x:0,y:0};if(a&&c&&c.relativePoints&&c.relativePoints.length)for(var f=0;fQa);d||this.pointerIsDown&&!this.pointerWasMoved||(this.pointerIsDown&&clearTimeout(this.holdTimers[h]),this.collectEventTargets(a,b,c,"move"));if(this.pointerIsDown)if(d&&this.pointerWasMoved&&!e)this.checkAndPreventDefault(b,this.target,this.element);else if(Ta(this.pointerDelta,this.prevCoords,this.curCoords),this.prepared.name){if(this.pointerWasMoved&&(!this.inertiaStatus.active||a instanceof G&&/inertiastart/.test(a.type))){if(!this.interacting()&&(Ta(this.pointerDelta, +this.prevCoords,this.curCoords),"drag"===this.prepared.name)){f=Math.abs(f);g=Math.abs(g);d=this.target.options.drag.axis;var k=f>g?"x":fk.bottom&&(b=k.top,k.top=k.bottom,k.bottom=b),k.left>k.right&&(b=k.left,k.left=k.right,k.right=b))):(k.top=Math.min(g.top,f.bottom),k.bottom=Math.max(g.bottom,f.top),k.left=Math.min(g.left,f.right),k.right=Math.max(g.right,f.left));k.width=k.right-k.left;k.height=k.bottom-k.top;for(var q in k)l[q]=k[q]-n[q];a.edges=this.prepared.edges;a.rect=k;a.deltaRect=l}this.target.fire(a); +return a},gestureStart:function(a){a=new G(this,a,"gesture","start",this.element);a.ds=0;this.gesture.startDistance=this.gesture.prevDistance=a.distance;this.gesture.startAngle=this.gesture.prevAngle=a.angle;this.gesture.scale=1;this.gesturing=!0;this.target.fire(a);return a},gestureMove:function(a){if(!this.pointerIds.length)return this.prevEvent;a=new G(this,a,"gesture","move",this.element);a.ds=a.scale-this.gesture.scale;this.target.fire(a);this.gesture.prevAngle=a.angle;this.gesture.prevDistance= +a.distance;Infinity===a.scale||null===a.scale||void 0===a.scale||isNaN(a.scale)||(this.gesture.scale=a.scale);return a},pointerHold:function(a,b,c){this.collectEventTargets(a,b,c,"hold")},pointerUp:function(a,b,c,d){var e=this.mouse?0:v(this.pointerIds,O(a));clearTimeout(this.holdTimers[e]);this.collectEventTargets(a,b,c,"up");this.collectEventTargets(a,b,c,"tap");this.pointerEnd(a,b,c,d);this.removePointer(a)},pointerCancel:function(a,b,c,d){var e=this.mouse?0:v(this.pointerIds,O(a));clearTimeout(this.holdTimers[e]); +this.collectEventTargets(a,b,c,"cancel");this.pointerEnd(a,b,c,d);this.removePointer(a)},ie8Dblclick:function(a,b,c){this.prevTap&&b.clientX===this.prevTap.clientX&&b.clientY===this.prevTap.clientY&&c===this.prevTap.target&&(this.downTargets[0]=c,this.downTimes[0]=(new Date).getTime(),this.collectEventTargets(a,b,c,"tap"))},pointerEnd:function(a,b,c,d){var e,h=this.target,f=h&&h.options,g=f&&this.prepared.name&&f[this.prepared.name].inertia;e=this.inertiaStatus;if(this.interacting()){if(e.active&& +!e.ending)return;var k=(new Date).getTime(),l=!1,m=!1,n=!1,p=Ma(h,this.prepared.name)&&f[this.prepared.name].snap.endOnly,q=Na(h,this.prepared.name)&&f[this.prepared.name].restrict.endOnly,r=0,t=0,f=this.dragging?"x"===f.drag.axis?Math.abs(this.pointerDelta.client.vx):"y"===f.drag.axis?Math.abs(this.pointerDelta.client.vy):this.pointerDelta.client.speed:this.pointerDelta.client.speed,m=(l=g&&g.enabled&&"gesture"!==this.prepared.name&&b!==e.startEvent)&&50>k-this.curCoords.timeStamp&&f>g.minSpeed&& +f>g.endSpeed;l&&!m&&(p||q)&&(g={},g.snap=g.restrict=g,p&&(this.setSnapping(this.curCoords.page,g),g.locked&&(r+=g.dx,t+=g.dy)),q&&(this.setRestriction(this.curCoords.page,g),g.restricted&&(r+=g.dx,t+=g.dy)),r||t)&&(n=!0);if(m||n){sa(e.upCoords,this.curCoords);this.pointers[0]=e.startEvent=new G(this,b,this.prepared.name,"inertiastart",this.element);e.t0=k;h.fire(e.startEvent);m?(e.vx0=this.pointerDelta.client.vx,e.vy0=this.pointerDelta.client.vy,e.v0=f,this.calcInertia(e),b=x({},this.curCoords.page), +h=na(h,this.element),b.x=b.x+e.xe-h.x,b.y=b.y+e.ye-h.y,h={useStatusXY:!0,x:b.x,y:b.y,dx:0,dy:0,snap:null},h.snap=h,r=t=0,p&&(b=this.setSnapping(this.curCoords.page,h),b.locked&&(r+=b.dx,t+=b.dy)),q&&(h=this.setRestriction(this.curCoords.page,h),h.restricted&&(r+=h.dx,t+=h.dy)),e.modifiedXe+=r,e.modifiedYe+=t,e.i=T(this.boundInertiaFrame)):(e.smoothEnd=!0,e.xe=r,e.ye=t,e.sx=e.sy=0,e.i=T(this.boundSmoothEndFrame));e.active=!0;return}(p||q)&&this.pointerMove(a,b,c,d,!0)}this.dragging?(e=new G(this,b, +"drag","end",this.element),q=this.getDrop(e,b,this.element),this.dropTarget=q.dropzone,this.dropElement=q.element,q=this.getDropEvents(b,e),q.leave&&this.prevDropTarget.fire(q.leave),q.enter&&this.dropTarget.fire(q.enter),q.drop&&this.dropTarget.fire(q.drop),q.deactivate&&this.fireActiveDrops(q.deactivate),h.fire(e)):this.resizing?(e=new G(this,b,"resize","end",this.element),h.fire(e)):this.gesturing&&(e=new G(this,b,"gesture","end",this.element),h.fire(e));this.stop(b)},collectDrops:function(a){var b= +[],c=[],d;a=a||this.element;for(d=0;dk),g["double"]=l,this.tapTime=g.timeStamp);for(a=0;ah.innerWidth-p.margin, +a=a.clientY>h.innerHeight-p.margin):(h=Fa(h),d=a.clientXh.right-p.margin,a=a.clientY>h.bottom-p.margin);p.x=c?1:d?-1:0;p.y=a?1:b?-1:0;p.isScrolling||(p.margin=e.margin,p.speed=e.speed,p.start(this))}},_updateEventTargets:function(a,b){this._eventTarget=a;this._curEventTarget=b}};G.prototype={preventDefault:ma,stopImmediatePropagation:function(){this.immediatePropagationStopped=this.propagationStopped=!0},stopPropagation:function(){this.propagationStopped= +!0}};for(var m={},lb="dragStart dragMove resizeStart resizeMove gestureStart gestureMove pointerOver pointerOut pointerHover selectorDown pointerDown pointerMove pointerUp pointerCancel pointerEnd addPointer removePointer recordPointer autoScrollMove".split(" "),Ra=0,Sa=lb.length;Rah.left&&k.xh.top&&k.y=h.left&&f<=h.right&&l>=h.top&&l<=h.bottom;K(g)&&(f=Math.max(0,Math.min(h.right,k.right)-Math.max(h.left,k.left))*Math.max(0,Math.min(h.bottom,k.bottom)-Math.max(h.top,k.top))/(k.width*k.height)>=g);this.options.drop.checker&&(f=this.options.drop.checker(a,b,f,this,e,c,d));return f},dropChecker:function(a){return A(a)?(this.options.drop.checker=a,this): +null===a?(delete this.options.getRect,this):this.options.drop.checker},accept:function(a){return t(a)||ea(a)?(this.options.drop.accept=a,this):null===a?(delete this.options.drop.accept,this):this.options.drop.accept},resizable:function(a){return z(a)?(this.options.resize.enabled=!1===a.enabled?!1:!0,this.setPerAction("resize",a),this.setOnEvents("resize",a),/^x$|^y$|^xy$/.test(a.axis)?this.options.resize.axis=a.axis:null===a.axis&&(this.options.resize.axis=D.resize.axis),H(a.preserveAspectRatio)? +this.options.resize.preserveAspectRatio=a.preserveAspectRatio:H(a.square)&&(this.options.resize.square=a.square),this):H(a)?(this.options.resize.enabled=a,this):this.options.resize},squareResize:function(a){return H(a)?(this.options.resize.square=a,this):null===a?(delete this.options.resize.square,this):this.options.resize.square},gesturable:function(a){return z(a)?(this.options.gesture.enabled=!1===a.enabled?!1:!0,this.setPerAction("gesture",a),this.setOnEvents("gesture",a),this):H(a)?(this.options.gesture.enabled= +a,this):this.options.gesture},autoScroll:function(a){z(a)?a=x({actions:["drag","resize"]},a):H(a)&&(a={actions:["drag","resize"],enabled:a});return this.setOptions("autoScroll",a)},snap:function(a){a=this.setOptions("snap",a);return a===this?this:a.drag},setOptions:function(a,b){var c=b&&da(b.actions)?b.actions:["drag"],d;if(z(b)||H(b)){for(d=0;d1){return;}g.preventDefault();var i=g.originalEvent.changedTouches[0],f=document.createEvent("MouseEvents");f.initMouseEvent(h,true,true,window,1,i.screenX,i.screenY,i.clientX,i.clientY,false,false,false,false,0,null);g.target.dispatchEvent(f);}c._touchStart=function(g){var f=this;if(a||!f._mouseCapture(g.originalEvent.changedTouches[0])){return;}a=true;f._touchMoved=false;d(g,"mouseover");d(g,"mousemove");d(g,"mousedown");};c._touchMove=function(f){if(!a){return;}this._touchMoved=true;d(f,"mousemove");};c._touchEnd=function(f){if(!a){return;}d(f,"mouseup");d(f,"mouseout");if(!this._touchMoved){d(f,"click");}a=false;};c._mouseInit=function(){var f=this;f.element.bind("touchstart",b.proxy(f,"_touchStart")).bind("touchmove",b.proxy(f,"_touchMove")).bind("touchend",b.proxy(f,"_touchEnd"));e.call(f);};})(jQuery); \ No newline at end of file +(function ($) { + + debugger; + // Detect touch support + $.support.touch = 'ontouchend' in document; + + // Ignore browsers without touch support + if (!$.support.touch) { + return; + } + + var mouseProto = $.ui.mouse.prototype, + _mouseInit = mouseProto._mouseInit, + _mouseDestroy = mouseProto._mouseDestroy, + touchHandled; + + /** + * Simulate a mouse event based on a corresponding touch event + * @param {Object} event A touch event + * @param {String} simulatedType The corresponding mouse event + */ + function simulateMouseEvent (event, simulatedType) { + + // Ignore multi-touch events + if (event.originalEvent.touches.length > 1) { + return; + } + + event.preventDefault(); + + var touch = event.originalEvent.changedTouches[0], + simulatedEvent = document.createEvent('MouseEvents'); + + // Initialize the simulated mouse event using the touch event's coordinates + simulatedEvent.initMouseEvent( + simulatedType, // type + true, // bubbles + true, // cancelable + window, // view + 1, // detail + touch.screenX, // screenX + touch.screenY, // screenY + touch.clientX, // clientX + touch.clientY, // clientY + false, // ctrlKey + false, // altKey + false, // shiftKey + false, // metaKey + 0, // button + null // relatedTarget + ); + + // Dispatch the simulated event to the target element + event.target.dispatchEvent(simulatedEvent); + } + + /** + * Handle the jQuery UI widget's touchstart events + * @param {Object} event The widget element's touchstart event + */ + mouseProto._touchStart = function (event) { + + debugger; + + var self = this; + + // Ignore the event if another widget is already being handled + if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) { + return; + } + + // Set the flag to prevent other widgets from inheriting the touch event + touchHandled = true; + + // Track movement to determine if interaction was a click + self._touchMoved = false; + + // Simulate the mouseover event + simulateMouseEvent(event, 'mouseover'); + + // Simulate the mousemove event + simulateMouseEvent(event, 'mousemove'); + + // Simulate the mousedown event + simulateMouseEvent(event, 'mousedown'); + }; + + /** + * Handle the jQuery UI widget's touchmove events + * @param {Object} event The document's touchmove event + */ + mouseProto._touchMove = function (event) { + + // Ignore event if not handled + if (!touchHandled) { + return; + } + + // Interaction was not a click + this._touchMoved = true; + + // Simulate the mousemove event + simulateMouseEvent(event, 'mousemove'); + }; + + /** + * Handle the jQuery UI widget's touchend events + * @param {Object} event The document's touchend event + */ + mouseProto._touchEnd = function (event) { + + // Ignore event if not handled + if (!touchHandled) { + return; + } + + // Simulate the mouseup event + simulateMouseEvent(event, 'mouseup'); + + // Simulate the mouseout event + simulateMouseEvent(event, 'mouseout'); + + // If the touch interaction did not move, it should trigger a click + if (!this._touchMoved) { + + // Simulate the click event + simulateMouseEvent(event, 'click'); + } + + // Unset the flag to allow other widgets to inherit the touch event + touchHandled = false; + }; + + /** + * A duck punch of the $.ui.mouse _mouseInit method to support touch events. + * This method extends the widget with bound touch event handlers that + * translate touch events to mouse events and pass them to the widget's + * original mouse event handling methods. + */ + mouseProto._mouseInit = function () { + + var self = this; + + // Delegate the touch handlers to the widget's element + self.element.bind({ + touchstart: $.proxy(self, '_touchStart'), + touchmove: $.proxy(self, '_touchMove'), + touchend: $.proxy(self, '_touchEnd') + }); + + // Call the original $.ui.mouse init method + _mouseInit.call(self); + }; + + /** + * Remove the touch event handlers + */ + mouseProto._mouseDestroy = function () { + + var self = this; + + // Delegate the touch handlers to the widget's element + self.element.unbind({ + touchstart: $.proxy(self, '_touchStart'), + touchmove: $.proxy(self, '_touchMove'), + touchend: $.proxy(self, '_touchEnd') + }); + + // Call the original $.ui.mouse destroy method + _mouseDestroy.call(self); + }; + +})(jQuery); \ No newline at end of file Index: lams_central/web/includes/javascript/rating.js =================================================================== diff -u -rad572fe631188a44a1b0976295ee33af9919ed78 -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_central/web/includes/javascript/rating.js (.../rating.js) (revision ad572fe631188a44a1b0976295ee33af9919ed78) +++ lams_central/web/includes/javascript/rating.js (.../rating.js) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -30,17 +30,26 @@ maxRatingsForItem = ""; else maxRatingsForItem = MAX_RATINGS_FOR_ITEM; - + + var ratingLimitsByCriteria; + if ( typeof LIMIT_BY_CRITERIA === "undefined" || LIMIT_BY_CRITERIA === undefined ) + ratingLimitsByCriteria = false; + else + ratingLimitsByCriteria = LIMIT_BY_CRITERIA; + $(".rating-stars-new").filter($(".rating-stars")).jRating({ - phpPath : LAMS_URL + "servlet/rateItem?hasRatingLimits=" + HAS_RATING_LIMITS+"&maxRatingsForItem="+maxRatingsForItem, + phpPath : LAMS_URL + "servlet/rateItem?hasRatingLimits=" + HAS_RATING_LIMITS + "&ratingLimitsByCriteria=" + ratingLimitsByCriteria + "&maxRatingsForItem=" + maxRatingsForItem, rateMax : 5, decimalLength : 1, onSuccess : function(data, itemId){ $("#user-rating-" + itemId).html(data.userRating); $("#average-rating-" + itemId).html(data.averageRating); $("#number-of-votes-" + itemId).html(data.numberOfVotes); $("#rating-stars-caption-" + itemId).css("visibility", "visible"); + var parts = itemId.split('-'); + $("#comment-tick-" + parts[1]).css("visibility", "visible"); + //handle rating limits if available handleRatingLimits(data.countRatedItems, itemId); }, @@ -93,6 +102,7 @@ idBox: commentsCriteriaId + '-' + itemId, comment: comment, hasRatingLimits: HAS_RATING_LIMITS, + ratingLimitsByCriteria: ratingLimitsByCriteria, maxRatingsForItem: maxRatingsForItem }, success: function(data, textStatus) { @@ -137,6 +147,8 @@ if (HAS_RATING_LIMITS) { + debugger; + //update info box $("#count-rated-items").html(countRatedItems); Index: lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/rating/Rating.hbm.xml =================================================================== diff -u -rf3be94729c90c7390a6aea11d1886a4b72d96670 -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/rating/Rating.hbm.xml (.../Rating.hbm.xml) (revision f3be94729c90c7390a6aea11d1886a4b72d96670) +++ lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/rating/Rating.hbm.xml (.../Rating.hbm.xml) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -58,7 +58,7 @@ @hibernate.property column="rating_criteria_type_id" length="11" + + + + + + + + + + + + Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch2040071.sql =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch2040071.sql (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch2040071.sql (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -0,0 +1,18 @@ +-- Turn off autocommit, so nothing is committed if there is an error + +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; + +-- LDEV-3767 Peer review: two new review methods +ALTER TABLE lams_rating_criteria ADD COLUMN rating_style BIGINT(20) NOT NULL DEFAULT 1; +ALTER TABLE lams_rating_criteria ADD COLUMN max_rating BIGINT(20) NOT NULL DEFAULT 5; +ALTER TABLE lams_rating_criteria ADD COLUMN minimum_rates INT(11) DEFAULT '0'; +ALTER TABLE lams_rating_criteria ADD COLUMN maximum_rates INT(11) DEFAULT '0'; + +UPDATE lams_rating_criteria SET rating_style = 0 WHERE comments_enabled = 1; +UPDATE lams_rating_criteria SET title = "Comment" WHERE comments_enabled = 1 AND title is null; + +-- If there were no errors, commit and restore autocommit to on +SET FOREIGN_KEY_CHECKS=0; +COMMIT; +SET AUTOCOMMIT = 1; Index: lams_common/src/java/org/lamsfoundation/lams/rating/dao/IRatingCommentDAO.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/dao/IRatingCommentDAO.java (.../IRatingCommentDAO.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dao/IRatingCommentDAO.java (.../IRatingCommentDAO.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -20,7 +20,6 @@ * **************************************************************** */ - package org.lamsfoundation.lams.rating.dao; import java.util.Collection; @@ -63,6 +62,11 @@ List getCommentsByCriteriaAndItemsAndUser(Long ratingCriteriaId, Collection itemIds, Integer userId); + /** + * Get the comment relating to a ranking/hedging/star criteria, rather than the comment for a comment criteria. + */ + List getRelatedCommentByCriteriaAndUser(Long ratingCriteriaId, Integer userId); + RatingComment getComment(Long ratingCriteriaId, Integer userId, Long itemId); } Index: lams_common/src/java/org/lamsfoundation/lams/rating/dao/IRatingDAO.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/dao/IRatingDAO.java (.../IRatingDAO.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dao/IRatingDAO.java (.../IRatingDAO.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -30,18 +30,23 @@ import java.util.Map; import org.lamsfoundation.lams.rating.dto.ItemRatingCriteriaDTO; +import org.lamsfoundation.lams.rating.dto.StyledRatingDTO; import org.lamsfoundation.lams.rating.model.Rating; public interface IRatingDAO { void saveOrUpdate(Object object); + + void delete(Object object); Rating getRating(Long ratingCriteriaId, Integer userId, Long itemId); List getRatingsByItem(Long contentId, Long itemId); List getRatingsByUser(Long contentId, Integer userId); + List getRatingsByUserCriteria(Long criteriaId, Integer userId); + /** * Returns rating statistics by particular item * @@ -83,6 +88,16 @@ int getCountItemsRatedByUser(final Long toolContentId, final Integer userId); /** + * Returns number of items rated by specified user in a current activity, for a particular criteria. It counts comments as ratings + * iff it is a comment rating. This method is applicable only for RatingCriterias of LEARNER_ITEM_CRITERIA_TYPE type. + * + * @param toolContentId + * @param userId + * @return + */ + int getCountItemsRatedByUserByCriteria(final Long criteriaId, final Integer userId); + + /** * Count how many users rated and commented each item. * * @param contentId @@ -93,4 +108,21 @@ Map countUsersRatedEachItem(final Long contentId, final Collection itemIds, Integer excludeUserId); + /** + * Count how many users rated and commented each item for a particular criteria. + * + * @param contentId + * @param itemIds + * @param excludeUserId + * @return + */ + Map countUsersRatedEachItemByCriteria(final Long criteriaId, final Collection itemIds, + Integer excludeUserId); + + /** + * Used by tools to get the ratings and comments relating to their items. To be used within SQL and supply the toolContentId as :toolContentId. + * See Peer Review for example usage. + */ + String getRatingSelectJoinSQL(Integer ratingStyle, boolean getByUser); + } Index: lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingCommentDAO.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingCommentDAO.java (.../RatingCommentDAO.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingCommentDAO.java (.../RatingCommentDAO.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -52,6 +52,9 @@ private static final String FIND_COMMENTS_BY_CRITERIA = "SELECT r.itemId, r.learner.userId, r.comment " + "FROM " + RatingComment.class.getName() + " AS r where r.ratingCriteria.ratingCriteriaId=?"; + private static final String FIND_RELATED_COMMENT_BY_CRITERIA_AND_USER = "SELECT r.itemId, r.learner.userId, r.comment FROM " + RatingComment.class.getName() + + " AS r where r.ratingCriteria.ratingCriteriaId=:ratingCriteriaId AND r.learner.userId=:userId"; + // private static final String COUNT_COMMENTS_BY_ITEM_AND_USER = "SELECT COUNT(r) FROM " // + RatingComment.class.getName() // + " AS r " @@ -93,6 +96,14 @@ return convertIntoCommentDtos(results); } + @Override + public List getRelatedCommentByCriteriaAndUser(Long ratingCriteriaId, Integer userId) { + List results = getSession().createQuery(FIND_RELATED_COMMENT_BY_CRITERIA_AND_USER) + .setLong("ratingCriteriaId", ratingCriteriaId) + .setInteger("userId", userId).list(); + + return convertIntoCommentDtos(results); + } /* * Converts DB results presentation into list of RatingCommentDTO. * Index: lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingCriteriaDAO.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingCriteriaDAO.java (.../RatingCriteriaDAO.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingCriteriaDAO.java (.../RatingCriteriaDAO.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -40,10 +40,10 @@ + " AS r WHERE r.toolContentId=? order by r.orderId asc"; private static final String IS_COMMENTS_ENABLED_FOR_TOOL_CONTENT_ID = "SELECT COUNT(*) FROM " - + RatingCriteria.class.getName() + " AS r WHERE r.toolContentId=? AND r.commentsEnabled=1"; + + RatingCriteria.class.getName() + " AS r WHERE r.toolContentId=? AND r.ratingStyle = 0"; private static final String GET_COMMENTS_MIN_WORDS_LIMIT_FOR_TOOL_CONTENT_ID = "SELECT r.commentsMinWordsLimit FROM " - + RatingCriteria.class.getName() + " AS r WHERE r.toolContentId=? AND r.commentsEnabled=1"; + + RatingCriteria.class.getName() + " AS r WHERE r.toolContentId=? AND r.ratingStyle = 0"; @Override public void saveOrUpdate(RatingCriteria criteria) { Index: lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingDAO.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingDAO.java (.../RatingDAO.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dao/hibernate/RatingDAO.java (.../RatingDAO.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -39,6 +39,7 @@ import org.lamsfoundation.lams.rating.dto.ItemRatingCriteriaDTO; import org.lamsfoundation.lams.rating.model.Rating; import org.lamsfoundation.lams.rating.model.RatingComment; +import org.lamsfoundation.lams.rating.model.RatingCriteria; public class RatingDAO extends LAMSBaseDAO implements IRatingDAO { @@ -57,6 +58,9 @@ private static final String FIND_RATINGS_BY_USER = "FROM " + Rating.class.getName() + " AS r where r.ratingCriteria.toolContentId=? AND r.learner.userId=?"; + private static final String FIND_RATINGS_BY_USER_CRITERIA = "FROM " + Rating.class.getName() + + " AS r where r.ratingCriteria.ratingCriteriaId=? AND r.learner.userId=?"; + private static final String FIND_RATING_AVERAGE_BY_ITEM = "SELECT AVG(r.rating), COUNT(*) FROM " + Rating.class.getName() + " AS r where r.ratingCriteria.ratingCriteriaId=? AND r.itemId=?"; @@ -72,6 +76,38 @@ + Rating.class.getName() + " AS r where r.ratingCriteria.toolContentId=? GROUP BY r.itemId, r.ratingCriteria.ratingCriteriaId"; + // Used by tools to get the ratings and comments relating to their items, as submitted by a particular user. + // To be used within SQL and supply the userId as :userId and criteriaId as :ratingCriteriaId + // See Peer Review for example usage. Special version for comment style as there is no entry in the lams_rating table for a comment style rating. + private static final String TOOL_SELECT_LEFT_JOIN_BY_USER_STANDARD = "SELECT r.item_id, rc.comment, r2.rating, AVG(r.rating) average_rating, COUNT(r.rating) count_vote " + + " FROM lams_rating r " + + " LEFT JOIN lams_rating r2 ON r2.rating_criteria_id = r.rating_criteria_id AND r.item_id = r2.item_id AND r2.user_id = :userId " + + " LEFT JOIN lams_rating_comment rc ON rc.rating_criteria_id = r.rating_criteria_id AND rc.item_id = r2.item_id AND rc.user_id = :userId " + + " WHERE r.rating_criteria_id = :ratingCriteriaId " + + " GROUP BY r.item_id"; + + // Used by tools to get the ratings and comments relating to their items. To be used within SQL and supply criteriaId as :ratingCriteriaId + // See Peer Review for example usage. + private static final String TOOL_SELECT_LEFT_JOIN_BY_USER_COMMENT = "SELECT r.item_id, r.comment " + + " FROM lams_rating_comment r WHERE r.rating_criteria_id = :ratingCriteriaId "; + + // Same as TOOL_SELECT_LEFT_JOIN_BY_USER_STANDARD except that it returns the average results for a single user (:userId), as left by other users + private static final String TOOL_SELECT_LEFT_JOIN_FOR_USER_STANDARD = "SELECT DISTINCT r.item_id, rc.comment, NULL rating, calc.average_rating, calc.count_vote " + + " FROM lams_rating r" + + " JOIN lams_rating_criteria c ON r.item_id = :userId AND c.tool_content_id = :toolContentId " + + " AND c.rating_criteria_id = :ratingCriteriaId AND c.rating_criteria_id = r.rating_criteria_id " + + " LEFT JOIN lams_rating_comment rc ON rc.rating_criteria_id = r.rating_criteria_id AND rc.item_id = r.item_id and rc.user_id = r.user_id " + + " LEFT JOIN ( " + + " SELECT r2.item_id, AVG(r2.rating) average_rating, COUNT(*) count_vote " + + " FROM lams_rating r2 WHERE r2.rating_criteria_id = :ratingCriteriaId AND r2.item_id = :userId " + + " GROUP BY r2.item_id " + + " ) calc ON calc.item_id = r.item_id "; + + // Same as TOOL_SELECT_LEFT_JOIN_BY_USER_COMMENT except that it returns all the comments results for a single user (:userId), as left by other users + private static final String TOOL_SELECT_LEFT_JOIN_FOR_USER_COMMENT = "SELECT r.item_id, r.comment" + + " FROM lams_rating_comment r " + + " WHERE r.item_id = :userId AND r.rating_criteria_id = :ratingCriteriaId"; + // private static final String COUNT_ITEMS_RATED_BY_ACTIVITY_AND_USER = "SELECT COUNT(DISTINCT r.itemId)+(SELECT COUNT(comment) FROM " // + RatingComment.class.getName() // + " AS comment " @@ -86,6 +122,11 @@ getSession().flush(); } + public void delete(Object object) { + getSession().delete(object); + getSession().flush(); + } + @Override public Rating getRating(Long ratingCriteriaId, Integer userId, Long itemId) { List list = (List) doFind(FIND_RATING_BY_CRITERIA_AND_USER_AND_ITEM, @@ -119,6 +160,11 @@ } @Override + public List getRatingsByUserCriteria(Long criteriaId, Integer userId) { + return (List) doFind(FIND_RATINGS_BY_USER_CRITERIA, new Object[] { criteriaId, userId }); + } + + @Override public ItemRatingCriteriaDTO getRatingAverageDTOByItem(Long ratingCriteriaId, Long itemId) { List list = (List) doFind(FIND_RATING_AVERAGE_BY_ITEM, new Object[] { ratingCriteriaId, itemId }); @@ -183,6 +229,29 @@ } @Override + public int getCountItemsRatedByUserByCriteria(final Long criteriaId, final Integer userId) { + + // unions don't work in HQL so doing 2 separate DB queries (http://stackoverflow.com/a/3940445) + String FIND_ITEM_IDS_RATED_BY_USER = "SELECT DISTINCT r.itemId FROM " + Rating.class.getName() + " AS r " + + " WHERE r.ratingCriteria.ratingCriteriaId = :criteriaId AND r.learner.userId =:userId"; + + String FIND_ITEM_IDS_COMMENTED_BY_USER = "SELECT DISTINCT comment.itemId FROM " + RatingComment.class.getName() + + " AS comment " + + " WHERE comment.ratingCriteria.ratingCriteriaId = :criteriaId AND comment.learner.userId =:userId AND comment.ratingCriteria.commentsEnabled IS TRUE"; + + List ratedItemIds = this.getSession().createQuery(FIND_ITEM_IDS_RATED_BY_USER) + .setLong("criteriaId", criteriaId).setInteger("userId", userId).list(); + + List commentedItemIds = this.getSession().createQuery(FIND_ITEM_IDS_COMMENTED_BY_USER) + .setLong("criteriaId", criteriaId).setInteger("userId", userId).list(); + + Set unionItemIds = new HashSet(ratedItemIds); + unionItemIds.addAll(commentedItemIds); + + return unionItemIds.size(); + } + + @Override public Map countUsersRatedEachItem(final Long contentId, final Collection itemIds, Integer excludeUserId) { @@ -207,6 +276,43 @@ .createQuery(FIND_ITEMID_USERID_COMMENT_PAIRS_BY_CONTENT_AND_ITEMS).setLong("contentId", contentId) .setParameterList("itemIds", itemIds).list(); + return createUsersRatedEachItem(itemIds, excludeUserId, itemIdToRatedUsersCountMap, ratedItemObjs, + commentedItemObjs); + } + + @Override + public Map countUsersRatedEachItemByCriteria(final Long criteriaId, final Collection itemIds, + Integer excludeUserId) { + + HashMap itemIdToRatedUsersCountMap = new HashMap(); + if (itemIds.isEmpty()) { + return itemIdToRatedUsersCountMap; + } + + // unions don't work in HQL so doing 2 separate DB queries (http://stackoverflow.com/a/3940445) + String FIND_ITEMID_USERID_PAIRS_BY_CONTENT_AND_ITEMS = "SELECT r.itemId, r.learner.userId FROM " + + Rating.class.getName() + + " AS r where r.ratingCriteria.ratingCriteriaId=:criteriaId AND r.itemId IN (:itemIds)"; + + String FIND_ITEMID_USERID_COMMENT_PAIRS_BY_CONTENT_AND_ITEMS = "SELECT comment.itemId, comment.learner.userId FROM " + + RatingComment.class.getName() + + " AS r where comment.ratingCriteria.ratingStyle=0 AND comment.ratingCriteria.ratingCriteriaId=:criteriaId " + + " AND comment.itemId IN (:itemIds) AND comment.ratingCriteria.commentsEnabled IS TRUE"; + + List ratedItemObjs = getSession().createQuery(FIND_ITEMID_USERID_PAIRS_BY_CONTENT_AND_ITEMS) + .setLong("criteriaId", criteriaId).setParameterList("itemIds", itemIds).list(); + + List commentedItemObjs = getSession() + .createQuery(FIND_ITEMID_USERID_COMMENT_PAIRS_BY_CONTENT_AND_ITEMS).setLong("criteriaId", criteriaId) + .setParameterList("itemIds", itemIds).list(); + + return createUsersRatedEachItem(itemIds, excludeUserId, itemIdToRatedUsersCountMap, ratedItemObjs, + commentedItemObjs); + } + + private Map createUsersRatedEachItem(final Collection itemIds, Integer excludeUserId, + HashMap itemIdToRatedUsersCountMap, List ratedItemObjs, + List commentedItemObjs) { for (Long itemId : itemIds) { HashSet userIds = new HashSet(); @@ -239,5 +345,16 @@ return itemIdToRatedUsersCountMap; } - + /** + * Used by tools to get the ratings and comments relating to their items. To be used within SQL and supply the toolContentId as :toolContentId. + * If getAllValues == true then returns data for all users (monitoring), otherwise just the data for a single user. + * See Peer Review for example usage. + */ + public String getRatingSelectJoinSQL(Integer ratingStyle, boolean getByUser){ + if ( ratingStyle == RatingCriteria.RATING_STYLE_COMMENT ) + return getByUser ? TOOL_SELECT_LEFT_JOIN_BY_USER_COMMENT : TOOL_SELECT_LEFT_JOIN_FOR_USER_COMMENT; + else + return getByUser ? TOOL_SELECT_LEFT_JOIN_BY_USER_STANDARD : TOOL_SELECT_LEFT_JOIN_FOR_USER_STANDARD; + } + } Index: lams_common/src/java/org/lamsfoundation/lams/rating/dto/StyledCriteriaRatingDTO.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/rating/dto/StyledCriteriaRatingDTO.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dto/StyledCriteriaRatingDTO.java (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -0,0 +1,84 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.lams.rating.dto; + +import java.util.List; + +import org.lamsfoundation.lams.rating.model.RatingCriteria; + +/** + * Use in conjunction with the new AuthoringRatingAllStyleCriteria tag to manage a mix of stars/hedging/ranking, as used in Peer Review. + * + * @author Fiona Malikoff + * + */ +public class StyledCriteriaRatingDTO { + + //common properties + private RatingCriteria ratingCriteria; + + // all the ratings done by the current user, for a set of items ids. + private List ratingDtos; + + private String justificationComment; + private Integer countRatedItems; + + public StyledCriteriaRatingDTO() { + } + + public RatingCriteria getRatingCriteria() { + return ratingCriteria; + } + + public void setRatingCriteria(RatingCriteria ratingCriteria) { + this.ratingCriteria = ratingCriteria; + } + + public List getRatingDtos() { + return ratingDtos; + } + + public void setRatingDtos(List ratingDtos) { + this.ratingDtos = ratingDtos; + } + + public String getJustificationComment() { + return justificationComment; + } + + public void setJustificationComment(String justificationComment) { + this.justificationComment = justificationComment; + } + + public Integer getCountRatedItems() { + return countRatedItems; + } + + public void setCountRatedItems(Integer countRatedItems) { + this.countRatedItems = countRatedItems; + } + + + +} Index: lams_common/src/java/org/lamsfoundation/lams/rating/dto/StyledRatingDTO.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/rating/dto/StyledRatingDTO.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/rating/dto/StyledRatingDTO.java (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -0,0 +1,98 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.lams.rating.dto; + + +/** + * Use in conjunction with the new AuthoringRatingAllStyleCriteria tag to manage a mix of stars/hedging/ranking, as used in Peer Review. + * + * @author Fiona Malikoff + * + */ +public class StyledRatingDTO { + + private Long itemId; + private String itemDescription; // user readable version of the item, usually the name of the learner that has been rated\ + + private String userRating; // rating left by the current user + private String averageRating; // average of ratings by all users + private String numberOfVotes; // number of "all users" used to calculate average + + private String comment; // comment left by the current user + + public StyledRatingDTO(Long itemId) { + this.itemId = itemId; + } + + public Long getItemId() { + return itemId; + } + + public void setItemId(Long itemId) { + this.itemId = itemId; + } + + public String getItemDescription() { + return itemDescription; + } + + public void setItemDescription(String itemDescription) { + this.itemDescription = itemDescription; + } + + public String getUserRating() { + return userRating; + } + + public void setUserRating(String userRating) { + this.userRating = userRating; + } + + public String getAverageRating() { + return averageRating; + } + + public void setAverageRating(String averageRating) { + this.averageRating = averageRating; + } + + public String getNumberOfVotes() { + return numberOfVotes; + } + + public void setNumberOfVotes(String numberOfVotes) { + this.numberOfVotes = numberOfVotes; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + + +} Index: lams_common/src/java/org/lamsfoundation/lams/rating/model/Rating.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/model/Rating.java (.../Rating.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/model/Rating.java (.../Rating.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -41,7 +41,7 @@ private User learner; - private float rating; + private Float rating; public Rating() { } @@ -95,11 +95,11 @@ /** */ - public void setRating(float rating) { + public void setRating(Float rating) { this.rating = rating; } - public float getRating() { + public Float getRating() { return this.rating; } } Index: lams_common/src/java/org/lamsfoundation/lams/rating/model/RatingCriteria.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/model/RatingCriteria.java (.../RatingCriteria.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/model/RatingCriteria.java (.../RatingCriteria.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -35,6 +35,24 @@ /** * Base class for all RatingCriterias. If you add another subclass, you must update * RatingCriteriaDAO.getRatingCriteriaByRatingCriteriaId() and add a ACTIVITY_TYPE constant. + * + * Criteria Styles: + * + * Comment: This supports the old style Peer Review comments, with every reviewer leaving one comment for each item/user + * being reviewed for a set of (star) criteria. + * Not support in the in Peer Review once it supports Ranking, Hedging, etc but kept for compatibility with existing data. + * Could be included in new style Peer Review with user interface changes. + * + * Star: The original style of comment, with up to 5 stars being used. For every star criteria, each reviewer leaves + * one star rating (in lams_rating) for each item/user and possibly one comment (in lams_rating_comment) for each item/user. + * + * Ranking: Ordering of the top 1, top 2, top 3, top 4, top 5 or all items/users. For every ranking criteria, each reviewer leaves + * one ranking (in lams_rating) for [ 1/2/3/4/5/all ] items/users and no comments. + * + * Hedging: Allocating out marks across items/users. For every hedging criteria, each reviewer leaves + * one ranking (in lams_rating) for 1 or more items/users and 0 or 1 justification comment overall (in lams_rating_comment). + * That is, for Hedging a reviewer makes one justification comment, whereas in Star the reviewer makes one comment for each user/item being reviewed. + * */ public abstract class RatingCriteria implements Serializable, Nullable, Comparable, Cloneable { @@ -65,6 +83,21 @@ public static final String I18N_DESCRIPTION = "rating.criteria.description"; public static final String I18N_HELP_TEXT = "rating.criteria.helptext"; + /** What style to use for doing the rating? Stored in Int member + * If Style = RATING_STYLE_COMMENT then the entry is for a comment criteria, with the data in RatingComment. + * If Style = RATING_STYLE_STAR then maxRating is 5, which lines up with the number of stars and comments are allowed. + * If Style = RATING_STYLE_RANKING then maxRating will be 1 through 5 or -1 for rank all. + * If Style = RATING_STYLE_HEDGING then maxRating contains the sum total mark to which the hedged ratings should add up. + * The comments table is also used to store the rating justification for hedging. */ + public static final int RATING_STYLE_COMMENT = 0; + public static final int RATING_STYLE_STAR = 1; + public static final int RATING_STYLE_RANKING = 2; + public static final int RATING_STYLE_HEDGING = 3; + + public static final int RATING_STYLE_STAR_DEFAULT_MAX = 5; + public static final int RATING_STYLE_RANKING_DEFAULT_MAX = 5; + public static final int RATING_STYLE_RANKING_RANK_ALL = 0; + // --------------------------------------------------------------------- // Instance variables // --------------------------------------------------------------------- @@ -83,10 +116,19 @@ /** The type of ratingCriteria */ private Integer ratingCriteriaTypeId; - private boolean commentsEnabled; + private boolean commentsEnabled; // comments for RATING_STYLE_COMMENT, RATING_STYLE_STAR justification for RATING_STYLE_HEDGING private int commentsMinWordsLimit; + private Integer ratingStyle; // see comments above for RATING_STYLE + + private Integer maxRating; // see comments above for RATING_STYLE + + private Integer minimumRates; // Minimum number of people for whom one user may rate this criteria. Used for RATING_STYLE_STAR. + + private Integer maximumRates; // Minimum number of people for whom one user may rate this criteria. Used for RATING_STYLE_STAR. + + // --------------------------------------------------------------------- // Object constructors // --------------------------------------------------------------------- @@ -97,22 +139,34 @@ /** full constructor */ public RatingCriteria(Long ratingCriteriaId, String title, Integer orderId, Date createDateTime, - Integer ratingCriteriaTypeId) { + Integer ratingCriteriaTypeId, Integer ratingStyle, Integer maxRating, Integer minimumRates, Integer maximumRates) { this.ratingCriteriaId = ratingCriteriaId; this.title = title; this.orderId = orderId; this.ratingCriteriaTypeId = ratingCriteriaTypeId; + this.ratingStyle = ratingStyle; + this.maxRating = maxRating; + this.minimumRates = minimumRates; + this.maximumRates = maximumRates; } /** default constructor */ public RatingCriteria() { + this.ratingStyle = RATING_STYLE_STAR; + this.maxRating = RATING_STYLE_STAR_DEFAULT_MAX; + this.minimumRates = 0; + this.maximumRates = 0; } /** minimal constructor */ public RatingCriteria(Long ratingCriteriaId, Date createDateTime, RatingCriteria parentRatingCriteria, Integer ratingCriteriaTypeId) { this.ratingCriteriaId = ratingCriteriaId; this.ratingCriteriaTypeId = ratingCriteriaTypeId; + this.ratingStyle = RATING_STYLE_STAR; + this.maxRating = RATING_STYLE_STAR_DEFAULT_MAX; + this.minimumRates = 0; + this.maximumRates = 0; } public static RatingCriteria getRatingCriteriaInstance(int ratingCriteriaType) { @@ -246,9 +300,72 @@ } // --------------------------------------------------------------------- + // RatingStyle checking methods + // --------------------------------------------------------------------- + /** + * Is it a Comment? + */ + public boolean isCommentRating() { + return getRatingStyle().intValue() == RatingCriteria.RATING_STYLE_COMMENT; + } + + /** + * Is it a Star Rating? + */ + public boolean isStarStyleRating() { + return getRatingStyle().intValue() == RatingCriteria.RATING_STYLE_STAR; + } + + /** + * Is it a Ranking? + */ + public boolean isRankingStyleRating() { + return getRatingStyle().intValue() == RatingCriteria.RATING_STYLE_RANKING; + } + + /** + * Is it a Hedging Mark? + */ + public boolean isHedgeStyleRating() { + return getRatingStyle().intValue() == RatingCriteria.RATING_STYLE_HEDGING; + } + + // --------------------------------------------------------------------- // Data Transfer object creation methods // --------------------------------------------------------------------- + public Integer getRatingStyle() { + return ratingStyle; + } + + public void setRatingStyle(Integer ratingStyle) { + this.ratingStyle = ratingStyle; + } + + public Integer getMaxRating() { + return maxRating; + } + + public void setMaxRating(Integer maxRating) { + this.maxRating = maxRating; + } + + public Integer getMinimumRates() { + return minimumRates; + } + + public void setMinimumRates(Integer minimumRates) { + this.minimumRates = minimumRates; + } + + public Integer getMaximumRates() { + return maximumRates; + } + + public void setMaximumRates(Integer maximumRates) { + this.maximumRates = maximumRates; + } + @Override public Object clone() { RatingCriteria criteria = null; Index: lams_common/src/java/org/lamsfoundation/lams/rating/service/IRatingService.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/service/IRatingService.java (.../IRatingService.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/service/IRatingService.java (.../IRatingService.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -31,14 +31,24 @@ import javax.servlet.http.HttpServletRequest; +import org.apache.tomcat.util.json.JSONArray; +import org.apache.tomcat.util.json.JSONException; import org.lamsfoundation.lams.rating.dto.ItemRatingCriteriaDTO; import org.lamsfoundation.lams.rating.dto.ItemRatingDTO; +import org.lamsfoundation.lams.rating.dto.StyledCriteriaRatingDTO; import org.lamsfoundation.lams.rating.model.Rating; import org.lamsfoundation.lams.rating.model.RatingCriteria; public interface IRatingService { void saveOrUpdateRating(Rating rating); + + /** + * Save a group of ratings as the new ratings for this criteria, marking any existing ratings NULL. + * Returns the number of "real" ratings, which should be newRatings.size. + * @return + */ + public int rateItems(RatingCriteria ratingCriteria, Integer userId, Map newRatings); /** * Read modified rating criterias from request, then update existing ones/add new ones/delete removed ones. Used on @@ -117,7 +127,30 @@ */ ItemRatingDTO getRatingCriteriaDtoWithActualRatings(Long contentId, Long itemId); + /** + * Used by tools to get the ratings and comments relating to their items. To be used within SQL and supply the toolContentId as :toolContentId, + * criteria id as :ratingCriteriaId and current user id as :userId + * If getByUser == true then returns data for all users, as left by the current user, otherwise gives the data for the current user as left by other users + * See Peer Review for example usage. + */ + String getRatingSelectJoinSQL(Integer ratingStyle, boolean getByUser); + + /** + * Convert the raw data from the database to StyledCriteriaRatingDTO and StyleRatingDTO. The rating service expects its own fields + * to be first in each Object array, and the last item in the array to be an item description (eg formatted user's name) + * Will go back to the database for the justification comment that would apply to hedging. + */ + StyledCriteriaRatingDTO convertToStyledDTO(RatingCriteria ratingCriteria, Long currentUser, boolean includeCurrentUser, List rawDataRows); + + /** + * Convert the raw data from the database to JSON rows similar to StyleRatingDTO. The rating service expects its own fields + * to be first in each Object array, and the last item in the array to be an item description (eg formatted user's name) + * Will go back to the database for the justification comment that would apply to hedging. + */ + JSONArray convertToStyledJSON(RatingCriteria ratingCriteria, Long currentUser, boolean includeCurrentUser, List rawDataRows, boolean needRatesPerUser) throws JSONException; + + /** * Returns number of images rated by specified user in a current activity. It counts comments as ratings. This * method is applicable only for RatingCriterias of LEARNER_ITEM_CRITERIA_TYPE type. * @@ -128,6 +161,16 @@ int getCountItemsRatedByUser(final Long toolContentId, final Integer userId); /** + * Returns number of items rated by specified user in a current activity, for a particular criteria. It counts comments as ratings + * iff it is a comment rating. This method is applicable only for RatingCriterias of LEARNER_ITEM_CRITERIA_TYPE type. + * + * @param toolContentId + * @param userId + * @return + */ + int getCountItemsRatedByUserByCriteria(final Long criteriaId, final Integer userId); + + /** * Count how many users rated and commented each item. * * @param contentId @@ -137,5 +180,8 @@ */ Map countUsersRatedEachItem(final Long contentId, final Collection itemIds, Integer excludeUserId); + + Map countUsersRatedEachItemByCriteria(final Long criteriaId, final Collection itemIds, + Integer excludeUserId); } Index: lams_common/src/java/org/lamsfoundation/lams/rating/service/RatingService.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r42cd8d7da9fa6615dbd82d376984000024b4b0fe --- lams_common/src/java/org/lamsfoundation/lams/rating/service/RatingService.java (.../RatingService.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/rating/service/RatingService.java (.../RatingService.java) (revision 42cd8d7da9fa6615dbd82d376984000024b4b0fe) @@ -25,6 +25,7 @@ package org.lamsfoundation.lams.rating.service; +import java.math.BigInteger; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; @@ -39,17 +40,23 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import org.apache.tomcat.util.json.JSONArray; +import org.apache.tomcat.util.json.JSONException; +import org.apache.tomcat.util.json.JSONObject; import org.lamsfoundation.lams.rating.dao.IRatingCommentDAO; import org.lamsfoundation.lams.rating.dao.IRatingCriteriaDAO; import org.lamsfoundation.lams.rating.dao.IRatingDAO; import org.lamsfoundation.lams.rating.dto.ItemRatingCriteriaDTO; import org.lamsfoundation.lams.rating.dto.ItemRatingDTO; import org.lamsfoundation.lams.rating.dto.RatingCommentDTO; import org.lamsfoundation.lams.rating.dto.RatingDTO; +import org.lamsfoundation.lams.rating.dto.StyledCriteriaRatingDTO; +import org.lamsfoundation.lams.rating.dto.StyledRatingDTO; import org.lamsfoundation.lams.rating.model.LearnerItemRatingCriteria; import org.lamsfoundation.lams.rating.model.Rating; import org.lamsfoundation.lams.rating.model.RatingComment; import org.lamsfoundation.lams.rating.model.RatingCriteria; +import org.lamsfoundation.lams.rating.model.ToolActivityRatingCriteria; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.util.MessageService; @@ -85,19 +92,31 @@ } @Override + public int getCountItemsRatedByUserByCriteria(final Long criteriaId, final Integer userId) { + return ratingDAO.getCountItemsRatedByUserByCriteria(criteriaId, userId); + } + + @Override public Map countUsersRatedEachItem(final Long contentId, final Collection itemIds, Integer excludeUserId) { return ratingDAO.countUsersRatedEachItem(contentId, itemIds, excludeUserId); } @Override + public Map countUsersRatedEachItemByCriteria(final Long criteriaId, final Collection itemIds, + Integer excludeUserId) { + return ratingDAO.countUsersRatedEachItemByCriteria(criteriaId, itemIds, excludeUserId); + } + + @Override public void saveOrUpdateRating(Rating rating) { ratingDAO.saveOrUpdate(rating); } @Override public ItemRatingCriteriaDTO rateItem(RatingCriteria ratingCriteria, Integer userId, Long itemId, float ratingFloat) { + Long ratingCriteriaId = ratingCriteria.getRatingCriteriaId(); Rating rating = ratingDAO.getRating(ratingCriteriaId, userId, itemId); @@ -120,6 +139,38 @@ } @Override + public int rateItems(RatingCriteria ratingCriteria, Integer userId, Map newRatings) { + + User learner = (User) userManagementService.findById(User.class, userId); + int numRatings = 0; + + List dbRatings = ratingDAO.getRatingsByUserCriteria(ratingCriteria.getRatingCriteriaId(), userId); + for ( Rating rating: dbRatings ) { + Long itemId = rating.getItemId(); + Float newRating = newRatings.get(itemId); + if ( newRating != null ) { + rating.setRating(newRating); + newRatings.remove(itemId); + numRatings++; + ratingDAO.saveOrUpdate(rating); + } else { + rating.setRating((Float)null); + } + } + for ( Map.Entry entry : newRatings.entrySet() ) { + Rating rating = new Rating(); + rating.setItemId(entry.getKey()); + rating.setLearner(learner); + rating.setRatingCriteria(ratingCriteria); + rating.setRating(entry.getValue()); + ratingDAO.saveOrUpdate(rating); + numRatings++; + } + + return numRatings; + } + + @Override public void commentItem(RatingCriteria ratingCriteria, Integer userId, Long itemId, String comment) { RatingComment ratingComment = ratingCommentDAO.getComment(ratingCriteria.getRatingCriteriaId(), userId, itemId); @@ -174,7 +225,7 @@ Long criteriaId = criteria.getRatingCriteriaId(); //comments' criteria are handled earlier, at the beginning of this function - if (criteria.isCommentsEnabled()) { + if (criteria.getRatingStyle() == 0) { continue; } @@ -226,17 +277,11 @@ boolean isSingleItem = itemIds.size() == 1; Long singleItemId = isSingleItem ? itemIds.iterator().next() : null; - // initialize itemDtos - List itemDtos = new LinkedList(); - for (Long itemId : itemIds) { - ItemRatingDTO itemDto = new ItemRatingDTO(); - itemDto.setItemId(itemId); - itemDtos.add(itemDto); - } + List itemDtos = initializeItemDtos(itemIds); //handle comments criteria for (RatingCriteria criteria : criterias) { - if (criteria.isCommentsEnabled()) { + if (criteria.isCommentRating()) { Long commentCriteriaId = criteria.getRatingCriteriaId(); List commentDtos; @@ -281,6 +326,17 @@ return itemDtos; } + private List initializeItemDtos(Collection itemIds) { + // initialize itemDtos + List itemDtos = new LinkedList(); + for (Long itemId : itemIds) { + ItemRatingDTO itemDto = new ItemRatingDTO(); + itemDto.setItemId(itemId); + itemDtos.add(itemDto); + } + return itemDtos; + } + @Override public ItemRatingDTO getRatingCriteriaDtoWithActualRatings(Long contentId, Long itemId) { @@ -305,7 +361,7 @@ Long criteriaId = criteria.getRatingCriteriaId(); // comments' criteria are handled earlier, at the beginning of this function - if (criteria.isCommentsEnabled()) { + if (criteria.isCommentRating()) { continue; } @@ -332,6 +388,7 @@ return itemDto; } + @Override public List getCriteriasByToolContentId(Long toolContentId) { List criterias = ratingCriteriaDAO.getByToolContentId(toolContentId); @@ -357,12 +414,44 @@ mapOrderIdToRatingCriteria.put(ratingCriteriaIter.getOrderId(), ratingCriteriaIter); } + + for ( Map.Entry entry : request.getParameterMap().entrySet()) { + log.debug("entry: "+entry.getKey()+" "+entry.getValue()); + } + int criteriaMaxOrderId = WebUtil.readIntParam(request, "criteriaMaxOrderId"); // i is equal to an old orderId for (int i = 1; i <= criteriaMaxOrderId; i++) { String criteriaTitle = WebUtil.readStrParam(request, "criteriaTitle" + i, true); + Integer ratingStyle = WebUtil.readIntParam(request, "ratingStyle" + i, true); + if ( ratingStyle == null ) + ratingStyle = RatingCriteria.RATING_STYLE_STAR; + + Integer maxRating = WebUtil.readIntParam(request, "maxRating" + i, true); + if (maxRating == null) { + switch (ratingStyle) { + case RatingCriteria.RATING_STYLE_STAR: + maxRating = RatingCriteria.RATING_STYLE_STAR_DEFAULT_MAX; + break; + case RatingCriteria.RATING_STYLE_RANKING: + maxRating = RatingCriteria.RATING_STYLE_RANKING_DEFAULT_MAX; + break; + case RatingCriteria.RATING_STYLE_HEDGING: + maxRating = 0; + break; + } + } + + Integer minRatings = 0; + Integer maxRatings = 0; + if ( ratingStyle == RatingCriteria.RATING_STYLE_STAR ) { + minRatings = WebUtil.readIntParam(request, "minimumRates" + i, true); + maxRatings = WebUtil.readIntParam(request, "maximumRates" + i, true); + } + boolean commentsEnabled = ( ratingStyle != RatingCriteria.RATING_STYLE_COMMENT ? WebUtil.readBooleanParam(request, "enableComments" + i, false) : false ); + RatingCriteria ratingCriteria = mapOrderIdToRatingCriteria.get(i); if (StringUtils.isNotBlank(criteriaTitle)) { int newCriteriaOrderId = WebUtil.readIntParam(request, "criteriaOrderId" + i); @@ -376,6 +465,19 @@ ratingCriteria.setOrderId(newCriteriaOrderId); ratingCriteria.setTitle(criteriaTitle); + ratingCriteria.setRatingStyle(ratingStyle); + ratingCriteria.setMaxRating(maxRating); + ratingCriteria.setCommentsEnabled(commentsEnabled); + if ( commentsEnabled ) { + Integer commentsMinWordsLimit = WebUtil.readIntParam(request, "commentsMinWordsLimit" + i, true); + ratingCriteria.setCommentsMinWordsLimit(commentsMinWordsLimit != null ? commentsMinWordsLimit : 0); + } else { + ratingCriteria.setCommentsMinWordsLimit(0); + } + + ratingCriteria.setMinimumRates( minRatings ); + ratingCriteria.setMaximumRates( maxRatings ); + ratingCriteriaDAO.saveOrUpdate(ratingCriteria); // !!updatedCriterias.add(ratingCriteria); @@ -392,7 +494,7 @@ // find comments' responsible RatingCriteria RatingCriteria commentsResponsibleCriteria = null; for (RatingCriteria ratingCriteriaIter : oldCriterias) { - if (ratingCriteriaIter.isCommentsEnabled()) { + if (ratingCriteriaIter.isCommentRating()) { commentsResponsibleCriteria = ratingCriteriaIter; break; } @@ -405,6 +507,7 @@ ((LearnerItemRatingCriteria) commentsResponsibleCriteria).setToolContentId(toolContentId); commentsResponsibleCriteria.setOrderId(0); commentsResponsibleCriteria.setCommentsEnabled(true); + commentsResponsibleCriteria.setRatingStyle(RatingCriteria.RATING_STYLE_COMMENT); } int commentsMinWordsLimit = WebUtil.readIntParam(request, "commentsMinWordsLimit"); @@ -420,7 +523,163 @@ } } + /** + * Convert the raw data from the database to StyledCriteriaRatingDTO and StyleRatingDTO. The rating service expects + * the potential itemId followed by rating.* (its own fields) and the last item in the array to be an item + * description (eg formatted user's name) Will go back to the database for the justification comment that would + * apply to hedging. + * + * If includeCurrentUser == true will include the current users' records (used for SelfReview and Monitoring) + * otherwise skips the current user, so they do not rate themselves! + * + * Entries in Object array for comment style: + * tool item id (usually user id), rating.item_id, rating_comment.comment, (tool fields)+ + * Entries in Object array for other styles: + * tool item id (usually user id), rating.item_id, rating_comment.comment, + * rating.rating, calculated.average_rating, calculated.count_vote, (tool fields)+ + */ @Override + public StyledCriteriaRatingDTO convertToStyledDTO(RatingCriteria ratingCriteria, Long currentUserId, boolean includeCurrentUser, + List rawDataRows) { + + StyledCriteriaRatingDTO criteriaDto = new StyledCriteriaRatingDTO(); + criteriaDto.setRatingCriteria(ratingCriteria); + + if (ratingCriteria.isHedgeStyleRating() && ratingCriteria.isCommentsEnabled() ) { + RatingComment justification = ratingCommentDAO.getComment(ratingCriteria.getRatingCriteriaId(), + currentUserId.intValue(), ratingCriteria.getRatingCriteriaId()); + if (justification != null) + criteriaDto.setJustificationComment(justification.getComment()); + } else if ( ratingCriteria.isStarStyleRating() ) { + int ratedCount = getCountItemsRatedByUserByCriteria(ratingCriteria.getRatingCriteriaId(), currentUserId.intValue()); + criteriaDto.setCountRatedItems(ratedCount); + } + + boolean isComment = ratingCriteria.isCommentRating(); + if ( rawDataRows != null ) { + + List ratingDtos = new ArrayList(); + criteriaDto.setRatingDtos(ratingDtos); + + + NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + numberFormat.setMaximumFractionDigits(1); + + for (Object[] row : rawDataRows) { + int numColumns = row.length; + if ( ( isComment && numColumns < 4 ) || ( !isComment && numColumns < 7) ) { + log.error("convertToStyledDTO: ratingCriteria" + ratingCriteria.getRatingCriteriaId() + " UserId: " + + currentUserId + " Skipping data row as there are not enough columns. Only " + numColumns + + " columns. Data:" + row); + break; + } + + long itemId = ((BigInteger) row[0]).longValue(); + if ( includeCurrentUser || itemId != currentUserId) { + + if (row[1] != null && row[0] != row[1]) { + log.error("convertToStyledDTO: ratingCriteria" + ratingCriteria.getRatingCriteriaId() + " UserId: " + + currentUserId + " Potential issue: expected item id " + row[0] + " does match real item id" + + row[1] + ". Data: " + row); + } + + StyledRatingDTO dto = new StyledRatingDTO(((BigInteger) row[0]).longValue()); + dto.setComment((String) row[2]); + dto.setItemDescription((String) row[numColumns - 1]); + if ( ! isComment ) { + dto.setUserRating(row[3] == null ? "" : numberFormat.format((Float) row[3])); + dto.setAverageRating(row[4] == null ? "" : numberFormat.format((Double) row[4])); + dto.setNumberOfVotes(row[5] == null ? "" : numberFormat.format((BigInteger) row[5])); + } + ratingDtos.add(dto); + } + } + } + + return criteriaDto; + } + + /** + * Convert the raw data from the database to JSON, similar on StyledCriteriaRatingDTO and StyleRatingDTO. + * The rating service expects the potential itemId followed by rating.* (its own fields) and the last item + * in the array to be an item description (eg formatted user's name) Will go back to the database for the + * justification comment that would apply to hedging. + * + * If includeCurrentUser == true will include the current users' records (used for SelfReview and Monitoring) + * otherwise skips the current user, so they do not rate themselves! + * + * In JSON for use with tablesorter. + * Entries in Object array for comment style: + * tool item id (usually user id), rating.item_id, rating_comment.comment, (tool fields)+ + * Entries in Object array for other styles: + * tool item id (usually user id), rating.item_id, rating_comment.comment, + * rating.rating, calculated.average_rating, calculated.count_vote, (tool fields)+ + * @throws JSONException + */ + @Override + public JSONArray convertToStyledJSON(RatingCriteria ratingCriteria, Long currentUserId, boolean includeCurrentUser, + List rawDataRows, boolean needRatesPerUser) throws JSONException { + + JSONArray rows = new JSONArray(); + boolean isComment = ratingCriteria.isCommentRating(); + + if ( rawDataRows != null ) { + + List itemIds = needRatesPerUser ? new ArrayList(rawDataRows.size()) : null; + + NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + numberFormat.setMaximumFractionDigits(1); + + for (Object[] row : rawDataRows) { + int numColumns = row.length; + if ( ( isComment && numColumns < 4 ) || ( !isComment && numColumns < 7) ) { + log.error("convertToStyledDTO: ratingCriteria" + ratingCriteria.getRatingCriteriaId() + " UserId: " + + currentUserId + " Skipping data row as there are not enough columns. Only " + numColumns + + " columns. Data:" + row); + break; + } + + Long itemId = ((BigInteger) row[0]).longValue(); + if ( includeCurrentUser || itemId != currentUserId) { + + if (row[1] != null && row[0] != row[1]) { + log.error("convertToStyledDTO: ratingCriteria" + ratingCriteria.getRatingCriteriaId() + " UserId: " + + currentUserId + " Potential issue: expected item id " + row[0] + " does match real item id" + + row[1] + ". Data: " + row); + } + + JSONObject userRow = new JSONObject(); + userRow.put("itemId", itemId); + userRow.put("comment", row[2] == null ? "" : (String) row[2]); + userRow.put("itemDescription", row[numColumns - 1] == null ? "" : (String) row[numColumns - 1]); + if ( ! isComment ) { + userRow.put("userRating", row[3] == null ? "" : numberFormat.format((Float) row[3])); + userRow.put("averageRating", row[4] == null ? "" : numberFormat.format((Double) row[4])); + userRow.put("numberOfVotes", row[5] == null ? "" : numberFormat.format((BigInteger) row[5])); + } + + rows.put(userRow); + + if ( needRatesPerUser ) + itemIds.add(itemId); + } + } + + if ( needRatesPerUser ) { + Map countUsersRatedEachItemMap = ratingDAO.countUsersRatedEachItemByCriteria(ratingCriteria.getRatingCriteriaId(), itemIds, -1); + for ( int i=0; i < rows.length(); i++ ) { + JSONObject row = rows.getJSONObject(i); + Long count = countUsersRatedEachItemMap.get(row.get("itemId")); + row.put("ratesPerUser", count != null ? count : 0); + } + } + } + + return rows; + + } + + @Override public boolean isCommentsEnabled(Long toolContentId) { return ratingCriteriaDAO.isCommentsEnabledForToolContent(toolContentId); } @@ -430,6 +689,10 @@ return ratingCriteriaDAO.getCommentsMinWordsLimitForToolContent(toolContentId); } + @Override + public String getRatingSelectJoinSQL(Integer ratingStyle, boolean getByUser) { + return ratingDAO.getRatingSelectJoinSQL(ratingStyle, getByUser); + } /* ********** Used by Spring to "inject" the linked objects ************* */ public void setRatingDAO(IRatingDAO ratingDAO) {