Eos/eosweb/core/static/nunjucks/question/preferential/selections_make.html

283 lines
11 KiB
HTML

{#
Eos - Verifiable elections
Copyright © 2017-2019 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>
{% if election.questions.__getitem__(questionNum).description %}
<p>{{ election.questions.__getitem__(questionNum).description | urlize | replace('<a ', '<a target="_blank" ') | safe }}</p>
{% endif %}
<p><small>
Vote for
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
{% elif election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices - 1 %}
either {{ election.questions.__getitem__(questionNum).min_choices }} or {{ election.questions.__getitem__(questionNum).max_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").first().children(".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 necessary
if ($(source).parents(".ticket").length > 0) {
// This is a candidate dragged out of a ticket – break the ticket
breakTicket($(source).parents(".ticket").first());
}
if ($(target).parents(".ticket").length > 0) {
// This is a candidate/ticket dragged into a .ticket-choices
// Was it dragged to the first or last position?
if (sibling == null) {
// Dragged to the end – just move it away
$(el).detach().insertAfter($(target).parents(".ticket").first());
} else if(sibling == $(target).children(".preferential-choice:not(.ticket)").first().get(0)) {
// Dragged to the beginning – just move it away
$(el).detach().insertBefore($(target).parents(".ticket").first());
} else {
// Dragged into the middle – break the ticket
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.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
booth.answers[booth.questionNum] = eosjs.eos.core.objects.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>
<div id="selections-make-help">
<p>This is a preferential voting question. You are required to vote for
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
exactly {{ election.questions.__getitem__(questionNum).min_choices }}
{% elif election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices - 1 %}
either {{ election.questions.__getitem__(questionNum).min_choices }} or {{ election.questions.__getitem__(questionNum).max_choices }}
{% else %}
between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }}
{% endif %}
choices.</p>
<p>If using a desktop computer or a laptop, use your mouse to click and drag the choices from the grey box to the blue box.</p>
<p>If using a smartphone or tablet, use your finger to drag the choices from the grey box to the blue box.</p>
<p>You should order your choices inside the blue box in order from most-preferred (at the top) to least-preferred (at the bottom).</p>
<p>It is in your best interests to vote for as many choices as you can.</p>
<p>Once you are satisfied with your selections, click the ‘Continue’ button.</p>
<p>Click the ‘OK’ button below to close this help screen.</p>
<p>If you require further assistance, contact your election administrator.</p>
</div>