www/htdocs/steve_interactive.js (342 lines of code) (raw):

/* 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 resetList() { 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, Math.random()) } // 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", Math.random()) //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", Math.random()) } 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") var no = document.createElement('div'); no.setAttribute("class", "ballotNumber") no.innerHTML = (s) // 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.style.left = "35px" inner.style.maxWidth = "300px" inner.style.maxHeight = "60px" inner.style.overflow = "hidden" 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(no) 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.opacity = "0" } // 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('vote').style.width = (chars_copy.length * 8)+ "px" document.getElementById('vote').value = ballotChars.join("") } // Fade in/out maneuvres function fadeOut(x) { if (source) { if (!x) { x = 1 } if (fading) { return; } 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"); } } } var gz = 0; function fadeIn(x, y, z) { if (source) { if (x == 0) { gz = z; } if (z != gz) { return; } x += 0.1 if (x >= 1) { x = 1 } document.getElementById(source).style.opacity = String(x) document.getElementById(source).setAttribute("class", "ballotSelected") if (x < 1) { fading = true window.setTimeout(function() { fadeIn(x, y, z)}, 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(); } } }