249 lines
8.8 KiB
HTML
249 lines
8.8 KiB
HTML
{#
|
|
Eos - Verifiable elections
|
|
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
#}
|
|
|
|
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
|
|
|
<p><small>
|
|
Vote for
|
|
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
|
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
|
|
{% else %}
|
|
between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }}
|
|
{% endif %}
|
|
choices. Click and drag the choices from the grey box to the blue box in order from most-preferred to least-preferred. It is in your best interests to vote for as many choices as you can.
|
|
</small></p>
|
|
|
|
<div id="question-choices-selected" class="preferential-choices">
|
|
<div style="color: #3465a4;">Options voted for:</div>
|
|
<div class="dragarea">
|
|
<div class="dragarea-hint"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ui hidden message" id="message-max-choices">
|
|
<p>You have now selected the maximum allowed number of choices. If you wish to add different choices, you must deselect some choices by dragging them from the blue box back to the grey box.</p>
|
|
</div>
|
|
<div class="ui hidden error message" id="message-too-many-choices">
|
|
<p>You have selected more than the maximum allowed number of choices. To proceed, you must deselect some choices by dragging them from the blue box back to the grey box.</p>
|
|
</div>
|
|
|
|
{% set flat_choices = election.questions.__getitem__(questionNum).flatten_choices() %}
|
|
|
|
{% macro printchoice(choice, ticket=None) %}
|
|
<div class="preferential-choice" data-choiceno="{{ flat_choices.indexOf(choice) }}">
|
|
<div class="number">
|
|
<select class="ui dropdown">
|
|
<option selected></option>
|
|
{% for i in range(flat_choices|length) %}
|
|
<option>{{ i + 1 }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="content">
|
|
<div class="candidate-name">{{ choice.name }}</div>
|
|
{% if choice.party %}
|
|
{% if (ticket and choice.party != ticket.name) or not ticket %}
|
|
<div class="party-name">{{ choice.party }}</div>
|
|
{% else %}
|
|
<div class="ticket-party-name">{{ choice.party }}</div>
|
|
{% endif %}
|
|
{% elif ticket %}
|
|
<div class="ticket-party-name">{{ ticket.name }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
<div id="question-choices-remaining" class="preferential-choices">
|
|
<div>Options not yet voted for:</div>
|
|
<div class="dragarea">
|
|
<div class="dragarea-hint"></div>
|
|
{% for choice in election.questions.__getitem__(questionNum).randomised_choices().impl %}
|
|
{% if choice.choices %}
|
|
{# Ticket #}
|
|
<div class="preferential-choice ticket">
|
|
<div class="number">
|
|
<select class="ui dropdown">
|
|
<option selected></option>
|
|
{% for i in range(flat_choices|length) %}
|
|
<option>{{ i + 1 }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="content">
|
|
<div class="party-name">{{ choice.name }}</div>
|
|
<div class="ticket-choices">
|
|
{% for choice2 in choice.choices.impl %}
|
|
{{ printchoice(choice2, choice) }}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
{{ printchoice(choice) }}
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var allowAdding = true;
|
|
var flat_choices = election.questions.__getitem__(booth.questionNum).flatten_choices();
|
|
|
|
function choicesChanged() {
|
|
// Recalculate numbers
|
|
$(".preferential-choices .preferential-choice .number select").val("");
|
|
var selectedChoices = $("#question-choices-selected .dragarea > .preferential-choice > .number select");
|
|
selectedChoices.each(function(i, el) {
|
|
$(el).val(i + 1);
|
|
});
|
|
var selectedCandidates = $("#question-choices-selected .preferential-choice:not(.ticket) .number select");
|
|
if (selectedCandidates.length >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
|
// Prevent making any more selections
|
|
allowAdding = false;
|
|
|
|
$("#question-choices-remaining .preferential-choice .number select").prop("disabled", true);
|
|
|
|
if (selectedCandidates.length > election.questions.__getitem__(booth.questionNum).max_choices) {
|
|
// Prevent progression
|
|
$(".primary.button").addClass("disabled");
|
|
$("#message-too-many-choices").removeClass("hidden");
|
|
} else {
|
|
$(".primary.button").removeClass("disabled");
|
|
$("#message-max-choices").removeClass("hidden");
|
|
}
|
|
} else {
|
|
allowAdding = true;
|
|
|
|
$("#question-choices-remaining .preferential-choice .number select").prop("disabled", false);
|
|
|
|
$(".primary.button").removeClass("disabled");
|
|
$("#message-max-choices").addClass("hidden");
|
|
$("#message-too-many-choices").addClass("hidden");
|
|
}
|
|
}
|
|
|
|
// Fill in ballot with previous selections
|
|
if (booth.q_state[booth.questionNum]) {
|
|
$("#question-choices-selected .dragarea").html(booth.q_state[booth.questionNum][0]);
|
|
$("#question-choices-remaining .dragarea").html(booth.q_state[booth.questionNum][1]);
|
|
choicesChanged();
|
|
}
|
|
|
|
// =============
|
|
// DRAG AND DROP
|
|
// =============
|
|
|
|
var dragulaChoices = dragula(
|
|
[document.querySelector("#question-choices-selected .dragarea"), document.querySelector("#question-choices-remaining .dragarea")].concat([].slice.apply(document.querySelectorAll(".ticket-choices"))),
|
|
{
|
|
moves: function(el, source, handle, sibling) {
|
|
if ("dragarea-hint" in el.classList) {
|
|
return false;
|
|
}
|
|
if ($.contains(document.querySelector("#question-choices-remaining"), el)) {
|
|
return allowAdding;
|
|
}
|
|
return true;
|
|
},
|
|
mirrorContainer: document.querySelector("#question-choices-remaining")
|
|
}
|
|
);
|
|
|
|
function breakTicket(ticket) {
|
|
//ticket.find(".ticket-choices .preferential-choice").each(function(i, el) {
|
|
// $(el).detach().insertAfter(ticket);
|
|
//});
|
|
ticket.find(".ticket-choices .preferential-choice").detach().insertAfter(ticket);
|
|
ticket.remove();
|
|
}
|
|
|
|
dragulaChoices.on("drop", function(el, target, source, sibling) {
|
|
// If the source or target is a ticket, break the ticket
|
|
if ($(source).parents(".ticket").length > 0) {
|
|
breakTicket($(source).parents(".ticket").first())
|
|
}
|
|
if ($(target).parents(".ticket").length > 0) {
|
|
breakTicket($(target).parents(".ticket").first())
|
|
}
|
|
|
|
choicesChanged();
|
|
});
|
|
|
|
// ===============
|
|
// DROP DOWN BOXES
|
|
// ===============
|
|
|
|
$(".preferential-choices .preferential-choice .number select").change(function(evt) {
|
|
var index = parseInt($(this).val());
|
|
|
|
var choiceEl = $(this).parents(".preferential-choice").first();
|
|
var ticket = null;
|
|
|
|
// If the source is within a ticket, break the ticket afterwards
|
|
// (It is impossible for the target to be within a ticket)
|
|
if (choiceEl.parents(".ticket").length > 0) {
|
|
ticket = choiceEl.parents(".ticket").first();
|
|
}
|
|
|
|
// Simulate drag-and-drop
|
|
var selectedChoices = $("#question-choices-selected .dragarea > .preferential-choice");
|
|
if ($(this).val() === "") {
|
|
// Deselect
|
|
choiceEl.detach().appendTo($("#question-choices-remaining .dragarea"));
|
|
} else if (index > selectedChoices.length) {
|
|
// Append to end
|
|
choiceEl.detach().appendTo($("#question-choices-selected .dragarea"));
|
|
} else {
|
|
// Insert in the middle
|
|
choiceEl.detach().insertBefore(selectedChoices[index - 1]);
|
|
}
|
|
|
|
// Now break the ticket
|
|
if (ticket !== null) {
|
|
breakTicket(ticket);
|
|
}
|
|
|
|
choicesChanged();
|
|
});
|
|
|
|
// =======
|
|
// GENERAL
|
|
// =======
|
|
|
|
function saveSelections() {
|
|
selections = [];
|
|
$("#question-choices-selected .preferential-choice:not(.ticket)").each(function(i, el) {
|
|
selections.push(parseInt(el.dataset.choiceno));
|
|
});
|
|
|
|
if (selections.length < election.questions.__getitem__(booth.questionNum).min_choices) {
|
|
if (!window.confirm('You have selected fewer than the minimum required number of choices. If you proceed to cast this ballot, it will **NOT** be counted. If this was not your intention, please click the "Cancel" button below now, and correct your selections.')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
answer = eosjs.eos.base.election.__all__.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
|
|
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
|
|
|
booth.q_state[booth.questionNum] = [$("#question-choices-selected .dragarea").html(), $("#question-choices-remaining .dragarea").html()]; // wew lad
|
|
|
|
return true;
|
|
}
|
|
</script>
|