How to Make a Star Rating Widget – jQuery vs ReactJS vs AngularJS vs Polymer

I found a Quora article by ReactJS lead developer Pete Hunt which compares a Star Rating Widget built identically in AngularJS and ReactJS. Since I was wondering what a similar jQuery version might look like, and since I’ve done a few of these widgets before (see Rapid Platform’s ‘Choice’ component), I decided to build a lightweight jQuery version of this Star Rating Widget for comparison. Feel free to use the Star Rating Widget code in this article for your own projects.

Touch it, you know you want to:

First up, the HTML where our bare minimum template "myrating" lives...

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.js"></script>
        <script type="text/javascript" src="rating.js"></script>
    </head>
    <body>
        <div id="myrating">
            <span class="star" style="cursor:pointer;">&#9733;</span>
        </div>
    </body>
</html>

Next we have rating.js, where the jQuery component lives...


jQuery(document).ready(function() {
    choice("#myrating .star", 4, 5);
});

function choice(selector, val, max) {
    var b = jQuery(selector);
    for (var i = 0; i < max; i++) {
        var bb = b.clone();
        bb.insertAfter(b);
        bb.click(function() {
            jQuery(this).prevAll().andSelf().removeClass("off").addClass("on").css("color", "#000");
            jQuery(this).nextAll().removeClass("on").addClass("off").css("color", "#aaa");
        });
    }
    jQuery(selector + ":eq(0)").remove();
    jQuery(selector + ":eq(" + (val - 1) + ")").click();
}

function choiceVal(selector) {
    return jQuery(selector + ".on").length;
}

Live Demo

Just interact with the widgets below. Each one is a separate instance.

The three widgets above were added to this page by simply calling choice() three times with different attributes:

choice("#myrating1 .star", 3, 5);
choice("#myrating2 .star", 6, 15);
choice("#myrating3 .star", 3, 3);

But before calling choice(), we must remember to add the targeted elements to the page's HTML somewhere:

<div id="myrating1"><span class="star"style="cursor:pointer;">&#9733;</span></div>
<div id="myrating2"><span class="star"style="cursor:pointer;">&#9733;</span></div>
<div id="myrating3"><span class="star"style="cursor:pointer;">&#9733;</span></div>

Generated Markup

Let's examine what the generated markup looks like for each variation.

jQuery

<div id="myrating2">
    <span class="star on" style="cursor: pointer; color: rgb(0, 0, 0);">★</span>
    <span class="star on" style="cursor: pointer; color: rgb(0, 0, 0);">★</span>
    <span class="star on" style="cursor: pointer; color: rgb(0, 0, 0);">★</span>
    <span class="star on" style="cursor: pointer; color: rgb(0, 0, 0);">★</span>
    <span class="star on" style="cursor: pointer; color: rgb(0, 0, 0);">★</span>
    <span class="star off" style="cursor: pointer; color: rgb(170, 170, 170);">★</span>
    <span class="star off" style="cursor: pointer; color: rgb(170, 170, 170);">★</span>
    <span class="star off" style="cursor: pointer; color: rgb(170, 170, 170);">★</span>
    <span class="star off" style="cursor: pointer; color: rgb(170, 170, 170);">★</span>
    <span class="star off" style="cursor: pointer; color: rgb(170, 170, 170);">★</span>
</div>

ReactJS

<ul class="rating" data-reactid=".0.5">
    <li class="filled" data-reactid=".0.5.0">★</li>
    <li class="filled" data-reactid=".0.5.1">★</li>
    <li class="filled" data-reactid=".0.5.2">★</li>
    <li class="filled" data-reactid=".0.5.3">★</li>
    <li class="filled" data-reactid=".0.5.4">★</li>
    <li class="false" data-reactid=".0.5.5">★</li>
    <li class="false" data-reactid=".0.5.6">★</li>
    <li class="false" data-reactid=".0.5.7">★</li>
    <li class="false" data-reactid=".0.5.8">★</li>
    <li class="false" data-reactid=".0.5.9">★</li>
</ul>

AngularJS

<div fundoo-rating="" rating-value="rating" max="10" on-rating-selected="saveRatingToServer(rating)" class="ng-isolate-scope ng-scope">
    <ul class="rating">
    <!-- ngRepeat: star in stars -->
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope filled $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope filled $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope filled $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope filled $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope filled $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope $$hashKey">★</li>
        <li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)" class="ng-scope $$hashKey">★</li>
    </ul>
</div>

Polymer

<star-rating>
    #shadow-root
    <div id="starRating">
        <a href="#" class="star" index="0"></a>
        <a href="#" class="star" index="1"></a>
        <a href="#" class="star" index="2"></a>
        <a href="#" class="star" index="3"></a>
        <a href="#" class="star" index="4"></a>
        <a href="#" class="star" index="5"></a>
        <a href="#" class="star" index="6"></a>
        <a href="#" class="star" index="7"></a>
        <a href="#" class="star" index="8"></a>
        <a href="#" class="star" index="9"></a>
    </div>
    <style>
        #starRating {
            display: inline-block;
        }

        .star {
            display: inline-block;
            width: 18px;
            height: 17px;
            margin-right: 5px;
            background: url('src/images/rating.png') no-repeat 0 -17px;
        }

       .star.check {
           background-position:0 0;
       }
    </style>
</star-rating>

You'll notice ReactJS and AngularJS use proprietary language such as data-reactid, ng-click, ng-class, ng-scope and $$hashkey, which aren't readily understandable as to what they do unless you've used these frameworks before. You can guess what they do, but it's still a bit like magic.

The use of class="star on" and class="star off" for the jQuery component is elegant and more expressive than the ReactJS component's use of class="filled" and class="false".

We can migrate the jQuery component's inline CSS into a separate class like ReactJS and AngularJS are doing, making the markup shorter than anything else:

<span class="star off">&#9733;</span>

Conclusion

I also researched a few more star rating widgets and decided to include one of the Polymer variations found here. Let's put this all into perspective using Pete Hunt's original list:

jQuery: 33 (12 HTML, 21 JS)
ReactJS: 47 (12 HTML, 35 JS)
AngularJS: 64 (18 HTML, 46 JS)
Polymer: 124 (29 HTML, 95 JS)

Impressive, but nothing too exciting and here's why. The jQuery version looks a bit tidier and more expressive, but it lacks elsewhere. It doesn't bind to a data model; the DOM is used for both data and state. We also don't render a second read-only version of the widget for showcasing component encapsulation and binding, which is what definitely gives ReactJS the upper hand in most cases, but for this widget it's simply not necessary. Still, the jQuery component isn't half bad - it renders using an HTML template, supports multiple instances, a variable number of stars, and a default value - more than enough to get the job done.

The verdict is still out on which version has better compatibility, re-usability and portability. Perhaps I will do a follow up.

Related

Tags: ,

Leave a Reply

Your email address will not be published. Required fields are marked *