From fdbd5ee6db2a2ea88c670a2b7b4c94539eebb9eb Mon Sep 17 00:00:00 2001
From: Ben Adida <ben@adida.net>
Date: Sun, 19 Jan 2014 15:33:42 -0800
Subject: [PATCH] implemented answer reordering and enabled the field in the
 election form

---
 helios/forms.py                     |  2 +-
 heliosbooth/templates/question.html |  6 ++--
 heliosbooth/vote.html               | 49 +++++++++++++++++++++++++++--
 3 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/helios/forms.py b/helios/forms.py
index c1ccea3..a5263fa 100644
--- a/helios/forms.py
+++ b/helios/forms.py
@@ -14,7 +14,7 @@ class ElectionForm(forms.Form):
   election_type = forms.ChoiceField(label="type", choices = Election.ELECTION_TYPES)
   use_voter_aliases = forms.BooleanField(required=False, initial=False, help_text='If selected, voter identities will be replaced with aliases, e.g. "V12", in the ballot tracking center')
   #use_advanced_audit_features = forms.BooleanField(required=False, initial=True, help_text='disable this only if you want a simple election with reduced security but a simpler user interface')
-  #randomize_answer_order = forms.BooleanField(required=False, initial=False, help_text='enable this if you want the answers to questions to appear in random order for each voter')
+  randomize_answer_order = forms.BooleanField(required=False, initial=False, help_text='enable this if you want the answers to questions to appear in random order for each voter')
   private_p = forms.BooleanField(required=False, initial=False, label="Private?", help_text='A private election is only visible to registered voters.')
   help_email = forms.CharField(required=False, initial="", label="Help Email Address", help_text='An email address voters should contact if they need help.')
   
diff --git a/heliosbooth/templates/question.html b/heliosbooth/templates/question.html
index 1740631..4d9af47 100644
--- a/heliosbooth/templates/question.html
+++ b/heliosbooth/templates/question.html
@@ -20,12 +20,12 @@ as many of the choices as you approve of
 </p>
 
 {#foreach $T.question.answers as answer}
-<div id="answer_label_{$T.question_num}_{$T.answer$index}"><input type="checkbox" class="ballot_answer" id="answer_{$T.question_num}_{$T.answer$index}" name="answer_{$T.question_num}_{$T.answer$index}" value="yes" onclick="BOOTH.click_checkbox({$T.question_num}, {$T.answer$index}, this.checked);" /> {$T.answer}
+<div id="answer_label_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}"><input type="checkbox" class="ballot_answer" id="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" name="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" value="yes" onclick="BOOTH.click_checkbox({$T.question_num}, {$T.answer_ordering[$T.answer$index]}, this.checked);" /> {$T.question.answers[$T.answer_ordering[$T.answer$index]]}
 
-{#if $T.question.answer_urls && $T.question.answer_urls[$T.answer$index] && $T.question.answer_urls[$T.answer$index] != ""}
+{#if $T.question.answer_urls && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] != ""}
 &nbsp;&nbsp;
 <span style="font-size: 12pt;">
-[<a target="_blank" href="{$T.question.answer_urls[$T.answer$index]}">more info</a>]
+[<a target="_blank" href="{$T.question.answer_urls[$T.answer_ordering[$T.answer$index]]}">more info</a>]
 </span>
 {#/if}
 </div>
diff --git a/heliosbooth/vote.html b/heliosbooth/vote.html
index 38f40c3..2667fa9 100644
--- a/heliosbooth/vote.html
+++ b/heliosbooth/vote.html
@@ -159,7 +159,7 @@ function escape_html(content) {
   return $('<div/>').text(content).html();
 }
 
-BOOTH.setup_election = function(raw_json) {
+BOOTH.setup_election = function(raw_json, election_metadata) {
   // IMPORTANT: we use the raw JSON for safer hash computation
   // so that we are using the JSON serialization of the SERVER
   // to compute the hash, not the JSON serialization in JavaScript.
@@ -182,6 +182,26 @@ BOOTH.setup_election = function(raw_json) {
     BOOTH.election[field] = escape_html(BOOTH.election[field]);
   });
 
+  // TODO: escape question and answers
+
+  // whether the election wants candidate order randomization or not
+  // we set up an ordering array so that the rest of the code is
+  // less error-prone. 
+  BOOTH.election.question_answer_orderings = [];
+  $(BOOTH.election.questions).each(function(i, question) {
+    var ordering = new Array(question.answers.length);
+
+    // initialize array so it is the identity permutation
+    $(ordering).each(function(j, answer) {ordering[j]=j;});
+
+    // if we want reordering, then we shuffle the array
+    if (election_metadata && election_metadata.randomize_answer_order) {
+      shuffleArray(ordering);
+    }
+
+    BOOTH.election.question_answer_orderings[i] = ordering;
+  });
+
   $('#header').processTemplate({'election' : BOOTH.election, 'election_metadata': BOOTH.election_metadata});
   $('#footer').processTemplate({'election' : BOOTH.election, 'election_metadata': BOOTH.election_metadata});
   BOOTH.setup_ballot();
@@ -246,6 +266,28 @@ BOOTH.previous = function(question_num) {
     }
 };
 
+// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
+function shuffleArray(array) {
+  var currentIndex = array.length
+    , temporaryValue
+    , randomIndex
+    ;
+
+  // While there remain elements to shuffle...
+  while (0 !== currentIndex) {
+     // Pick a remaining element...
+    randomIndex = Math.floor(Math.random() * currentIndex);
+    currentIndex -= 1;
+
+    // And swap it with the current element.
+    temporaryValue = array[currentIndex];
+    array[currentIndex] = array[randomIndex];
+    array[randomIndex] = temporaryValue;
+  }
+
+  return array;
+}
+
 BOOTH.show_question = function(question_num) {
   BOOTH.started_p = true;
 
@@ -256,7 +298,8 @@ BOOTH.show_question = function(question_num) {
   BOOTH.show_progress('1');
   BOOTH.show($('#question_div')).processTemplate({'question_num' : question_num,
                       'last_question_num' : BOOTH.election.questions.length - 1,
-                      'question' : BOOTH.election.questions[question_num], 'show_reviewall' : BOOTH.all_questions_seen
+                      'question' : BOOTH.election.questions[question_num], 'show_reviewall' : BOOTH.all_questions_seen,
+		      'answer_ordering': BOOTH.election.question_answer_orderings[question_num]
                 });
 
   // fake clicking through the answers, to trigger the disabling if need be
@@ -330,7 +373,7 @@ BOOTH.load_and_setup_election = function(election_url) {
         // let's also get the metadata
         $.getJSON(election_url + "/meta", {}, function(election_metadata) {
           BOOTH.election_metadata = election_metadata;
-          BOOTH.setup_election(raw_json);
+          BOOTH.setup_election(raw_json, election_metadata);
           BOOTH.show_election();
           BOOTH.election_url = election_url;
         });
-- 
GitLab