Tuesday, June 10, 2008

Tweaking Star Rating component of RichUI Grails plugin

RichUI plugin for Grails is easy to use with number of interesting Ajax enabled components. One of the components is Star Rating component. Core behavior of this component is good but I wanted to get a little bit more than this core behavior. This component works fine out of the box. You click on the star, go to server, update rating and update number of stars assigned. And everything is executed without reloading the whole page. But what I wanted to achieve is also to update number of votes within the same Ajax request. And this is not supported by component. I checked the code of this component and found out that this can be achieved with really only few lines of code. Actually solution is so simple that maybe it would be good if it is included into the official plugin code. And all described in this post you can of course see live on the grails tutorials site.

So to make more clear what I am talking about have a look on the picture.


Idea is that when you click on the star, number of votes is increased also without necessity to reload the whole page. And this is not possible with the current star rating component.

Right place to start searching what to change is RatingRenderer.groovy file. This class is responsible for rendering the star rating component.

Signature of method is

protected void renderTagContent(Map attrs, GroovyPageTagBody body, MarkupBuilder builder)
where attrs are attributes sent to the component from the gsp page. And after some code inspection it is clear that update is implemented using remoteFunction grails tag. To see more about remoteFunction read this post. One of the parameters to remoteFunction is also name of the element in the gsp page to update. Line of the code in RatingRenderer.groovy that accomplish this is
String link = attrs.link.replace(":class:", "r${i}-unit rater").replace(":title:", "$i").replace("update", "${id}").replace("number", "$i").replace("%3Arating%3A", "${i}")

and for us interesting part is replace("update",${id}). And id to update is automatically generated in the first line of method. So actually, all we need to be able to update any page element, not exactly only stars, is to provide alternate id instead of one automatically generated.

So all I changed is following:

String updateId = id
if (attrs.updateId) {
updateId = attrs.updateId
String link = attrs.link.replace(":class:", "r${i}-unit rater").replace(":title:", "$i").replace("update", "${updateId}").replace("number", "$i").replace("%3Arating%3A", "${i}")

This way if I provide updateId attribute to the star rating component, remote function generated by this component will update page element with provided id, otherwise it will keep original behavior.

And now, all I have to do is to use construct like this one in my gsp pages:

<div id="${tutorialid}tutorial">
<richui:rating dynamic="true" updateId="${tutorialid}tutorial" id="${tutorialid}" units="5" rating="${rating}" showCurrent="true" controller="rating" action="rate" />
${rate?.votes} votes; ${clicks} clicks

And of course previous code snippet is part of the grails template so during rendition necessary values are read from the provided model.

So only with three lines of code added to existing plugin code and one change to existing plugin code, star rating component is more powerful than the original one.

Wasn't that easy? ;)


Anonymous said...

I was tryin to implement the star rating functionality.

Jan, can you also provide the controller rating code, and how does the 'clicks' and 'vote' works ?


jan said...

I will see to describe controller functionality in the next few days.

Ken Kousen said...

Thanks! I was having exactly that problem, and your solution was very helpful.