[SalesForce] CSS Star Rater on VisualForce

I'm trying to implement a star rating on VisualForce, where a user hovers over a star and the star will highlight. I'm looking at the example here: CSS Star Rater .

The things I'm having trouble with is how can I get this into VF:

<span class="star-rating">
   <input type="radio" name="rating" value="1"><i></i>
   <input type="radio" name="rating" value="2"><i></i>
   <input type="radio" name="rating" value="3"><i></i>
   <input type="radio" name="rating" value="4"><i></i>
   <input type="radio" name="rating" value="5"><i></i>
</span>

and highlighting the star.

Here is the CSS file that I have:

.star-rating{
  font-size:0;
  white-space:nowrap;
  display:inline-block;
  width:250px;
  height:50px;
  overflow:hidden;
  position:relative;
  background:
      url('data:image/svg+xml;utf-8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve"><polygon fill="#DDDDDD" points="10,0 13.09,6.583 20,7.639 15,12.764 16.18,20 10,16.583 3.82,20 5,12.764 0,7.639 6.91,6.583 "/></svg>');
  background-size: contain;
}

.star-rating input{ 
   -moz-appearance:none;
   -webkit-appearance:none;
   opacity: 0;
   display:inline-block;
   width: 20%;
   height: 100%; 
   margin:0;
   padding:0;
   z-index: 2;
   position: relative;
   &:hover + i,
   &:checked + i{
     opacity:1;    
   }
}

.star-rating i{
   opacity: 0;
   position: absolute;
   left: 0;
   top: 0;
   height: 100%;
   width: 20%;
   z-index: 1;
   background: 
      url('data:image/svg+xml;utf-8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve"><polygon fill="#FFDF88" points="10,0 13.09,6.583 20,7.639 15,12.764 16.18,20 10,16.583 3.82,20 5,12.764 0,7.639 6.91,6.583 "/></svg>');
   background-size: contain;
 }

.star-rating i ~ i{
  width: 40%;
}
.star-rating i ~ i ~ i{
  width: 60%;
}
.star-rating i ~ i ~ i ~ i{
  width: 80%;
}
.star-rating i ~ i ~ i ~ i ~ i{
  width: 100%;
}

So far, I have this VisualForce code:

<apex:page docType="html-5.0" >
<apex:stylesheet value="{!URLFOR($Resource.FeedbackStarsStyling)}"/>
<apex:outputPanel styleClass="star-rating">
    <apex:form >
         <apex:selectRadio layout="lineDirection">
            <apex:selectOption itemValue="1"></apex:selectOption>
             <i></i>
            <apex:selectOption itemValue="2"></apex:selectOption>
             <i></i>
            <apex:selectOption itemValue="3"></apex:selectOption>
             <i></i>
            <apex:selectOption itemValue="4"></apex:selectOption>
             <i></i>
            <apex:selectOption itemValue="5"></apex:selectOption>
             <i></i>
        </apex:selectRadio>
    </apex:form>    
</apex:outputPanel>  

What are the changes can I make? Or do I need to approach this entirely differently because VisualForce can't do this stuff?

Thanks!

Best Answer

Note: I created a working version and put it in to a public gist, which also demonstrates the rating text updating when a value is selected.

Here's a demo of this working in Visualforce:

Rating Demo


We can get this to work, but you have to get a bit more creative, because the framework adds extra markup to our code. There's a few hurdles you have here.

First, the CSS used is actually LESS, which means it's meant to be pre-processed (which CodePen does for you). You have to expand the &:hover i and &:checked i elements.

.star-rating input:hover + i,
.star-rating input:checked + i{
     opacity:1;    
   }

This takes us to our next problem. You see, non apex:selectOption and apex:selectOptions elements end up being all coalesced together. When you look at the source code, you end up with something like this:

<form id="j_id0:j_id6" name="j_id0:j_id6" method="post" action="/apex/fivestars" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="j_id0:j_id6" value="j_id0:j_id6">

             <i></i>
             <i></i>
             <i></i>
             <i></i>
             <i></i><fieldset style="border: none;"><table role="presentation">
    <tbody><tr>
<td>
<input type="radio" name="j_id0:j_id6:j_id7" id="j_id0:j_id6:j_id7:0" value="1"><label for="j_id0:j_id6:j_id7:0"></label></td>
<td>
<input type="radio" name="j_id0:j_id6:j_id7" id="j_id0:j_id6:j_id7:1" value="2"><label for="j_id0:j_id6:j_id7:1"></label></td>
<td>
<input type="radio" name="j_id0:j_id6:j_id7" id="j_id0:j_id6:j_id7:2" value="3"><label for="j_id0:j_id6:j_id7:2"></label></td>
<td>
<input type="radio" name="j_id0:j_id6:j_id7" id="j_id0:j_id6:j_id7:3" value="4"><label for="j_id0:j_id6:j_id7:3"></label></td>
<td>
<input type="radio" name="j_id0:j_id6:j_id7" id="j_id0:j_id6:j_id7:4" value="5"><label for="j_id0:j_id6:j_id7:4"></label></td>
    </tr>
</tbody></table></fieldset><div id="j_id0:j_id6:j_id18"></div>
</form>

As you can see, the <i> elements end up getting put next to each other, and the remainder is inside a table. The CSS just won't work as is.

However, as you can see, we now have something to work with; we can actually just make the stars using the labels.

Now, this is where things get really messy. We need to start positioning everything and doing all sorts of extra CSS fuss.

So, after we tinker around with the CSS, selecting the fieldset, td elements, and labels, we end up with the following CSS. I also had to tinker with the z-index and width values to get everything working correctly, because the elements are nested differently.

.star-rating fieldset {
  font-size:0;
  white-space:nowrap;
  display:inline-block;
  width:250px;
  height:50px;
  overflow:hidden;
  position:relative;
  background:
      url('data:image/svg+xml;utf-8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve"><polygon fill="#DDDDDD" points="10,0 13.09,6.583 20,7.639 15,12.764 16.18,20 10,16.583 3.82,20 5,12.764 0,7.639 6.91,6.583 "/></svg>');
  background-size: contain;
}

.star-rating input { 
   -moz-appearance:none;
   -webkit-appearance:none;
   opacity: 0;
   display:inline-block;
   width: 100%;
   height: 100%; 
   margin:0;
   padding:0;
   z-index: 2;
   position: relative;
}

.star-rating input:hover + label,
.star-rating input:checked + label {
     opacity:1;    
   }

.star-rating label {
   opacity: 0;
   position: absolute;
   left: 0;
   top: 0;
   height: 100%;
   width: 20%;
   z-index: 4;
   background: 
      url('data:image/svg+xml;utf-8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve"><polygon fill="#FFDF88" points="10,0 13.09,6.583 20,7.639 15,12.764 16.18,20 10,16.583 3.82,20 5,12.764 0,7.639 6.91,6.583 "/></svg>');
   background-size: contain;
 }

.star-rating td ~ td label {
  width: 40%;
  z-index: 3;  
}
.star-rating td ~ td ~ td label {
  width: 60%;
  z-index: 2;
}
.star-rating td ~ td ~ td ~ td label {
  z-index: 1;
  width: 80%;
}
.star-rating td ~ td ~ td ~ td ~ td label {
  z-index: 0;
  width: 100%;
}

And, finally, we can display our page:

<apex:outputPanel styleClass="star-rating">
    <apex:form >
         <apex:selectRadio layout="lineDirection">
            <apex:selectOption itemValue="1"></apex:selectOption>
            <apex:selectOption itemValue="2"></apex:selectOption>
            <apex:selectOption itemValue="3"></apex:selectOption>
            <apex:selectOption itemValue="4"></apex:selectOption>
            <apex:selectOption itemValue="5"></apex:selectOption>
        </apex:selectRadio>
    </apex:form>    
</apex:outputPanel> 
Related Topic