Anonymous
Domino 2.0 Rich Internet Applications with IBM Lotus Notes/Domino
You are here: Today » Making an Ajax star rating system for Domino
« My blog now runs on HTML5
Domino 2.0: new template in progress »

Making an Ajax star rating system for Domino

The rating system I've made uses an external database to collect the votes and Ajax to send the new vote and update the rating.

The HTML

The current total rating is rendered by the server:

Sub getStarRating(ds As DwSession)
    On Error Goto catch
    Dim doc As NotesDocument
    Dim iVal As Integer, iCount As Integer
    Dim iScore As Integer
    
    Set doc=ds.getdoc ' = the context document
    iVal=getIntValue(doc, "totalRating")
    iCount=getIntValue(doc, "votedNum")
    ds.add |<div><h2>Star rating</h2>|
' beware of division by zero    
    If icount>0 Then iScore=Cint(ival/iCount/5*100+0.5)        
    ds.add |<div class="sr" id="x| & doc.UniversalID & |"><b style="width:|& iScore & |%">| & iScore & |%</b></div>|
    ds.add |</div>|
    
    Goto finally
catch:
    Error Err , "getStarRating, line " & Erl & ": " & Error$
    Resume finally
finally:
End Sub

I use JavaScript to add the links to add a new rating:

G.rating={
base:'/<Computed Value>/',
init:function(){
    var m=this,o=$$('div'),i;if(G.getCookie('userunid')=='')return
    for(i=0;i<o.length;i++)if(o[i].className=='sr')m.prepare(o[i])
},
prepare:function(a){
    var m=this,i
    for(i=1;i<6;i++)G.append(a,'a',{className:'s'+i,title:i+'/5',onclick:m.click})
},
...

The complete rendered HTML:

<div><h2>Star rating</h2>
<div class="sr" id="xB8445EDBA1424DA9C12575DC004B557E">
<b style="width: 67%;">67%</b>
<a title="1/5" class="s1"></a>
<a title="2/5" class="s2"></a>
<a title="3/5" class="s3"></a>
<a title="4/5" class="s4"></a>
<a title="5/5" class="s5"></a>
</div>
</div>

This is a very ingenious way to show the total rating and the new user rating on hover, only using CSS, no JavaScript involved. It takes a while until you realize what's happening: on hover, the 'left' property is set to 0 and the width is increased to show all the stars of that link. At the same time, the z-index is changed so that the actual link goes under all others, so that the hover on them still work:

/* star rating */
.sr,.sr b,.sr a:hover{height:17px;background:url(star-rating.png)}
.sr{position:relative;width:85px}
.sr b,.sr a{display:block;position:absolute;text-indent:-4000px}
.sr b{background-position:0 -17px}
.sr a{width:17px;height:17px;z-index:2}
.sr a:hover{left:0;background-position:bottom left;z-index:1}
.s2{left:17px}
.s2:hover{width:34px}
.s3{left:34px}
.s3:hover{width:51px}
.s4{left:51px}
.s4:hover{width:68px}
.s5{left:68px}
.s5:hover{width:85px}

The Ajax call

I've extended my 'G' namespace to support Ajax and cookies:

/* G namespace */
var G={gecko:navigator.product=='Gecko',
init:function(){for(var n in G)if(G[n].init)G[n].init()},
set:function(a,b,c){for(var o in b)a[o]=b[o];if(c){for(var o in c)a.style[o]=c[o]};return a},
create:function(a,b,c,d){var o=(d||document).createElement(a);G.set(o,b);G.set(o.style,c);return o},
append:function(a,b,c,d){var o=b.tagName?b:G.create(b,c,d,a.ownerDocument);a.appendChild(o);return o},
_:function(){var w=window;return w.XMLHttpRequest?new XMLHttpRequest():w.ActiveXObject?new ActiveXObject('Microsoft.XMLHTTP'):null},
get:function(a){var r=G._();r.open('GET',a,false);r.send(null);return r},
post:function(a,b){var r=G._();r.open('POST',a,false);r.setRequestHeader('Content-Type','application/x-www-form-urlencoded');r.send(b);return r},
setCookie:function(a,b,c){var d,ex='';if(c){d=new Date();d.setFullYear(d.getFullYear()+10);ex="; expires="+d.toGMTString()};document.cookie=a+"="+escape(b)+ex+"; path=/"},
getCookie:function(a){var v=document.cookie.split(a+'=');return(v.length<2)?'':unescape(v[1].split(';')[0])}
},$=function(a){return document.getElementById(a)},$$=function(a,b){return (b||document).getElementsByTagName(a)}

And this is the Ajax call itself. It sends the information to the server agent 'exec'. This agent gives the new percentage back and the JavaScript updates the rating control accordingly:

click:function(e){
    var m=G.rating,o=e?e.target:window.event.srcElement,v=o.title.substr(0,1),q=o.parentNode.id.substr(1),ajx,v,q
    ajx=G.get(m.base+'exec?open&action=rate&num='+v+'&unid='+q+'&user='+G.getCookie('userunid'))
    v=ajx.responseText+'%'
    q=$$('b',$('x'+q))[0]
    q.innerHTML=v
    q.style.width=v
}

Server side processing of the Ajax call

I decided to have one agent 'exec' to execute all future requests that are not document-specific. In the URL, I just specify the action and the other parameters. Here's the part for the Ajax star rating:

Sub Initialize
    On Error Goto catch
    Dim q As New WebQuery
    Dim s As New NotesSession
    Dim config As notesdocument
    Dim trackingDb As NotesDatabase
    Dim view As notesview
    Dim docUnid As String, userUnid As String, rating As Integer    
    Dim sKey(1) As String
    Dim entry As NotesViewEntry
    Dim voteDoc As notesdocument
    Dim doc As NotesDocument
    
    Print "content-type:text/html"
    Select Case q.getValue("action")
    Case "rate"
        docUnid=q.getValue("unid")
        userUnid=q.getValue("user")
        rating=Cint(q.getValue("num"))
        Set doc=s.CurrentDatabase.GetDocumentByUNID(docUnid)
        Set config=getConfig
        Set trackingDb=s.GetDatabase("", config.RatingDb(0), False)
        Set view=trackingDb.GetView("vRatings")
        sKey(0)=docUnid
        sKey(1)=userUnid
        Set entry=view.GetEntryByKey(sKey,True)    
        If entry Is Nothing Then
            Set voteDoc=trackingDb.CreateDocument
            voteDoc.form="UserVote"
            voteDoc.docUnid=docUnid
            voteDoc.userUnid=userUnid
' increase the number of votes
            doc.VotedNum=getIntValue(doc, "VotedNum")+1
        Else
            Set voteDoc=entry.Document
' subtract the old value from the total
            doc.totalRating=getIntValue(doc, "totalRating")-getIntValue(voteDoc, "rating")
        End If
' save the individual vote
        voteDoc.rating=rating
        Call voteDoc.Save(True,False,True)
' add the rating to the total
        doc.totalRating=getIntValue(doc, "totalRating")+rating
        Call doc.Save(True,False,True)
' output the new percentage
        Print Cint(getIntValue(doc, "totalRating")/getIntValue(doc, "votedNum")/5*100+0.5)        
    Case Else
        Error 1, "No action defined for " & q.getValue("action")
    End Select
    
    Goto finally
catch:
    Print "Error" & Err & " in line " & Erl & ": " & Error$
    Resume finally
finally:
End Sub

Next?

You can see the code in action on the blog entry pages.

Star rating

77%

Comments

  1. 08/10/2009 22.35.38, Nicola Senno

    Nice tutorial thanks. Only one thing, it doesn't work on IE6. I found those two classes WebQuery and stringBuffer really helpfull

To add a comment, log in or register as new user. It's free and safe.