This repository has been archived on 2021-05-25. You can view files and clone it, but cannot push or open issues or pull requests.
pyRCV2/html/blt/index.js

273 lines
7.1 KiB
JavaScript

/*
pyRCV2: Preferential vote counting
Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
let candidates = [];
let ballots = [];
let inpFile = document.getElementById('inpFile');
let txtBallotValue = document.getElementById('txtBallotValue');
let tblBallot = document.getElementById('tblBallot');
let selBallots = document.getElementById('selBallots');
selBallots.value = 'new';
function initBallot() {
// Init ballot table
tblBallot.innerHTML = '';
for (let candidate of candidates) {
let elTr = document.createElement('tr');
let elTd = document.createElement('td');
let elInput = document.createElement('input');
elInput.type = 'number';
elInput.min = '1';
elInput.style.width = '3em';
elTd.appendChild(elInput);
elTr.appendChild(elTd);
// Add listener
elInput.addEventListener('keyup', function(evt) {
if (evt.key === 'Enter') {
evt.preventDefault();
clickSave();
}
});
elTd = document.createElement('td');
elTd.innerText = candidate;
elTr.appendChild(elTd);
tblBallot.appendChild(elTr);
}
}
function clickSave() {
// Save ballot
let ballot = {'value': txtBallotValue.value, 'preferences': []};
for (let elInput of tblBallot.querySelectorAll('input')) {
ballot['preferences'].push(elInput.value);
}
if (selBallots.value === 'new') {
// New ballot
ballots.push(ballot);
// Add new ballot <option>
let elOption = document.createElement('option');
elOption.innerText = 'Ballot ' + ballots.length;
selBallots.insertBefore(elOption, selBallots.selectedOptions[0]);
} else if (selBallots.selectedIndex >= 0) {
// Editing existing ballot
ballots[selBallots.selectedIndex] = ballot;
// Advance to next ballot
selBallots.selectedIndex += 1;
}
// Update ballot entry
changeBallot();
}
function changeBallot() {
if (selBallots.value === 'new') {
// Clear input
for (let elInput of tblBallot.querySelectorAll('input')) {
elInput.value = '';
}
txtBallotValue.value = '1';
} else if (selBallots.selectedIndex >= 0) {
// Update input
let ballot = ballots[selBallots.selectedIndex];
let i = 0;
for (let elInput of tblBallot.querySelectorAll('input')) {
elInput.value = ballot['preferences'][i];
i += 1;
}
txtBallotValue.value = ballot['value'];
}
// Move cursor to first input box
tblBallot.querySelector('input').focus();
tblBallot.querySelector('input').select();
}
let openMode = 'json';
function clickOpenJSON() {
openMode = 'json';
inpFile.value = '';
inpFile.click();
}
function clickImportBLT() {
openMode = 'blt';
inpFile.value = '';
inpFile.click();
}
async function changeInpFile() {
if (inpFile.value !== '') {
if (openMode === 'json') {
// Open JSON file
let text = await inpFile.files[0].text();
let obj = JSON.parse(text);
// Load data
candidates = obj['candidates'];
ballots = obj['ballots'];
} else if (openMode === 'blt') {
// Open BLT file
let text = await inpFile.files[0].text();
// Import data
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.StringNum);
let election = py.pyRCV2.blt.readBLT(text);
candidates = [];
for (let candidate of election.candidates) {
candidates.push(candidate.py_name);
}
ballots = [];
for (let ballot of election.ballots) {
let js_ballot = {'value': ballot.value.__str__(), 'preferences': []};
for (let i = 0; i < candidates.length; i++) {
js_ballot['preferences'].push('');
}
// Add preference numbers
for (let i = 0; i < ballot.preferences.length; i++) {
candidate = ballot.preferences[i];
js_ballot['preferences'][election.candidates.indexOf(candidate)] = '' + (i + 1);
}
ballots.push(js_ballot);
}
}
// Go to ballot entry screen
document.getElementById('bltMain').style.display = '';
document.getElementById('divEditCandidates').style.display = 'none';
// Update ballot entry
initBallot();
// Update ballot list
selBallots.innerHTML = '<option value="new" selected>-- New Ballot --</option>';
selBallots.value = 'new';
for (let i = 0; i < ballots.length; i++) {
let elOption = document.createElement('option');
elOption.innerText = 'Ballot ' + (i + 1);
selBallots.insertBefore(elOption, selBallots.selectedOptions[0]);
}
inpFile.value = '';
}
}
function clickSaveJSON() {
let result = {
'candidates': candidates,
'ballots': ballots,
};
// Download file
let url = URL.createObjectURL(new File([JSON.stringify(result)], 'ballots.json', {type: 'application/json'}));
let elA = document.createElement('a');
elA.href = url;
elA.download = '';
elA.click();
}
function clickExportBLT() {
let election = py.pyRCV2.model.Election();
let result = window.prompt('Enter the number of seats to elect:', '1');
if (result !== null) {
election.seats = parseInt(result);
} else {
return;
}
result = window.prompt('Enter the name of the election:');
if (result !== null) {
election.py_name = result;
} else {
return;
}
for (let candidate of candidates) {
let py_candidate = py.pyRCV2.model.Candidate(candidate);
election.candidates.push(py_candidate);
}
// Process ballots
for (let ballot of ballots) {
let py_ballot = py.pyRCV2.model.Ballot(py.pyRCV2.numbers.StringNum(ballot['value']), []);
for (let i = 1; i < candidates.length + 1; i++) {
let candidate = null;
for (let j = 0; j < candidates.length; j++) {
if (ballot.preferences[j].trim() === '' + i) {
candidate = j;
break;
}
}
if (candidate !== null) {
py_ballot.preferences.push(election.candidates[candidate]);
} else {
// No such preference
break;
}
}
election.ballots.push(py_ballot);
}
// Download file
let url = URL.createObjectURL(new File([py.pyRCV2.blt.writeBLT(election)], 'election.blt', {type: 'application/json'}));
let elA = document.createElement('a');
elA.href = url;
elA.download = '';
elA.click();
}
function clickEditCandidates() {
document.getElementById('bltMain').style.display = 'none';
document.getElementById('divEditCandidates').style.display = '';
document.getElementById('txtCandidates').value = candidates.join('\n');
}
function clickSaveCandidates() {
candidates = [];
for (let candidate of document.getElementById('txtCandidates').value.split('\n')) {
if (candidate.trim() !== '') {
candidates.push(candidate.trim());
}
}
// Update ballot entry
initBallot();
changeBallot();
document.getElementById('bltMain').style.display = '';
document.getElementById('divEditCandidates').style.display = 'none';
}