CFC Spry Rating on RIAForge

I have uploaded the project files for CFC Spry Rating to RIAForge for your enjoyment.

It should change quite a bit in the near future as I add some more customization options.

Here is an example:

Happy Coding...

Comments
Duane's Gravatar Gary,

thanks for CFC Spry Rating. I have uploaded to a site I'm testing code on. It dipslays properly and you can click and vote. However when I come back to the same page, I am allowed to vote again. Also, how would I set it up to display the average vote (by stars) and total number of votes next to it.

Here is a link to what I am talking about. I am enclosing my code to the cfc. I am working with MSSql and had to make a few changes to get it to work. Some of the items where bombing out because the sql was case sensitive and I keep receiving an error on the script in line 2. Also ifnull had to be changed to isnull.

Thanks for your help.

**************CFC********************
<cfcomponent output="no">
<!---<cfscript>variables.dsn=request.dsn</cfscript>--->
<cfset variables.dsn = request.data_source />
<cffunction name="newWidget" access="public" output="true" returntype="void">
   <cfargument name="ratingName" type="string" required="true" hint="This should be unique">
   <cfargument name="ratingTitle" type="string" required="true" hint="Title that will appear to the user">
   <cfargument name="ratingNumber" type="numeric" required="true" hint="Max number of stars">
   <cfargument name="pathToSpryJS" type="string" required="true" hint="relative or absolute web path to Spry widget JS files">
   <cfargument name="pathToSpryCSS" type="string" required="true" hint="relative or absolute web path to Spry widget CSS files">
    <cfset rsWidget = insertOrSelect(ratingName,RatingTitle)>
    <cfset uservote = getRatingForUser(rsWidget.id,"#cftoken##cfid#")>
    <cfset averagevote = getRatingValue(rswidget.id)>
   <cfset pathtoCFC = "/"& replace(getmetadata().name,".","/","all") & ".cfc">   
   <cfsavecontent variable="body">
   <cfoutput>
   <script language="JavaScript" type="text/javascript" src="#pathToSpryJS#SpryRating.js"></script>
<link href="#pathToSpryCSS#samples.css" rel="stylesheet" type="text/css" />
<link href="#pathToSpryCSS#SpryRating.css" rel="stylesheet" type="text/css" />
<div><h3>#rsWidget.title#</h3><span id="rating#rsWidget.id#" class="ratingContainer">
       <cfloop from="1" to="#ratingNumber#" index="i">
       <span class="ratingButton"></span>
       </cfloop>
<input id="ratedElement" type="hidden" name="ratingField" value="" />
         <span class="ratingRatedMsg">Thanks for your rating!</span>
</span>
<p>&nbsp;</p>
</div><script type="text/javascript">
   var firstRating#replace(rsWidget.id,"-","_","all")# = new Spry.Widget.Rating("rating#rsWidget.id#", {allowMultipleRating:false,<cfif userVote gt 0> readOnly:true,</cfif>saveUrl: "#pathToCFC#?method=userRate",postData:"ratingId=#rsWidget.id#&rate=@@ratingValue@@",ratingValue:<cfoutput>#averageVote#</cfoutput>});
   var myObs = {};
   firstRating#replace(rsWidget.id,"-","_","all")#.addObserver(myObs);
   myObs.onServerUpdate = function(obj, req){
      var returnVal = parseFloat(req.xhRequest.responseText);
      if (!isNaN(returnVal)){
         firstRating#replace(rsWidget.id,"-","_","all")#.setValue(returnVal, true);
      }
   }
</script>
   </cfoutput>
   </cfsavecontent>
<cfoutput>#HtmlCompressFormat(body)#</cfoutput>
</cffunction>
<!--- Saves rate based on users id (default cftoken/cfid, user will have to clear out sesison cookie
to be able to vote again, you can change this to something else like user id --->
<cffunction name="userRate" access="remote" output="false" returntype="string">
   <cfargument name="ratingId" type="string" required="true">
   <cfargument name="rate" type="numeric" required="true">
   <cfargument name="userId" type="string" required="no" default="#cftoken##cfid#">
   
      <cfquery name="castVote" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
         insert into ratingsvote (ratingId,rate,userId)
         values ('#ratingid#',#rate#,'#userId#')
      </cfquery>
      <cfreturn getRatingValue(ratingid)>
</cffunction>
<!--- gets the average rating for specified ratings widget --->
<cffunction name="getRatingValue" access="remote" output="false" retuntype="string">
   <cfargument name="ratingId" type="string" required="true">
<!--- mysql specific ifnull --->
   <cfquery name="getRatingValue" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
         select isnull(AVG(rate),'0') avg_rate from ratingsvote where ratingId = '#ratingid#'
   </cfquery>
   <cfreturn getRatingValue.avg_rate >
</cffunction>
<!--- gets the users vote to --->
<cffunction name="getRatingForUser" access="remote" output="false" returntype="string">
   <cfargument name="ratingId" type="string" required="true">
   <cfargument name="userID" type="string" required="true">
   <cfquery name="getRatingForUser" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
      select rate from ratingsvote where ratingId='#ratingId#' and userId ='#userID#'
   </cfquery>
   <cfif getRatingForUser.recordcount eq 0>
      <cfset uservote = 0>
   <cfelse>
      <cfset uservote = getRatingForUser.rate>
   </cfif>
   <cfreturn uservote>
</cffunction>
<!--- helper function for the newwidget, will either get the details from the db if
exists or insert a new vote --->
<cffunction name="insertOrSelect" access="private" returntype="query" output="false">
   <cfargument name="ratingName" type="string" required="true">
   <cfargument name="ratingTitle" type="string" required="true">
   <cfquery name="getRatingId" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
      select id,title from rating where name = '#ratingName#'
   </cfquery>
   <cfif not getRatingId.recordcount>
      <cfset ratingID = createUUID()>
      <cfquery name="getRatingId" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
         insert into rating (id,title,name)
         values ('#ratingid#','#ratingTitle#','#ratingName#' )
      </cfquery>
      <cfset getRatingId = queryNew('id,title,name')>
      <cfset newrow = queryAddRow(getRatingId,1)>
      <cfset temp = querySetCell(getRatingId,'id','#ratingid#')>
      <cfset temp = querySetCell(getRatingId,'title','#ratingTitle#')>
      <cfset temp = querySetCell(getRatingId,'name','#ratingName#')>
   </cfif>
   <cfreturn getRatingId/>
</cffunction>
<cffunction name="HtmlCompressFormat" returntype="string" access="private" output="false">
   <cfargument name="sInput" type="string" required="yes">
<cfset var level = 2>
<cfsilent>
<cfscript>
/**
* Replaces a huge amount of unnecessary whitespace from your HTML code.
*
* @param sInput     HTML you wish to compress. (Required)
* @return Returns a string.
* @author Jordan Clark (&#74;&#111;&#114;&#100;&#97;&#110;&#67;&#108;&#97;&#114;&#107;&#64;&#84;&#101;&#108;&#117;&#115;&#46;&#110;&#101;&#116;)
* @version 1, November 19, 2002
*/

if( arrayLen( arguments ) GTE 2 AND isNumeric(arguments[2]))
{
level = arguments[2];
}
// just take off the useless stuff
sInput = trim(sInput);
switch(level)
{
case "3":
{
// extra compression can screw up a few little pieces of HTML, doh
sInput = reReplace( sInput, "[[:space:]]{2,}", " ", "all" );
sInput = replace( sInput, "> <", "><", "all" );
sInput = reReplace( sInput, "<!--[^>]+>", "", "all" );
break;
}
case "2":
{
sInput = reReplace( sInput, "[[:space:]]{2,}", chr( 13 ), "all" );
break;
}
case "1":
{
// only compresses after a line break
sInput = reReplace( sInput, "(" & chr( 10 ) & "|" & chr( 13 ) & ")+[[:space:]]{2,}", chr( 13 ), "all" );
break;
}
}
</cfscript>
</cfsilent>
<cfreturn sInput/>
</cffunction>
</cfcomponent>

**************MSSQL********************************
CREATE TABLE [dbo].[rating] (
[id] [varchar](45) NOT NULL default '',
[name] [varchar](255) NOT NULL default '',
[title] [varchar](255) default NULL,
CONSTRAINT [PK_rating] PRIMARY KEY
(
   [id]
)
) ON [PRIMARY]

CREATE TABLE [dbo].[ratingsvote] (
[id] [int] IDENTITY(1,1) NOT NULL,
[ratingId] [varchar](45) default NULL,
[userId] [varchar](45) default NULL,
[rate] [int] default '0',
CONSTRAINT [PK_ratingsvote] PRIMARY KEY
(
   [id]
)
) ON [PRIMARY]
# Posted By Duane | 3/13/08 9:55 AM
Duane's Gravatar I forgot to give you the sample site link to view what I'm talking about.

http://76.12.43.89/index.cfm?method=article&ar...

Click on any 'article' to get a feel for the issue.
# Posted By Duane | 3/13/08 9:57 AM
Gary Gilbert's Gravatar Hi Duane,

I just checked your site with firebug and it looks like that you have the wrong URL in the spry post I am showing a 404 so it would actually never save the votes at all.

http://76.12.43.89/cfcH149293/rating.cfc?method=us... comes up with a 404 not found.

Get that problem solved first :)

Next, since you are using the session of the user to track their vote you want your session to last a bit longer so don't use session cookies use regular old cookies that last until 2037.

Or write your own cookie on the users machine that won't expire, you can simply drop in a UUID into your cookie and save that as your userid in the database.

Hope that helps
# Posted By Gary Gilbert | 3/13/08 10:41 AM
Ryan's Gravatar Hi Gary,

How would I got about adding in some custom fields such as a comment box and a name field?
# Posted By Ryan | 3/21/08 10:16 PM
glieu's Gravatar Hi Gary,

thanks Gary...great job

I am a newbee to coldfusion. I used Duane code on MSSQL it works. I am having with trouble like Duane, when i vote and closed the browser, open the browser again, i can vote again. I guest i am use session cookies.

How do you write your own cookies?

Please help.

Many thanks
# Posted By glieu | 4/1/08 10:51 PM
Gary Gilbert's Gravatar @glieu,

Are you sure your vote is being saved in the database? Make sure you use firebug to check that your vote is being properly submitted Duane code had an error in it which made it look like he was voting but his vote never got saved. If the vote isn't saved in the database it will look like you can vote over and over.
# Posted By Gary Gilbert | 4/2/08 3:37 AM
Duane's Gravatar I got it to work like I wanted it. Again, Gary great work and thanks. I had problems getting the correct variable pathtoCFC into rating.cfc. Due to my site structure any call to a cfc require an application variable. I ended up hard coding a pathtoCFC within ratingCFC, because I couldn't get this to work properly. I also added a cookie with a unique userid, so that user could never vote twice. I modified variables.dsn to match my site dsn, and added username and password variables to the query's which my site requires. Here's my code:

<cfcomponent output="no">
<cfset variables.dsn = request.data_source />
<!--- Check to see if Cookie.ratingUserId is set, if not set a value. --->
<cfif IsDefined("Cookie.ratingUserId")>
    <cfset ratingUserId = Cookie.ratingUserId>    
<cfelse>
    <cfset ratingUserId = createUUID()>
<cfcookie name = "ratingUserId" value = "#ratingUserId#" expires = "never">
</cfif>

<cffunction name="newWidget" access="public" output="true" returntype="void">
   <cfargument name="ratingName" type="string" required="true" hint="This should be unique">
   <cfargument name="ratingTitle" type="string" required="true" hint="Title that will appear to the user">
   <cfargument name="ratingNumber" type="numeric" required="true" hint="Max number of stars">
   <cfargument name="pathToSpryJS" type="string" required="true" hint="relative or absolute web path to Spry widget JS files">
   <cfargument name="pathToSpryCSS" type="string" required="true" hint="relative or absolute web path to Spry widget CSS files">
    <cfset rsWidget = insertOrSelect(ratingName,RatingTitle)>
    <cfset uservote = getRatingForUser(rsWidget.id,"#ratingUserId#")>
    <cfset averagevote = getRatingValue(rswidget.id)>
   <!---<cfset pathtoCFC = "/"& replace(getmetadata().name,".","/","all") & ".cfc">--->
<cfset pathtoCFC = "/cfc/rating.cfc"><!--- coded specifically for my application DTH --->
   <cfsavecontent variable="body">
   <cfoutput>
   <script language="JavaScript" type="text/javascript" src="#pathToSpryJS#SpryRating.js"></script>
<link href="#pathToSpryCSS#samples.css" rel="stylesheet" type="text/css" />
<link href="#pathToSpryCSS#SpryRating.css" rel="stylesheet" type="text/css" />
<div><h3>#rsWidget.title#</h3><span id="rating#rsWidget.id#" class="ratingContainer">
       <cfloop from="1" to="#ratingNumber#" index="i">
       <span class="ratingButton"></span>
       </cfloop>
<input id="ratedElement" type="hidden" name="ratingField" value="" />
         <span class="ratingRatedMsg">Thanks for your rating!</span>
</span>
<p>&nbsp;</p>
</div><script type="text/javascript">
   var firstRating#replace(rsWidget.id,"-","_","all")# = new Spry.Widget.Rating("rating#rsWidget.id#", {allowMultipleRating:false,<cfif userVote gt 0> readOnly:true,</cfif>saveUrl: "#pathToCFC#?method=userRate",postData:"ratingId=#rsWidget.id#&rate=@@ratingValue@@",ratingValue:<cfoutput>#averageVote#</cfoutput>});
   var myObs = {};
   firstRating#replace(rsWidget.id,"-","_","all")#.addObserver(myObs);
   myObs.onServerUpdate = function(obj, req){
      var returnVal = parseFloat(req.xhRequest.responseText);
      if (!isNaN(returnVal)){
         firstRating#replace(rsWidget.id,"-","_","all")#.setValue(returnVal, true);
      }
   }
</script>
   </cfoutput>
   </cfsavecontent>
<cfoutput>#HtmlCompressFormat(body)#</cfoutput>
</cffunction>
<!--- Saves rate based on users id (default cftoken/cfid, user will have to clear out sesison cookie
to be able to vote again, you can change this to something else like user id --->
<cffunction name="userRate" access="remote" output="false" returntype="string">
   <cfargument name="ratingId" type="string" required="true">
   <cfargument name="rate" type="numeric" required="true">
   <cfargument name="userId" type="string" required="no" default="#ratingUserId#"><!--- changed from the original default="#cftoken##cfid#" --->
   
      <cfquery name="castVote" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
         insert into ratingsvote (ratingId,rate,userId)
         values ('#ratingid#',#rate#,'#userId#')
      </cfquery>
      <cfreturn getRatingValue(ratingid)>
</cffunction>
<!--- gets the average rating for specified ratings widget --->
<cffunction name="getRatingValue" access="remote" output="false" returntype="string">
   <cfargument name="ratingId" type="string" required="true">
<!--- mysql specific ifnull --->
   <cfquery name="getRatingValue" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
         select isnull(AVG(rate),'0') avg_rate from ratingsvote where ratingId = '#ratingid#'
   </cfquery>
   <cfreturn getRatingValue.avg_rate >
</cffunction>
<!--- gets the users vote to --->
<cffunction name="getRatingForUser" access="remote" output="false" returntype="string">
   <cfargument name="ratingId" type="string" required="true">
   <cfargument name="userID" type="string" required="true">
   <cfquery name="getRatingForUser" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
      select rate from ratingsvote where ratingId = '#ratingId#' and userId ='#userID#'
   </cfquery>
   <cfif getRatingForUser.recordcount eq 0>
      <cfset uservote = 0>
   <cfelse>
      <cfset uservote = getRatingForUser.rate>
   </cfif>
   <cfreturn uservote>
</cffunction>
<!--- helper function for the newwidget, will either get the details from the db if
exists or insert a new vote --->
<cffunction name="insertOrSelect" access="private" returntype="query" output="false">
   <cfargument name="ratingName" type="string" required="true">
   <cfargument name="ratingTitle" type="string" required="true">
   <cfquery name="getRatingId" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
      select id,title from rating where name = '#ratingName#'
   </cfquery>
   <cfif not getRatingId.recordcount>
      <cfset ratingID = createUUID()>
      <cfquery name="getRatingId" datasource="#variables.dsn#" username="#request.db_un#" password="#request.db_pw#">
         insert into rating (id,title,name)
         values ('#ratingid#','#ratingTitle#','#ratingName#' )
      </cfquery>
      <cfset getRatingId = queryNew('id,title,name')>
      <cfset newrow = queryAddRow(getRatingId,1)>
      <cfset temp = querySetCell(getRatingId,'id','#ratingid#')>
      <cfset temp = querySetCell(getRatingId,'title','#ratingTitle#')>
      <cfset temp = querySetCell(getRatingId,'name','#ratingName#')>
   </cfif>
   <cfreturn getRatingId/>
</cffunction>
<cffunction name="HtmlCompressFormat" returntype="string" access="private" output="false">
   <cfargument name="sInput" type="string" required="yes">
<cfset var level = 2>
<cfsilent>
<cfscript>
/**
* Replaces a huge amount of unnecessary whitespace from your HTML code.
*
* @param sInput     HTML you wish to compress. (Required)
* @return Returns a string.
* @author Jordan Clark (&#74;&#111;&#114;&#100;&#97;&#110;&#67;&#108;&#97;&#114;&#107;&#64;&#84;&#101;&#108;&#117;&#115;&#46;&#110;&#101;&#116;)
* @version 1, November 19, 2002
*/

if( arrayLen( arguments ) GTE 2 AND isNumeric(arguments[2]))
{
level = arguments[2];
}
// just take off the useless stuff
sInput = trim(sInput);
switch(level)
{
case "3":
{
// extra compression can screw up a few little pieces of HTML, doh
sInput = reReplace( sInput, "[[:space:]]{2,}", " ", "all" );
sInput = replace( sInput, "> <", "><", "all" );
sInput = reReplace( sInput, "<!--[^>]+>", "", "all" );
break;
}
case "2":
{
sInput = reReplace( sInput, "[[:space:]]{2,}", chr( 13 ), "all" );
break;
}
case "1":
{
// only compresses after a line break
sInput = reReplace( sInput, "(" & chr( 10 ) & "|" & chr( 13 ) & ")+[[:space:]]{2,}", chr( 13 ), "all" );
break;
}
}
</cfscript>
</cfsilent>
<cfreturn sInput/>
</cffunction>
</cfcomponent>
# Posted By Duane | 4/2/08 2:33 PM