fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2011-12-01 06:26 am

[dw-free] Display how a person answered all questions of a poll

[commit: http://hg.dwscoalition.org/dw-free/rev/154dd52a7a64]

http://bugs.dwscoalition.org/show_bug.cgi?id=26

Add a "View respondents" link (for non-anonymous polls) which lists all
users who have voted in the poll; this list can be further expanded to see
what each user's vote is.

Patch by [personal profile] yvi.

Files modified:
  • bin/upgrading/en.dat
  • cgi-bin/LJ/Poll.pm
  • cgi-bin/LJ/Poll/Question.pm
  • htdocs/js/jquery.poll.js
  • htdocs/js/livejournal.js
  • htdocs/tools/endpoints/poll.bml
--------------------------------------------------------------------------------
diff -r 1cb6ed2d70b0 -r 154dd52a7a64 bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Wed Nov 30 23:53:06 2011 -0600
+++ b/bin/upgrading/en.dat	Thu Dec 01 14:25:15 2011 +0800
@@ -2172,6 +2172,8 @@
 
 poll.pollnum=Poll #[[num]]
 
+poll.respondents.user=[[user]] answered:
+
 poll.scaleanswers=<strong>Mean:</strong> [[mean]] <strong>Median:</strong> [[median]] <strong>Std. Dev</strong> [[stddev]]
 
 poll.security=Open to <strong>[[whovote]]</strong>. Results viewable to <strong>[[whoview]]</strong>
@@ -2200,6 +2202,8 @@
 
 poll.viewanswers=View Answers
 
+poll.viewrespondents=View Respondents
+
 portal.bdays.count.des=By default, the 5 friends with the soonest birthdays are shown.
 
 portal.bdays.count.name=Birthdays to Display
diff -r 1cb6ed2d70b0 -r 154dd52a7a64 cgi-bin/LJ/Poll.pm
--- a/cgi-bin/LJ/Poll.pm	Wed Nov 30 23:53:06 2011 -0600
+++ b/cgi-bin/LJ/Poll.pm	Thu Dec 01 14:25:15 2011 +0800
@@ -676,6 +676,7 @@
     $self->_load;
     return $self->{name};
 }
+# returns "yes" if the poll is anonymous
 sub isanon {
     my $self = $_[0];
     $self->_load;
@@ -884,7 +885,7 @@
     my @qs = $self->questions;
 
     ### view answers to a particular question in a poll
-    if ($mode eq "ans") {
+    if ( $mode eq "ans" ) {
         return "<b>[" . LJ::Lang::ml('poll.error.cantview') . "]</b>"
             unless $self->can_view;
         my $q = $self->question($qid)
@@ -898,6 +899,22 @@
         my $pages    = $q->answers_pages($self->journalid, $pagesize);
         $ret .= '<div>' . $q->paging_bar_as_html($page, $pages, $pagesize, $self->journalid, $pollid, $qid, no_class => 1) . '</div>';
         return $ret;
+    } elsif ( $mode eq "ans_extended" ) {
+        # view detailed answers for every user
+        return "<b>[" . LJ::Lang::ml( 'poll.error.cantview' ) . "]</b>"
+            unless $self->can_view;
+
+        my @userids;
+
+        my $respondents = $self->journal->selectall_arrayref(
+            "SELECT DISTINCT(userid) FROM pollresult2 WHERE pollid=? AND journalid=? ",
+            undef, $pollid, $self->journalid );
+
+        foreach my $userid ( @$respondents ) {
+            $ret .= "<div class='useranswer'>" . $self->user_answers_as_html( $userid ) . "</div><br />";
+        }
+
+        return $ret;
     }
 
     # Users cannot vote unless they are logged in
@@ -957,6 +974,11 @@
         $ret .= "<br />\n";
         # change vote link
         $ret .= "[ <a href='$LJ::SITEROOT/poll/?id=$pollid&amp;mode=enter' class='LJ_PollChangeLink' id='LJ_PollChangeLink_${pollid}' lj_pollid='$pollid' >" . LJ::Lang::ml( 'poll.changevote' ) . "</a> ]" if $self->can_vote( $remote ) && !$self->is_closed;
+        if ( $self->can_view && $self->isanon ne "yes" ) {
+            $ret .= "<br /><br /><div class='respondents'><a href='$LJ::SITEROOT/poll/?id=$pollid&amp;mode=ans_extended' class='LJ_PollRespondentsLink' " .
+            "id='LJ_PollRespondentsLink_${pollid}' " .
+            "lj_pollid='$pollid' >" . LJ::Lang::ml( 'poll.viewrespondents' ) . "</a></div><br />"
+        }
     } else {
         $ret .= "<br />\n";
     }
@@ -1299,6 +1321,50 @@
     return @qs;
 }
 
+# returns a string with the html of how a user answered all questions of this poll
+sub user_answers_as_html {
+    my ( $self, $userid ) = @_;
+
+    my $ret;
+    my $u = LJ::load_userid( $userid );
+
+    $ret = "<span class='useranswer' id='useranswer_" . $u->userid . "'>"  . LJ::Lang::ml( 'poll.respondents.user', { user => $u->ljuser_display } ) . "\n";
+
+    my @qs = $self->questions;
+
+    foreach my $q ( @qs ) {
+        $ret .= $q->user_answer_as_html( $userid );
+    }
+    $ret .= "</span>";
+
+    return $ret;
+ }
+
+# returns a string with the html of the people who responded to this poll
+sub respondents_as_html {
+    my ( $self ) = @_;
+    my $pollid = $self->pollid;
+
+    my @res = @{ $self->journal->selectall_arrayref(
+        "SELECT DISTINCT(userid) FROM pollresult2 WHERE pollid=? AND journalid=? ",
+        undef, $pollid, $self->journalid ) };
+    my @respondents = map { $_->[0] } @res;
+
+    my $users = LJ::load_userids( @respondents );
+
+    my $ret;
+    foreach my $userid ( @respondents ) {
+        my $u = $users->{$userid};
+        next unless $u;
+
+        $ret .= "<div> <a href='$LJ::SITEROOT/poll/?id=$pollid&amp;mode=ans_extended'" .
+            "class='LJ_PollUserAnswerLink'" .
+            "lj_pollid='$pollid' lj_userid='$userid'" .
+            "id='LJ_PollUserAnswerLink_${pollid}_$userid'>[+]</a>" .
+            "<span class='polluser' id='LJ_PollUserAnswerRes_${pollid}_$userid'>" . $u->ljuser_display . "</span></div>\n";
+    }
+    return $ret;
+}
 
 ########## Props
 # get the typemap for pollprop2
diff -r 1cb6ed2d70b0 -r 154dd52a7a64 cgi-bin/LJ/Poll/Question.pm
--- a/cgi-bin/LJ/Poll/Question.pm	Wed Nov 30 23:53:06 2011 -0600
+++ b/cgi-bin/LJ/Poll/Question.pm	Thu Dec 01 14:25:15 2011 +0800
@@ -307,6 +307,51 @@
     return $ret;
 }
 
+#returns how a user answered this question
+sub user_answer_as_html {
+    my $self = shift;
+    my $userid = shift;
+    my $isanon = shift;
+
+    my $ret = '';
+
+    # Get data
+    my $sth = $self->poll->journal->prepare(
+        "SELECT value FROM pollresult2 " .
+        "WHERE pollid=? AND pollqid=? AND userid=? " );
+
+    $sth->execute( $self->pollid, $self->pollqid, $userid );
+    die $sth->errstr if $sth->err;
+
+    my ( $pollid, $pollqid ) = ( $self->pollid, $self->pollqid );
+
+    my $qtext = $self->qtext;
+    my @res;
+    push @res, $_ while $_ = $sth->fetchrow_hashref;
+
+    foreach my $res ( @res ) {
+        my $value = $res->{value};
+        my @items = $self->items;
+
+        my %it;
+        $it{$_->{pollitid}} = $_->{item} foreach @items;
+
+        # some question types need translation; type 'text' doesn't.
+        if ( $self->type eq "radio" || $self->type eq "drop" ) {
+            $value = $it{$value};
+        } elsif ( $self->type eq "check" ) {
+            $value = join( ", ", map { $it{$_} } split( /,/, $value ) );
+        }
+
+        LJ::Poll->clean_poll( \$value );
+        LJ::Poll->clean_poll( \$qtext );
+
+        $ret .= '<b>' . $qtext . "</b> -- " . $value . "<br/>\n";
+    }
+
+    return $ret;
+}
+
 sub paging_bar_as_html {
     my $self = shift;
 
diff -r 1cb6ed2d70b0 -r 154dd52a7a64 htdocs/js/jquery.poll.js
--- a/htdocs/js/jquery.poll.js	Wed Nov 30 23:53:06 2011 -0600
+++ b/htdocs/js/jquery.poll.js	Thu Dec 01 14:25:15 2011 +0800
@@ -84,6 +84,35 @@
                 }
             });
         }).end()
+        .filter(".respondents").children("a.LJ_PollRespondentsLink").click(function(e){
+            e.stopPropagation();
+            e.preventDefault();
+
+            var $clicked = $(this);
+            var pollid = $clicked.attr("lj_pollid");
+
+            $clicked
+            .ajaxtip({namespace: "pollanswer"})
+            .ajaxtip("load", {
+                endpoint: "poll",
+                context: self,
+                data: {
+                    pollid  : pollid,
+                    action  : "get_respondents"
+                },
+                success: function( data, status, jqxhr ) {
+                    if ( data.error ) {
+                        $clicked.ajaxtip( "error", data.error )
+                    } else {
+                        $clicked.ajaxtip( "cancel" ).hide();
+                        $clicked.closest("div").append(data.answer_html);
+                        $clicked.closest("div").parent().trigger( "updatedcontent.poll" );
+                    }
+                    self._trigger( "complete" );
+                }
+                });
+
+        }).end()
         .filter("a.LJ_PollChangeLink").click(function(e){
             e.stopPropagation();
             e.preventDefault();
@@ -106,7 +135,60 @@
                         self._trigger( "complete" );
                     }
                     });
-        }).end()
+        }).end();
+
+        $("a.LJ_PollUserAnswerLink").click(function(e){
+            e.stopImmediatePropagation();
+            e.preventDefault();
+
+            var $clicked = $(this);
+
+            var pollid = $clicked.attr("lj_pollid");
+            var userid = $clicked.attr("lj_userid");
+
+            if ( ! pollid || ! userid ) return;
+
+            if ( $clicked.attr('innerHTML') === "[-]" ) {
+                $clicked.siblings(".useranswer").remove()
+                    .end().siblings(".polluser").show();
+                $clicked.html("[+]");
+            } else {
+                $clicked
+                .ajaxtip({namespace: "polluseranswer"})
+                .ajaxtip("load", {
+                    endpoint: "poll",
+                    context: self,
+                    data: {
+                        pollid  : pollid,
+                        userid  : userid,
+                        action  : "get_user_answers"
+                    },
+                    success: function( data, status, jqxhr ) {
+                        if ( data.error ) {
+                            $clicked.ajaxtip( "error", data.error )
+                        } else {
+                            var pollid = data.pollid;
+                            var userid = data.userid;
+                            if ( ! pollid || ! userid ) {
+                                $clicked.ajaxtip( "error", "Error fetching poll results." );
+                            } else {
+                                $clicked.ajaxtip( "cancel" );
+
+                                $clicked.html("[-]");
+                                $clicked.siblings(".polluser").hide()
+                                    .closest("div").append(data.answer_html);
+                            }
+                        }
+
+                        self._trigger( "complete" );
+                    },
+                    error: function( jqxhr, status, error ) {
+                        $clicked.ajaxtip( "error", "Error contacting server. " + error);
+                        self._trigger( "complete" );
+                    }
+                });
+            }
+        });
 
     },
     _initForm: function() {
diff -r 1cb6ed2d70b0 -r 154dd52a7a64 htdocs/js/livejournal.js
--- a/htdocs/js/livejournal.js	Wed Nov 30 23:53:06 2011 -0600
+++ b/htdocs/js/livejournal.js	Thu Dec 01 14:25:15 2011 +0800
@@ -236,6 +236,20 @@
     Array.prototype.forEach.call(pollForms, function (pollForm) {
         DOM.addEventListener(pollForm, "submit", LiveJournal.pollFormSubmitted.bindEventListener(pollForm));
     });
+
+    var pollRespondentsLinks = DOM.getElementsByTagAndClassName(document, 'a', "LJ_PollRespondentsLink") || [];
+
+    // attach click handlers to each link to show respondents to a poll
+    Array.prototype.forEach.call(pollRespondentsLinks, function (pollRes) {
+        DOM.addEventListener(pollRes, "click", LiveJournal.pollRespondentsLinkClicked.bindEventListener(pollRes));
+    });
+
+    var pollUserLinks = DOM.getElementsByTagAndClassName(document, 'a', "LJ_PollUserAnswerLink") || [];
+
+    // attach click handlers to each answer link
+    Array.prototype.forEach.call(pollUserLinks, function (pollLink) {
+        DOM.addEventListener(pollLink, "click", LiveJournal.pollUserAnswerLinkClicked.bindEventListener(pollLink));
+    });
 };
 
 LiveJournal.pollButtonClicked = function (e) {  
@@ -478,6 +492,146 @@
     LiveJournal.initPolls();
 };
 
+LiveJournal.pollRespondentsLinkClicked = function (e) {
+    Event.stop(e);
+
+    if (! this || ! this.tagName || this.tagName.toLowerCase() != "a")
+    return true;
+
+    var pollid = this.getAttribute("lj_pollid");
+    if (! pollid) return true;
+
+    var action = "get_respondents";
+
+    answerEle = $("LJ_PollRespondentsLink_" + pollid);
+    if (! answerEle) return false;
+
+    var params = {
+        "pollid"   : pollid,
+        "action"   : action
+    };
+
+    var opts = {
+        "url"    : LiveJournal.getAjaxUrl("poll"),
+        "method" : "POST",
+        "data"   : HTTPReq.formEncoded(params),
+        "onData" : LiveJournal.pollRespondentsReceived,
+        "onError": LiveJournal.ajaxError
+    };
+
+    HTTPReq.getJSON(opts);
+
+    if (!PollPages.hourglass) {
+        var coords = DOM.getAbsoluteCursorPosition(e);
+        PollPages.hourglass = new Hourglass();
+        PollPages.hourglass.init();
+        PollPages.hourglass.hourglass_at(coords.x, coords.y);
+        PollPages.e = e;
+    }
+
+    return false;
+}
+
+LiveJournal.pollRespondentsReceived = function (answers) {
+    if (! answers) return false;
+
+    if (PollPages.hourglass) {
+        PollPages.hourglass.hide();
+        PollPages.hourglass = null;
+    }
+
+    if (answers.error) return LiveJournal.ajaxError(answers.error);
+
+    var pollid = answers.pollid;
+    if (! pollid) return false;
+
+    answerEle = $("LJ_PollRespondentsLink_" + pollid);
+    if (! answerEle) return false;
+
+    var answer = answers.answer_html ? answers.answer_html : "(No answers)";
+    var newAnswerEle = document.createElement("span");
+    newAnswerEle.innerHTML = answer;
+    answerEle.parentNode.replaceChild(newAnswerEle, answerEle);
+
+    if (typeof ContextualPopup != "undefined")
+        ContextualPopup.setup();
+
+    LiveJournal.initPolls();
+};
+
+
+LiveJournal.pollUserAnswerLinkClicked = function (e) {
+    Event.stop(e);
+
+    if (! this || ! this.tagName || this.tagName.toLowerCase() != "a")
+    return true;
+
+    var pollid = this.getAttribute("lj_pollid");
+    if (! pollid) return true;
+
+    var userid = this.getAttribute("lj_userid");
+    if (! userid) return true;
+
+    var action = "get_user_answers";
+
+    answerEle = $("LJ_PollUserAnswerLink_" + pollid + "_" + userid);
+    if (! answerEle) return false;
+
+    	// Do ajax request to replace the link with the answers
+    var params = {
+        "pollid"   : pollid,
+        "userid"   : userid,
+        "action"   : action
+    };
+
+    var opts = {
+       	"url"    : LiveJournal.getAjaxUrl("poll"),
+       	"method" : "POST",
+       	"data"   : HTTPReq.formEncoded(params),
+       	"onData" : LiveJournal.pollUserAnswersReceived,
+       	"onError": LiveJournal.ajaxError
+    };
+
+    HTTPReq.getJSON(opts);
+
+    if (!PollPages.hourglass) {
+       	var coords = DOM.getAbsoluteCursorPosition(e);
+       	PollPages.hourglass = new Hourglass();
+       	PollPages.hourglass.init();
+       	PollPages.hourglass.hourglass_at(coords.x, coords.y);
+       	PollPages.e = e;
+    }
+    return false;
+};
+
+LiveJournal.pollUserAnswersReceived = function (answers) {
+    if (! answers) return false;
+
+    if (PollPages.hourglass) {
+        PollPages.hourglass.hide();
+        PollPages.hourglass = null;
+    }
+
+    if (answers.error) return LiveJournal.ajaxError(answers.error);
+
+    var pollid = answers.pollid;
+    var userid = answers.userid;
+    if (! pollid || ! userid) return false;
+
+    var linkEle = $("LJ_PollUserAnswerLink_" + pollid + "_" + userid);
+    if (! linkEle) return false;
+
+    answerEle = $("LJ_PollUserAnswerRes_" + pollid + "_" + userid);
+    if (! answerEle) return false;
+
+    answerEle.innerHTML = answers.answer_html ? answers.answer_html : "(No answers)";
+	linkEle.innerHTML = "";
+	answerEle.style.display = "block";
+	
+    if (typeof ContextualPopup != "undefined")
+        ContextualPopup.setup();
+};
+
 // gets a url for doing ajax requests
 LiveJournal.getAjaxUrl = function (action) {
     // if we are on a journal subdomain then our url will be
diff -r 1cb6ed2d70b0 -r 154dd52a7a64 htdocs/tools/endpoints/poll.bml
--- a/htdocs/tools/endpoints/poll.bml	Wed Nov 30 23:53:06 2011 -0600
+++ b/htdocs/tools/endpoints/poll.bml	Thu Dec 01 14:25:15 2011 +0800
@@ -32,7 +32,8 @@
     BML::noparse();
 
     my $pollid   = $POST{pollid}  or return $err->("No pollid");
-    my $pollqid  = $POST{pollqid} or return $err->("No pollqid");
+    my $pollqid  = $POST{pollqid} || 0;
+    my $userid   = $POST{userid} || 0;
     my $action   = $POST{action};
     my $page     = $POST{page};
     my $pagesize = $POST{pagesize} || 2000;
@@ -46,10 +47,18 @@
     }
 
     if ($action eq 'get_answers') {
+        return $err->( "No pollqid" ) unless $pollqid;
+
         my $question = $poll->question($pollqid) or return $err->("Error loading question $pollqid");
         my $pages    = $question->answers_pages($poll->journalid, $pagesize);
         $ret->{paging_html} = $question->paging_bar_as_html($page, $pages, $pagesize, $poll->journalid, $pollid, $pollqid);
         $ret->{answer_html} = $question->answers_as_html($poll->journalid, $poll->isanon, $page, $pagesize, $pages);
+    } elsif ( $action eq 'get_respondents' ) {
+        $ret->{answer_html} = $poll->respondents_as_html;
+    }  elsif ($action eq 'get_user_answers') {
+        return $err->( "No userid" ) unless $userid;
+
+        $ret->{answer_html} = $poll->user_answer_as_html( $userid );
     } else {
         return $err->("Invalid action $action");
     }
@@ -58,6 +67,7 @@
         %$ret,
         pollid  => $pollid,
         pollqid => $pollqid,
+        userid  => $userid,
         page    => $page,
     };
 
--------------------------------------------------------------------------------