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.
Comments
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.