3
\$\begingroup\$

I'm trying to make a star based rating system.

First of all, I don't like to make it all with JS and only innerHTML the rated stars in a given div. I want to make it more like a plugin with the ability to make anything votable and you just have to pass the classes.

My biggest problem, however, is that I'm setting classes on hover and removing them on mouse leave. That's just fine until the user clicks and the classes are set. Think you can imagine what happens if the user hovers again: The classes would get removed.

But still I want the user to be able to change his vote. I managed it with two different divs where one is hidden. I absolutely don't feel comfortable with this solution:

<div class="votable hide">
    <i class="fa fa-3x fa-star-o" data-vote-type="1"></i>
    <i class="fa fa-3x fa-star-o" data-vote-type="2"></i>
    <i class="fa fa-3x fa-star-o" data-vote-type="3"></i>
    <i class="fa fa-3x fa-star-o" data-vote-type="4"></i>
    <i class="fa fa-3x fa-star-o" data-vote-type="5"></i>
</div>
<div class="voted">
    <i class="fa fa-3x fa-star" data-vote-type="1"></i>
    <i class="fa fa-3x fa-star" data-vote-type="2"></i>
    <i class="fa fa-3x fa-star" data-vote-type="3"></i>
    <i class="fa fa-3x fa-star-o" data-vote-type="4"></i>
    <i class="fa fa-3x fa-star-o" data-vote-type="5"></i>
</div>

With jQuery I'm enabling .votable on hover. On click I'm setting the actual vote to .voted and on mouse leave I'm hiding .votable and showing .voted.

$('.voted').hover(
    function() {
        $('.voted').addClass('hide');
        $('.votable').removeClass('hide');
    }
);

$('.votable').on('mouseleave',function(){
    $('.voted').removeClass('hide');
    $('.votable').addClass('hide');
});

$('.votable > i').hover(
    function() {
        $(this).prevAll().andSelf().removeClass('fa-star-o').addClass('fa-star');
        $(this).nextAll().removeClass('fa-star').addClass('fa-star-o');
    },
    function() {
        $(this).prevAll().andSelf().removeClass('fa-star').addClass('fa-star-o');
    }
);

$('.votable > i').click(function(e) {
    var vote_type = $(this).attr('data-vote-type');
    var el = $('.voted > i[data-vote-type="' + vote_type + '"]');

    $(el).prevAll().andSelf().removeClass('fa-star-o').addClass('fa-star');
    $(el).nextAll().removeClass('fa-star').addClass('fa-star-o');
});

You can see the code working at my fiddle.

What would be a better way of achieving this?

\$\endgroup\$
1
\$\begingroup\$

About your HTML:

  • Consider using the meter element to display the rating:

    <meter min="0" max="5" value="3">★★★☆☆</meter>
    

    (However, I don’t know how current user agents present this, i.e., if they add some special styling.)

  • In any case, you should not use the i element for displaying an icon via CSS. Use span instead if you need an empty element.

  • If you don’t want to use a form for voting, consider using WAI-ARIA to make voting accessible.

\$\endgroup\$
  • \$\begingroup\$ You're right. I like to use <i> because it would perfectly fit for an icon. But it's of course intended to be used for italic. I don't know meter, I'll take a look at it. \$\endgroup\$ – sleepless Feb 21 '15 at 20:06
0
\$\begingroup\$

As an item can have both the voted and votable classes, I'd make a specific style for items that have both.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.