You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@steve.apache.org by hu...@apache.org on 2015/03/16 12:43:22 UTC
svn commit: r1666970 - in /steve/trunk/www: cgi-bin/cast-vote.pl
htdocs/images/ htdocs/images/ballot_bg.png htdocs/images/dragleft.png
htdocs/images/dragright.png htdocs/images/target.png
htdocs/steve_interactive.css htdocs/steve_interactive.js
Author: humbedooh
Date: Mon Mar 16 11:43:22 2015
New Revision: 1666970
URL: http://svn.apache.org/r1666970
Log:
Add interactive mode for STV type elections.
Here goes nuthin'
Added:
steve/trunk/www/htdocs/images/
steve/trunk/www/htdocs/images/ballot_bg.png (with props)
steve/trunk/www/htdocs/images/dragleft.png (with props)
steve/trunk/www/htdocs/images/dragright.png (with props)
steve/trunk/www/htdocs/images/target.png (with props)
steve/trunk/www/htdocs/steve_interactive.css
steve/trunk/www/htdocs/steve_interactive.js
Modified:
steve/trunk/www/cgi-bin/cast-vote.pl
Modified: steve/trunk/www/cgi-bin/cast-vote.pl
URL: http://svn.apache.org/viewvc/steve/trunk/www/cgi-bin/cast-vote.pl?rev=1666970&r1=1666969&r2=1666970&view=diff
==============================================================================
--- steve/trunk/www/cgi-bin/cast-vote.pl (original)
+++ steve/trunk/www/cgi-bin/cast-vote.pl Mon Mar 16 11:43:22 2015
@@ -51,6 +51,8 @@ my $issue_name = "$group-$issue";
my $q = CGI->new;
+my $interactive = $q->param(interactive)
+
if ($ENV{REQUEST_METHOD} eq "GET" or $ENV{REQUEST_METHOD} eq "HEAD") {
open my $fh, "$VOTE_ISSUEDIR/$group/$issue/issue"
@@ -99,6 +101,9 @@ EOT
if ($type eq "yna") {
$output .= yna_form($voter, $issue_name, $issue_content, $trailer);
}
+ elsif ($type =~ /^stv([1-9])$/ && $interactive) {
+ $output .= stv_form_interactive($1, $voter, $issue_name, $issue_content, $trailer);
+ }
elsif ($type =~ /^stv([1-9])$/) {
$output .= stv_form($1, $voter, $issue_name, $issue_content, $trailer);
}
@@ -384,6 +389,88 @@ $other_issues
</html>
EoSTV
}
+
+sub stv_form_interactive {
+ my ($num, $voter, $issue_name, $issue_content, $trailer) = @_;
+ my $other_issues = other_issues($issue_name, $voter);
+ my @chars;
+ my @names;
+ while ($issue_content =~ m/\[([a-z])\]\s+(.+)/g) {
+ push @chars, "'$1'";
+ push @names, "\"$2\"";
+ }
+ my $str_candidates = join(", ", @names);
+ my $str_chars = join(", ", @chars);
+ my $str_statements = ""; # TODO!
+ my $num_candidates = scalar(@names);
+ return <<EoSTV;
+ <!DOCTYPE HTML>
+<html>
+ <head>
+ <link rel="stylesheet" href="/steve_interactive.css">
+ <script type="text/javascript">
+
+ // STV Data
+ var seats = $num; // Number of seats on the board
+
+ // Nominees
+ var candidates = [ $str_names ];
+ var chars = [ $str_chars ];
+
+ // Statements
+ var statements = { $str_statements };
+
+ </script>
+ <script src="/steve_interactive.js" type="text/javascript"></script>
+
+ <title>Cast your vote <$voter> on $issue_name:</title>
+ </head>
+ <body onload="shuffleCandidates(); drawCandidates()">
+ <h2><h1>Cast your vote <$voter> on $issue_name:</h1></h2>
+
+ <p>
+ This is an interactive ballot for $issue_name, with $num_candidates
+ nominated people and $num board seats available. The red line
+ denotes the cutaway, should all your choices be voted in. All the
+ nominees are placed in random order on the canidate list. If the
+ canidate has prepared a statement, you can view it by clicking on
+ the statement link to the right of the candidate's name.
+ </p>
+
+ <p>
+ <b>How to vote:</b><br/> Drag a candidate from the candidate list to the
+ ballot box to place them in the vote. You can rearrange your votes as you
+ see fit, by dragging candidates up/down on the ballot box list. You may
+ place as many canidates in the ballot box as you see fit. If you want to
+ remove a single candidate from your ballot box, simply drag the canidate
+ back to the list of remaining candidates to the left.
+ </p>
+
+ <div id="candidates">
+ Not seeing the canidate list? Please enable JavaScript!
+ </div>
+
+ <div id="ballotbox" ondragover="event.preventDefault();"
+ ondragenter="event.preventDefault();" ondragend="event.preventDefault();"
+ ondrop="dropCandidate(event)">
+ <font color='red'><h3>Drag candidates over here to vote for them</h3></font>
+ <ol id="ballot">
+ <img src="/images/target.png" style="margin-left: 100px;"/>
+ </ol>
+ <div id="stv">
+ <form method="POST">
+ <b>Your STV order:</b>
+ <input type="text" id="vote" name="vote" style="width: 160px; font-family:
+ monospace;"/> <input type="submit" value="Cast votes"/>
+ <input type="button" value="Reset" onclick="reset()"/>
+ </form>
+ </div>
+ </div>
+ $other_issues
+ </body>
+</html>
+EoSTV
+}
sub select_form {
my ($num, $voter, $issue_name, $issue_content, $trailer) = @_;
Added: steve/trunk/www/htdocs/images/ballot_bg.png
URL: http://svn.apache.org/viewvc/steve/trunk/www/htdocs/images/ballot_bg.png?rev=1666970&view=auto
==============================================================================
Binary file - no diff available.
Propchange: steve/trunk/www/htdocs/images/ballot_bg.png
------------------------------------------------------------------------------
svn:mime-type = image/png
Added: steve/trunk/www/htdocs/images/dragleft.png
URL: http://svn.apache.org/viewvc/steve/trunk/www/htdocs/images/dragleft.png?rev=1666970&view=auto
==============================================================================
Binary file - no diff available.
Propchange: steve/trunk/www/htdocs/images/dragleft.png
------------------------------------------------------------------------------
svn:mime-type = image/png
Added: steve/trunk/www/htdocs/images/dragright.png
URL: http://svn.apache.org/viewvc/steve/trunk/www/htdocs/images/dragright.png?rev=1666970&view=auto
==============================================================================
Binary file - no diff available.
Propchange: steve/trunk/www/htdocs/images/dragright.png
------------------------------------------------------------------------------
svn:mime-type = image/png
Added: steve/trunk/www/htdocs/images/target.png
URL: http://svn.apache.org/viewvc/steve/trunk/www/htdocs/images/target.png?rev=1666970&view=auto
==============================================================================
Binary file - no diff available.
Propchange: steve/trunk/www/htdocs/images/target.png
------------------------------------------------------------------------------
svn:mime-type = image/png
Added: steve/trunk/www/htdocs/steve_interactive.css
URL: http://svn.apache.org/viewvc/steve/trunk/www/htdocs/steve_interactive.css?rev=1666970&view=auto
==============================================================================
--- steve/trunk/www/htdocs/steve_interactive.css (added)
+++ steve/trunk/www/htdocs/steve_interactive.css Mon Mar 16 11:43:22 2015
@@ -0,0 +1,253 @@
+/*
+#####
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#####
+*/
+body {
+ /*color: #333333;*/
+ font-family: 'Helvetica', arial;
+}
+.wrap {
+ padding: 40px;
+ text-align: center;
+}
+hr {
+ clear: both;
+ margin-top: 40px;
+ margin-bottom: 40px;
+ border: 0;
+ border-top: 1px solid #aaaaaa;
+}
+h1 {
+ font-size: 30px;
+ margin-bottom: 40px;
+}
+p {
+ margin-bottom: 20px;
+}
+.btn {
+ background: #428bca;
+ border: #357ebd solid 1px;
+ border-radius: 3px;
+ color: #fff;
+ display: inline-block;
+ font-size: 14px;
+ padding: 8px 15px;
+ text-decoration: none;
+ text-align: center;
+ min-width: 60px;
+ position: relative;
+ transition: color .1s ease;
+ /* top: 40em;*/
+}
+.btn:hover {
+ background: #357ebd;
+}
+.btn.btn-big {
+ font-size: 18px;
+ padding: 15px 20px;
+ min-width: 100px;
+}
+.btn-close {
+ color: #aaaaaa;
+ font-size: 30px;
+ text-decoration: none;
+ position: absolute;
+ right: 5px;
+ top: 0;
+}
+.btn-close:hover {
+ color: #919191;
+}
+.modal:before {
+ content: "";
+ display: none;
+ background: rgba(0, 0, 0, 0.6);
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 10;
+}
+.modal:target:before {
+ display: block;
+}
+.modal:target .modal-dialog {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0);
+ top: 20%;
+}
+.modal-dialog {
+ background: #fefefe;
+ border: #333333 solid 1px;
+ border-radius: 5px;
+ margin-left: -200px;
+ position: fixed;
+ left: 50%;
+ top: -100%;
+ z-index: 11;
+ width: 640px;
+ word-wrap: break-word;
+ -webkit-transform: translate(0, -500%);
+ -ms-transform: translate(0, -500%);
+ transform: translate(0, -500%);
+ -webkit-transition: -webkit-transform 0.3s ease-out;
+ -moz-transition: -moz-transform 0.3s ease-out;
+ -o-transition: -o-transform 0.3s ease-out;
+ transition: transform 0.3s ease-out;
+}
+.modal-body {
+ padding: 20px;
+ word-wrap: break-word;
+}
+pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+}
+.modal-header,
+.modal-footer {
+ padding: 10px 20px;
+}
+.modal-header {
+ border-bottom: #eeeeee solid 1px;
+}
+.modal-header h2 {
+ font-size: 20px;
+}
+.modal-footer {
+ border-top: #eeeeee solid 1px;
+ text-align: right;
+}
+/*ADDED TO STOP SCROLLING TO TOP*/
+#close {
+ display: none;
+}
+
+.cls {
+ width: 100%;
+ height: 2px;
+ float: left;
+}
+#stv {
+ width: 100%;
+ margin-top: 60px;
+ float: left;
+}
+#ballotbox {
+ width: 460px;
+ min-height: 100px;
+ padding-top: 30px;
+ padding-bottom: 10px;
+ background-image: url(/images/ballot_bg.png), linear-gradient(to bottom, #cedce7 0%,#596a72 100%);
+ background-repeat: repeat;
+ border: 4px groove #333;
+ float: left;
+}
+#candidates {
+ width: 580px;
+ float: left;
+ min-height: 400px;
+ background-repeat: no-repeat;
+}
+
+#ballot {
+ margin: 0 0 0 0;
+}
+.ballotbox {
+ border: 1px dotted #666;
+ padding: 4px;
+ max-width: 300px;
+ min-height: 22px;
+ cursor: move;
+ cursor: grab;
+ font-family: monospace;
+ font-size: 16px;
+ background: linear-gradient(to bottom, #f8f8f8 0%,#dddddd 100%);
+ -khtml-user-drag: element;
+}
+
+.fillerbox {
+ padding: 4px;
+ max-width: 300px;
+ min-height: 26px;
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0 0 0 0;
+ -khtml-user-drag: element;
+ margin-left: 40px;
+}
+
+
+.ballotbox_clist {
+ border: 1px dotted #666;
+ padding: 4px;
+ max-width: 330px;
+ min-height: 22px;
+ cursor: move;
+ cursor: grab;
+ font-family: monospace;
+ font-size: 16px;
+ background: linear-gradient(to bottom, #f8f8f8 0%,#dddddd 100%);
+ -khtml-user-drag: element;
+}
+
+.statement_marker {
+ border: 1px dotted #666;
+ padding: 4px;
+ max-width: 100px;
+ min-height: 12px;
+ font-family: monospace;
+ font-size: 12px;
+ color: #FFF;
+ background: linear-gradient(to bottom, #3b679e 0%,#2b88d9 50%,#207cca 51%,#7db9e8 100%);
+ -khtml-user-drag: element;
+ float: right;
+ right: 0px;
+ top: 0px;
+ position: static;
+}
+
+.statement_marker a {
+ color: #fff;
+}
+
+.ballotSelected {
+ border: 1px dotted #666;
+ padding: 4px;
+ max-width: 300px;
+ min-height: 22px;
+ cursor: move;
+ cursor: grab;
+ font-family: monospace;
+ font-size: 16px;
+ background: linear-gradient(to bottom, #f4f0ad 0%,#eae477 100%);
+}
+.ballotbox:hover {
+ background: linear-gradient(to bottom, #b0d4e3 0%,#88bacf 100%);
+}
+p {
+ max-width: 1100px;
+}
+body, html {
+ width: 1200px;
+ margin: 0 auto;
+}
+
+* {
+ -webkit-user-select: none;
+}
\ No newline at end of file
Added: steve/trunk/www/htdocs/steve_interactive.js
URL: http://svn.apache.org/viewvc/steve/trunk/www/htdocs/steve_interactive.js?rev=1666970&view=auto
==============================================================================
--- steve/trunk/www/htdocs/steve_interactive.js (added)
+++ steve/trunk/www/htdocs/steve_interactive.js Mon Mar 16 11:43:22 2015
@@ -0,0 +1,463 @@
+/* WARNING: This script contains Voodoo! */
+/*
+#####
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#####
+*/
+
+var ballotNames = []
+var ballotChars = []
+var chars = chars? chars : ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] // Corresponding STV letters, in same order as nominees
+var fading = false
+// Cut away unused chars
+while (chars.length > candidates.length) chars.splice(-1,1)
+
+// Make copies for reset
+var candidates_copy = []
+var chars_copy = []
+
+var failover = null;
+
+for (i in candidates) candidates_copy.push(candidates[i])
+for (i in chars) chars_copy.push(chars[i])
+
+
+// Set transfer data during drag'n'drop
+function dragVote(ev) {
+ ev.dataTransfer.setData("Text", ev.target.getAttribute("data"));
+ failover = ev.target.getAttribute("data")
+ if (ballotNames.indexOf(failover) == -1) {
+ document.getElementById('candidates').style.backgroundImage = "url(/images/dragright.png)"
+ document.getElementById('candidates').style.backgroundRepeat = "no-repeat"
+ } else {
+ document.getElementById('candidates').style.backgroundImage = "url(/images/dragleft.png)"
+ document.getElementById('candidates').style.backgroundRepeat = "no-repeat"
+ }
+}
+
+var source, dest
+
+function cancel(ev) {
+ ev.preventDefault()
+}
+
+function reset() {
+ candidates = []
+ chars = []
+ for (i in candidates_copy) candidates.push(candidates_copy[i])
+ for (i in chars_copy) chars.push(chars_copy[i])
+ ballotNames = []
+ ballotChars = []
+ shuffleCandidates();
+
+ drawCandidates()
+ fading = false
+ document.getElementById('ballot').innerHTML = '<img src="/images/target.png" style="margin-left: 100px;" ondrop="event.preventDefault(); dropCandidate(event);"/>';
+ drawList();
+}
+
+// Did we drop a vote on top of another?
+function dropVote(ev, parent) {
+
+ //ev.preventDefault();
+ if (parent || fading) return;
+
+ // Get who we dragged and who we dropped it on
+ source = ev.dataTransfer.getData("Text");
+ dest = parent ? ev.target.parentNode.getAttribute("data") : ev.target.getAttribute("data")
+ if (dest == "UPPER") { dest = ballotNames[0]}
+ if (dest == "LOWER") { dest = ballotNames[ballotNames.length -1] }
+ if (candidates.indexOf(dest) != -1) {
+ alert("Back to school!")
+ }
+
+ // If we didn't drag this onto ourselves, let's initiate the fade-out and swap
+ if (source != dest) {
+ fadeOut(1, "ballot");
+ }
+
+}
+
+function dropComplete(z) {
+ if (fading) {
+ return;
+ }
+ // Get array indices
+ var sid = ballotNames.indexOf(source);
+ var did = ballotNames.indexOf(dest)
+
+ // Splice!
+ if (sid >= 0 && did >= 0) {
+ ballotNames.splice(did, 0, ballotNames.splice(sid, 1)[0])
+ ballotChars.splice(did, 0, ballotChars.splice(sid, 1)[0])
+ } else {
+ alert(source + ":" + dest)
+ }
+ //ev.preventDefault();
+ // Redraw and carry on
+
+ drawList()
+ fadeIn(0, z)
+}
+
+
+// A little shuffle, so we don't all get the same order at first
+function shuffleCandidates() {
+ for (var i = 0; i < candidates.length; i++) {
+
+ // Pick some numbers
+ var sid = parseInt(Math.random()*candidates.length-0.01);
+ var did = parseInt(Math.random()*candidates.length-0.01);
+
+ // Splice!
+ if (sid >= 0 && did >= 0) {
+ candidates.splice(did, 0, candidates.splice(sid, 1)[0])
+ chars.splice(did, 0, chars.splice(sid, 1)[0])
+ }
+ }
+}
+
+
+function drawCandidates() {
+ var box = document.getElementById('candidates')
+ box.innerHTML = "<h3>Candidates:</h3>"
+ for (i in candidates) {
+ var name = candidates[i]
+ var char = chars[i]
+ // Add element and set drag'n'drop + data
+ var outer = document.createElement('div')
+ var inner = document.createElement('span')
+ inner.style.fontFamily = "monospace"
+ inner.innerHTML = char + ": " + name;
+ inner.setAttribute("ondrop", "dropCandidate(event, true)")
+ outer.setAttribute("class", "ballotbox_clist")
+ outer.setAttribute("id", name)
+ outer.setAttribute("data", name)
+ inner.setAttribute("data", name)
+ outer.setAttribute("draggable", "true")
+ outer.setAttribute("ondragstart", "dragVote(event)")
+ outer.appendChild(inner)
+ outer.setAttribute("title", "Drag to move " + name + " to the ballot box")
+ outer.setAttribute("ondrop", "dropCandidate(event, false)")
+ outer.setAttribute("ondragover", "event.preventDefault();")
+ outer.setAttribute("ondragend", "event.preventDefault();")
+ outer.setAttribute("ondragenter", "event.preventDefault();")
+
+ // Does the candidate have a statement? if so, put it on there
+ if (statements[char]) {
+ var statement = document.createElement('div')
+ statement.setAttribute("class", "statement_marker")
+ statement.setAttribute("title", "Click to read " + name + "'s statement")
+ statement.innerHTML = "<a href='#statement_"+char+"'>Statement</a>"
+
+ outer.appendChild(statement)
+
+ var popup = document.createElement("div")
+ popup.setAttribute("class", "modal")
+ popup.setAttribute("id", "statement_" + char)
+ popup.setAttribute("aria-hidden", "true")
+
+ var popupd = document.createElement("div")
+ popupd.setAttribute("class", "modal-dialog")
+ popup.appendChild(popupd)
+
+ var popuph = document.createElement("div")
+ popuph.setAttribute("class", "modal-header")
+ popuph.innerHTML = '<h2>Statement from ' + name + '</h2><a href="#close" class="btn-close" aria-hidden="true">×</a>'
+
+ var popupb = document.createElement("div")
+ popupb.setAttribute("class", "modal-body")
+ popupb.innerHTML = '<pre>' + (statements[char] ? statements[char] : "This candidate has not prepared a statement") +'</pre>'
+
+ var popupf = document.createElement("div")
+ popupf.setAttribute("class", "modal-footer")
+ popupf.innerHTML = '<a href="#close" class="btn">Close statement</a>'
+
+ popupd.appendChild(popuph)
+ popupd.appendChild(popupb)
+ popupd.appendChild(popupf)
+
+ document.getElementsByTagName('body')[0].appendChild(popup)
+ }/* else {
+ var statement = document.createElement('div')
+ statement.setAttribute("class", "statement_marker")
+ statement.style = "background: linear-gradient(to bottom, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%) !important;"
+ statement.style.color = "#666";
+ statement.innerHTML = "<i>No statement</i>"
+
+ outer.appendChild(statement)
+ }*/
+ box.appendChild(outer)
+
+ }
+}
+
+// Did we drop a vote on top of another?
+function dropCandidate(ev) {
+
+ ev.preventDefault();
+ source = ev.dataTransfer.getData("Text");
+ dest = ev.target.getAttribute("data")
+ var z = 0;
+ if (dest == "UPPER") { dest = ballotNames[0]; z = 0}
+ if (dest == "LOWER") { dest = ballotNames[ballotNames.length -1]; z = 1;}
+ if (dest && candidates.indexOf(dest) != -1) {
+ return;
+ }
+ if (ballotNames.indexOf(source) == -1 && candidates.indexOf(source) != -1) {
+ var x = ballotNames.indexOf(dest)
+ x += z
+ if (ballotNames.indexOf(dest) != -1) {
+ ballotNames.splice(x,0,source);
+ ballotChars.splice(x,0,chars[candidates.indexOf(source)]);
+ } else {
+ ballotNames.push(source)
+ ballotChars.push(chars[candidates.indexOf(source)])
+ }
+ chars.splice(candidates.indexOf(source), 1)
+ candidates.splice(candidates.indexOf(source), 1)
+
+ fadeIn(0, "ballot")
+ //ev.preventDefault()
+ drawCandidates();
+ drawList();
+
+ }
+
+}
+
+// Did we drop a vote on top of another?
+function dropBack(ev) {
+ ev.preventDefault();
+ source = ev.dataTransfer.getData("Text");
+ dest = ev.target.getAttribute("data")
+
+ if (dest == "UPPER") { dest = ballotNames[0]}
+ if (dest == "LOWER") { dest = ballotNames[ballotNames.length -1] }
+
+ if ((!dest || candidates.indexOf(dest) != -1) && ballotNames.indexOf(source) != -1) {
+
+ candidates.push(source)
+ chars.push(ballotChars[ballotNames.indexOf(source)])
+ ballotChars.splice(ballotNames.indexOf(source), 1)
+ ballotNames.splice(ballotNames.indexOf(source), 1)
+ drawList();
+ drawCandidates();
+ fadeIn(0, "candidates")
+
+ } else {
+ dest = null
+ source = null
+ }
+}
+
+function showLines(ev) {
+
+ source = ev.dataTransfer.getData("Text");
+ source = source ? source : failover;
+ ev.preventDefault();
+ if (ev.target) {
+ var above = false
+ dest = ev.target.getAttribute("data")
+ var odest = dest;
+ var override = false
+ if (dest == "UPPER") { dest = ballotNames[0]; override = true; above = true;}
+ if (dest == "LOWER") { dest = ballotNames[ballotNames.length-1]; override = true; above= false; }
+ for (i=0;i< document.getElementById('ballot').childNodes.length;i++) {
+ var el = document.getElementById('ballot').childNodes[i]
+ el.style.borderTop = ""
+ el.style.borderBottom = ""
+ }
+ document.getElementById('UPPER').style.borderTop = "none"
+ document.getElementById('LOWER').style.borderBottom = "none"
+ document.getElementById('UPPER').style.borderBottom = "none"
+ document.getElementById('LOWER').style.borderTop = "none"
+ if (ballotNames.indexOf(dest) != -1 && dest != source) {
+ a = ballotNames.indexOf(source);
+ b = ballotNames.indexOf(dest);
+
+ override = false
+ if (a != -1 && !override) {
+
+ if (a > b) {
+ above = true;
+ } else {
+ above = false;
+ }
+ } else {
+ b--;
+ if (b == -1) {
+ above = false;
+ } if (b == ballotNames.length-1) {
+ above = false;
+ }
+ }
+
+ if (((a == -1 || above == true) && odest != "UPPER") || odest == "LOWER") {
+ document.getElementById(odest).style.borderTop = "16px solid #0AF";
+ } else {
+ document.getElementById(odest).style.borderBottom = "16px solid #0AF";
+ }
+ }
+ }
+
+}
+
+function insertAfter(newNode, referenceNode) {
+ referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
+}
+
+function insertBefore(newNode, referenceNode) {
+ referenceNode.parentNode.insertBefore(newNode, referenceNode);
+}
+
+function drawList() {
+
+
+ // Remove drag helper
+ document.getElementById('candidates').style.background = "";
+
+ // Fetch ballot master and clear it
+ var ballot = document.getElementById('ballot')
+ ballot.innerHTML = ""
+ var s = 0;
+
+ // For each nominee, do...
+ for (i in ballotNames) {
+ s++;
+ var el = ballotNames[i];
+ var outer = document.createElement('li');
+ // Set style
+ outer.setAttribute("class", "ballotbox")
+
+ // Above/below cutaway line? If so, draw it
+ if (s == seats) {
+ outer.style.borderBottom = "1px solid #A00"
+ }
+ if (s == seats+1) {
+ outer.style.borderTop = "1px solid #A00"
+ }
+
+ // 'grey out' people below cutaway line
+ if (s > seats) {
+ outer.style.opacity = "0.75"
+ }
+
+ // Add element and set drag'n'drop + data
+ var inner = document.createElement('span')
+ inner.innerHTML = ballotChars[i] + ": " + el;
+ inner.setAttribute("ondrop", "dropVote(event, true)")
+ outer.setAttribute("id", el)
+ outer.setAttribute("data", el)
+ inner.setAttribute("data", el)
+ outer.setAttribute("draggable", "true")
+ outer.setAttribute("ondragstart", "dragVote(event)")
+ outer.setAttribute("ondragenter", "showLines(event)")
+ outer.appendChild(inner)
+ outer.setAttribute("title", "Drag to move " + el + " up or down on the list")
+ outer.setAttribute("ondrop", "dropVote(event, false)")
+
+
+ if (el == source) {
+ outer.style.transform = "scaleY(0)"
+ outer.style.minHeight = "0px"
+ }
+
+ // Add to box
+ ballot.appendChild(outer)
+ }
+
+ // Drop upper and lower filler boxes, so people can drag to the top/bottom of the list as well
+ if (!document.getElementById('UPPER')) {
+ var d = document.createElement('div');
+ d.setAttribute("class", "fillerbox")
+ d.setAttribute("data", "UPPER");
+ d.setAttribute("id", "UPPER");
+ d.setAttribute("ondragenter", "showLines(event)")
+ d.setAttribute("ondrop", "dropVote(event, false)")
+ insertBefore(d, ballot);
+
+ var d = document.createElement('div');
+ d.setAttribute("class", "fillerbox")
+ d.setAttribute("id", "LOWER")
+ d.setAttribute("data", "LOWER");
+ d.setAttribute("ondrop", "dropVote(event, false)")
+ d.setAttribute("ondragenter", "showLines(event)")
+ insertAfter(d, ballot);
+ }
+
+ // Clear any bad lines
+ document.getElementById('UPPER').style.borderTop = "none"
+ document.getElementById('LOWER').style.borderBottom = "none"
+ document.getElementById('UPPER').style.borderBottom = "none"
+ document.getElementById('LOWER').style.borderTop = "none"
+
+ // Set the current STV order
+ document.getElementById('cast').style.width = (chars_copy.length * 8)+ "px"
+ document.getElementById('cast').value = ballotChars.join("")
+}
+
+
+// Fade in/out maneuvres
+function fadeOut(x) {
+ if (source) {
+ if (!x) {
+ x = 1
+ }
+ x -= 0.1
+ document.getElementById(source).setAttribute("class", "ballotSelected")
+ document.getElementById(source).style.opacity = String(x)
+ if (x > 0) {
+ window.setTimeout(function() { fadeOut(x)}, 20)
+ } else {
+ dropComplete("candidates");
+ }
+ }
+}
+
+function fadeIn(x, y) {
+ if (source) {
+ x += 0.1
+ if (x >= 1) {
+ x = 1
+ }
+ document.getElementById(source).style.opacity = String(x)
+ document.getElementById(source).style.height = (x*22) + "px"
+ document.getElementById(source).style.fontSize = (x*16) + "px"
+ document.getElementById(source).style.transform = "scaleY(" + x + ")"
+
+
+ document.getElementById(source).setAttribute("class", "ballotSelected")
+ if (x < 1) {
+ fading = true
+ window.setTimeout(function() { fadeIn(x, y)}, 25)
+
+ } else {
+ window.setTimeout(function() {fading = false }, 250)
+ if (y == "ballot") {
+ document.getElementById(source).setAttribute("class", "ballotbox")
+
+ } else {
+ document.getElementById(source).setAttribute("class", "ballotbox_clist")
+
+ }
+ source = null
+ drawList();
+
+
+ }
+ }
+}