<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Dave Dash</title>
 <link href="http://davedash.com/tag/ypattern/atom.xml" rel="self"/>
 <link href="http://davedash.com/tag/ypattern"/>
 <updated>2012-04-07T22:42:44-07:00</updated>
 <id>http://davedash.com/</id>
 <author>
   <name>Dave Dash</name>
   <email>dd+atom1@davedash.com</email>
 </author>

 
 <entry>
   <title>Star Rating using YUI (and Django)</title>
   <link href="http://davedash.com/2008/03/04/star-rating-using-yui-and-django/"/>
   <updated>2008-03-04T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/03/04/star-rating-using-yui-and-django</id>
   <content type="html">&lt;p&gt;I have a very good star rater on &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt;, but it was written using
some sloppy prototype code.  I wanted to redo star raters in a well thought out
manner and I wanted to use &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt;.  In this particular tutorial I will use
&lt;a href=&quot;http://djangoproject.com/&quot;&gt;Django&lt;/a&gt; although it is not a requirement.&lt;/p&gt;

&lt;p&gt;For some background information on star raters, see this
&lt;a href=&quot;http://developer.yahoo.com/ypatterns/pattern.php?pattern=ratinganobject&quot;&gt;Yahoo! Design Pattern&lt;/a&gt;.  Our pattern is more of a join star rater, similar
to what's found on Netflix: you see an average rating for a restaurant or dish
unless you yourself have rated it.&lt;/p&gt;

&lt;p&gt;This was a thought out design decision for our &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; redesign.
Our site is primarily a personal utility that answers the question, &quot;What
dishes do I like at a particular restaurant?&quot;  If you haven't rated something
the website can only offer up an average and you can use that as a decision as
to whether you should eat something or not.&lt;/p&gt;

&lt;p&gt;If you have eaten something however, that average rating is irrelevant.  You
don't need fellow meal advisors to tell you that you liked Chicken Makhani, you
already know that for yourself.  Therefore we show only your rating unless you
haven't rated something.&lt;/p&gt;

&lt;h3&gt;Working backwards&lt;/h3&gt;

&lt;p&gt;I like to &quot;work backwards&quot; as it were.  Meaning, I like to just write the code
that I ultimately will use to output a star rater.  From there I will work on
the supporting code that is necessary.  I find by using this strategy, I can
keep my code fairly clean and organized.&lt;/p&gt;

&lt;h3&gt;The template&lt;/h3&gt;

&lt;p&gt;So ultimately I want this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{\% star 'mything' 3 4 '/path/to/script' %}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To show up as this:&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
&lt;img src=&quot;http://spindrop.us/wp-content/uploads/2008/03/starrater.png&quot; alt=&quot;starrater.png&quot; border=&quot;0&quot; width=&quot;105&quot; height=&quot;31&quot; /&gt;
&lt;/div&gt;




&lt;!--more--&gt;


&lt;p&gt;Unfortunately &lt;a href=&quot;http://www.djangoproject.com/documentation/templates/&quot;&gt;Django templates&lt;/a&gt; doesn't seem to have named attributes for &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/&quot;&gt;template tags&lt;/a&gt;, so I'll need to explain my syntax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;star&lt;/code&gt;: is the template tag which we define below&lt;/li&gt;
&lt;li&gt;&lt;code&gt;'mything'&lt;/code&gt;: is an id string we will use for this rater and its associated objects&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt;: this is the second argument to star, it will be the users current rating, it can also be None&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4.1&lt;/code&gt;: this is the third argument, it will be the average rating, it can also be None&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/path/to/script&lt;/code&gt;: is the form that will process our rating&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;The HTML we want&lt;/h3&gt;

&lt;p&gt;Another developer had a &lt;a href=&quot;http://www.unessa.net/en/hoyci/projects/yui-star-rating/&quot;&gt;good approach for handling star ratings&lt;/a&gt; and for handling Javascript in general.  Create an underlying Javascript-free system, and then let the Javascript make it pretty.  This is way to degrade gracefully.&lt;/p&gt;

&lt;p&gt;Ultimately, I had my own approach to this problem, I wanted much of the visual lifting to happen on the CSS layer.  So, we'll use the following code:&lt;/p&gt;

&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;html&quot;&gt;
&lt;span class=&quot;joint_star_rater&quot;&gt;
    &lt;form id=&quot;rater_restaurant&quot; method=&quot;post&quot; action=&quot;/restaurant/pizza-luce/rate/&quot;&gt;
        &lt;fieldset&gt;
            &lt;legend&gt;Rating&lt;/legend&gt;
            &lt;ul&gt;

        &lt;li style=&quot;width: 100px;&quot; title=&quot;5&quot; class=&quot;current meta&quot;&gt;Current Rating: 5&lt;/li&gt;


        &lt;li title=&quot;Poor&quot; class=&quot;star_1 star&quot;&gt;
            &lt;label for=&quot;restaurant_rating_1&quot;&gt;Poor&lt;/label&gt;
            &lt;input type=&quot;radio&quot; name=&quot;rating&quot; value=&quot;1&quot; id=&quot;restaurant_rating_1&quot;/&gt;
        &lt;/li&gt;

        &lt;li title=&quot;Fair&quot; class=&quot;star_2 star&quot;&gt;
            &lt;label for=&quot;restaurant_rating_2&quot;&gt;Fair&lt;/label&gt;
            &lt;input type=&quot;radio&quot; name=&quot;rating&quot; value=&quot;2&quot; id=&quot;restaurant_rating_2&quot;/&gt;
        &lt;/li&gt;

        &lt;li title=&quot;Good&quot; class=&quot;star_3 star&quot;&gt;
            &lt;label for=&quot;restaurant_rating_3&quot;&gt;Good&lt;/label&gt;
            &lt;input type=&quot;radio&quot; name=&quot;rating&quot; value=&quot;3&quot; id=&quot;restaurant_rating_3&quot;/&gt;
        &lt;/li&gt;

        &lt;li title=&quot;Very Good&quot; class=&quot;star_4 star&quot;&gt;
            &lt;label for=&quot;restaurant_rating_4&quot;&gt;Very Good&lt;/label&gt;
            &lt;input type=&quot;radio&quot; name=&quot;rating&quot; value=&quot;4&quot; id=&quot;restaurant_rating_4&quot;/&gt;
        &lt;/li&gt;

        &lt;li title=&quot;Excellent&quot; class=&quot;star_5 star&quot;&gt;
            &lt;label for=&quot;restaurant_rating_5&quot;&gt;Excellent&lt;/label&gt;
            &lt;input type=&quot;radio&quot; name=&quot;rating&quot; value=&quot;5&quot; id=&quot;restaurant_rating_5&quot;/&gt;
        &lt;/li&gt;

            &lt;/ul&gt;
        &lt;/fieldset&gt;
        &lt;input type=&quot;submit&quot; name=&quot;rate&quot; value=&quot;rate it&quot; class=&quot;submit&quot;/&gt;
    &lt;/form&gt;
&lt;/span&gt;

&lt;/textarea&gt;&lt;/p&gt;


&lt;p&gt;A couple things to note in our HTML.  Our unique string is &lt;code&gt;restaurant&lt;/code&gt;.  It's got an ID that is as unique as you want: &lt;code&gt;rater_restaurant&lt;/code&gt; where &lt;code&gt;restaurant&lt;/code&gt; was the first argument to our template tag.  We use &lt;code&gt;restaurant&lt;/code&gt; to create some other unique IDs as well.&lt;/p&gt;

&lt;p&gt;Also, this rating form makes a lot of sense semantically.  While this form in its current state is a far cry from some ajaxy goodness, it makes clear sense as to what is going on.&lt;/p&gt;

&lt;h3&gt;The template tag&lt;/h3&gt;

&lt;p&gt;Well we know what we want from the HTML side, so let's start coding our &lt;code&gt;star&lt;/code&gt; tag:&lt;/p&gt;

&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
@register.simple_tag
def star(id_string, current, average, path, spanfree=False):
    meta = None
    if current != None:
        meta = &quot;&quot;&quot;
        &lt;li class=&quot;current meta&quot; title=&quot;%d&quot; style=&quot;width:%dpx&quot;&gt;Current Rating: %d&lt;/li&gt;
        &quot;&quot;&quot; % (int(current), int(current)*20, int(current))
    else:
        meta = &quot;&quot;&quot;
        &lt;li class=&quot;average meta&quot; title=&quot;%.1f&quot; style=&quot;width:%dpx&quot;&gt;Average Rating: %.1f&lt;/li&gt;
        &quot;&quot;&quot; % (average, average*20,average)

    stars   = ''
    ratings = ['Poor', 'Fair', 'Good', 'Very Good', 'Excellent']

    for i in range(1,6):
        stars = stars + &quot;&quot;&quot;
        &lt;li class=&quot;star_%d star&quot; title=&quot;%s&quot;&gt;
            &lt;label for=&quot;%s_rating_%d&quot;&gt;%s&lt;/label&gt;
            &lt;input id=&quot;%s_rating_%d&quot; type=&quot;radio&quot; value=&quot;%d&quot; name=&quot;rating&quot;/&gt;
        &lt;/li&gt;
        &quot;&quot;&quot; % (i, ratings[i-1],id_string, i, ratings[i-1], id_string, i, i)
    html = &quot;&quot;&quot;
    &lt;form action=&quot;%s&quot; method=&quot;post&quot; id=&quot;rater_%s&quot;&gt;
        &lt;fieldset&gt;
            &lt;legend&gt;Rating&lt;/legend&gt;
            &lt;ul&gt;
            %s
            %s
            &lt;/ul&gt;
        &lt;/fieldset&gt;
        &lt;input type=&quot;submit&quot; class=&quot;submit&quot; value=&quot;rate it&quot; name=&quot;rate&quot;/&gt;
    &lt;/form&gt;
    &quot;&quot;&quot; % (path, id_string, meta,stars)
    if spanfree:
        return html
    else:
        return &quot;&quot;&quot;&lt;span class=&quot;joint_star_rater&quot;&gt;%s&lt;/span&gt;&quot;&quot;&quot; % html
    return html

&lt;/textarea&gt;&lt;/p&gt;


&lt;h3&gt;The CSS&lt;/h3&gt;

&lt;p&gt;A lot of work will happen via CSS.  The CSS will remove quite a bit of the textual data that can be interpreted graphically with stars.&lt;/p&gt;

&lt;p&gt;The strategy we use is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fix the &lt;code&gt;UL&lt;/code&gt; at a certain width with a background of grey stars&lt;/li&gt;
&lt;li&gt;decorate the &lt;code&gt;LI.average&lt;/code&gt; and &lt;code&gt;LI.current&lt;/code&gt; with repeating stars (blue and orange respectively) with a  &lt;code&gt;z-index&lt;/code&gt; of &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;decorate the &lt;code&gt;LI.average:hover&lt;/code&gt; and &lt;code&gt;LI.current:hover&lt;/code&gt; with a transparent background&lt;/li&gt;
&lt;li&gt;decorate &lt;code&gt;LI:hover input&lt;/code&gt; as a colored in star and a &lt;code&gt;z-index&lt;/code&gt; of &lt;code&gt;2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;This might not make sense now, until you see the CSS in full action.  Also for the stars we'll use a sprite of 3 stars.  A grey defunct star as the default background, a blue star if it's the average rating for an item and an orange star if it's what the user wants.&lt;/p&gt;

&lt;p&gt;I use the following sprite:&lt;/p&gt;

&lt;div style=&quot;background: #000;text-align:center; padding:1em&quot;&gt;
&lt;img src=&quot;http://spindrop.us/wp-content/uploads/2008/03/stars.png&quot; alt=&quot;stars.png&quot; border=&quot;0&quot; width=&quot;20&quot; height=&quot;60&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;The following CSS will do some magic:&lt;/p&gt;

&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;css&quot;&gt;
.joint_star_rater {display:inline-block;}
.joint_star_rater ul{width:100px;position:relative;height:20px;background:url(../images/icons/stars.png) repeat-x 0 0}
.joint_star_rater li.meta{position:absolute;text-indent:-9999px;display:block;z-index:1;}
.joint_star_rater ul:hover li.meta{display:none;}
.joint_star_rater li.current{background:url(../images/icons/stars.png) repeat-x 0 -40px}
.joint_star_rater li.average{background:url(../images/icons/stars.png) repeat-x 0 -20px}
.joint_star_rater li{height:20px;width:20px;position:absolute;text-indent:-9999px;z-index:3;}
.joint_star_rater li.star_2{left:20px;}
.joint_star_rater li.star_3{left:40px}
.joint_star_rater li.star_4{left:60px}
.joint_star_rater li.star_5{left:80px}
.joint_star_rater li.star_1:hover{width:20px}
.joint_star_rater li.star_2:hover{width:40px}
.joint_star_rater li.star_3:hover{width:60px}
.joint_star_rater li.star_4:hover{width:80px}
.joint_star_rater li.star_5:hover{width:100px}
.joint_star_rater li.star:hover{background:url(../images/icons/stars.png) repeat-x 0 -40px;z-index:2;left:0;}
.joint_star_rater input.submit{display:none;}
&lt;/textarea&gt;&lt;/p&gt;


&lt;p&gt;The &lt;code&gt;inline-block&lt;/code&gt; value for &lt;code&gt;display&lt;/code&gt; is not supported very well.  I recently switched to Firefox 3 Beta and it renders as expected.  Firefox 2 has problems with it.  I may revise the CSS later to accommodate it.&lt;/p&gt;

&lt;h3&gt;The Javascript&lt;/h3&gt;

&lt;p&gt;The fundamental drawback to the design here, is that it really only works well with the Javascript on.  In fact, with the CSS on and Javascript off, this code will not work very well for the end user.  This too will be revised in the future.&lt;/p&gt;

&lt;p&gt;Our Javascript needs to do something very simple:
* extract the star value you clicked on
* send it to the server
* redraw the stars&lt;/p&gt;

&lt;p&gt;It's a very simple operation, but I honestly think other libraries have an advantage to YUI in this regard.&lt;sup id=&quot;#fnr_1&quot;&gt;&lt;a href=&quot;#fn_1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;  Here's some unobtrusive code I came up with:&lt;/p&gt;

&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;js&quot;&gt;
var MA = {}; // MA namespace
MA.e = YAHOO.util.Event;
MA.d = YAHOO.util.Dom;
MA.c = YAHOO.util.Connect;

MA.star_rater = function() {
    var e = YAHOO.util.Event;
    var d = YAHOO.util.Dom;

    return {
        init: function() {
            e.onDOMReady(this.setup,this, true)
        },

        setup: function() {
            e.on(d.get('doc4'),'click',this.handleClick,this,true);
        },

        handleClick: function(ev) {
            var target = e.getTarget(ev);
            if (d.hasClass(target, 'star')
            &amp;&amp; d.hasClass(target.parentNode.parentNode.parentNode.parentNode, 'joint_star_rater')) {
                this.rate(target);
            }
        },

        rate: function(el) {
            if (MA.is_authenticated('Please sign in before rating =)')) {

                root   = el.parentNode.parentNode.parentNode.parentNode;
                action      = el.parentNode.parentNode.parentNode.action;
                input       = MA.d.getFirstChildBy(el, function(d) {return (d.tagName == 'input'||d.tagName=='INPUT')});
                this.value  = input.value;
                postdata    = &quot;value=&quot;+this.value;

                handleSuccess = function(o) { root.innerHTML = o.responseText }
                callback = {
                    success:handleSuccess,
                }


                var request = MA.c.asyncRequest('POST', action, callback, postdata);
                // construct a connection object to this and use it to make a post
                // retrieve the post and then replace it with the original span
            }
        },

    }
}();

MA.star_rater.init();
&lt;/textarea&gt;&lt;/p&gt;


&lt;p&gt;Note: I intentionally left out irrelevant pieces of code, like the function definition of &lt;code&gt;MA.is_authenticated()&lt;/code&gt;, this code isn't meant for cutting and pasting, it's meant for cutting-pasting and then some careful editing.&lt;/p&gt;

&lt;h3&gt;The callback view&lt;/h3&gt;

&lt;p&gt;The callback script is what you specify when you call &lt;code&gt;{\% star ... %}&lt;/code&gt;.  The view I use is as follows:&lt;/p&gt;

&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
@login_required
def rate(request, slug):
    MyObject = get_my_object()
    value    = request['value']

    MyObject,rate(value);
    return render_to_response(&quot;rating.html&quot;, locals(), context_instance=RequestContext(request))

&lt;/textarea&gt;&lt;/p&gt;


&lt;p&gt;That code is oversimplified... you have to write your own logic as it applies to our site.  The &lt;code&gt;rating.html&lt;/code&gt; is simply the call to your star tag:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{\% load tags %}
{\% star 'mything' restaurant.current_rating restaurant.average_rating restaurant.get_rating_url 1 %}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the &lt;code&gt;1&lt;/code&gt; at the end.  It's a flag to turn off the outer &lt;code&gt;span&lt;/code&gt; so we can just insert the guts back into the original &lt;code&gt;span&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Final Thoughts&lt;/h3&gt;

&lt;p&gt;The star-rater is really a large problem that's hard to tackle in one sitting and quite frankly is not documented well anywhere.  The code I've provided is a shadow of the real code I'll be using, but hopefully it's enough to get you started.&lt;/p&gt;

&lt;p&gt;I definitely will update my production code to solve a few outstanding issues, as I mentioned above.  I'll try to update this tutorial at the same time.  If there are questions about the examples given, feel free to ask and I'll attempt to answer.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
&lt;ol&gt;
&lt;li id=&quot;fn_1&quot;&gt;YUI has the &lt;code&gt;Selector&lt;/code&gt; and that might alleviate some problems, but jQuery has very nice selection capabilities.  In this future version I keep promising, I'll use the YUI &lt;code&gt;Selector&lt;/code&gt; &lt;a href=&quot;#fnr_1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 

</feed>

