Complete ticket implementation

Closes #7
This commit is contained in:
RunasSudo 2017-12-11 11:52:22 +10:30
parent 6bb2dfddcb
commit 90bb2b6265
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 70 additions and 16 deletions

View File

@ -119,11 +119,23 @@ class ListChoiceQuestion(Question):
def pretty_answer(self, answer):
if len(answer.choices) == 0:
return '(blank votes)'
return ', '.join([self.choices[choice].name for choice in answer.choices])
flat_choices = self.flatten_choices()
return ', '.join([flat_choices[choice].name for choice in answer.choices])
def max_bits(self):
answer = self.answer_type(choices=list(range(len(self.choices))))
return len(EosObject.to_json(EosObject.serialise_and_wrap(answer))) * 8
def flatten_choices(self):
# Return a flat list of Choices, without Tickets
flat_choices = []
for choice in self.choices:
if isinstance(choice, Ticket):
for choice2 in choice.choices:
flat_choices.append(choice2)
else:
flat_choices.append(choice)
return flat_choices
class ApprovalAnswer(Answer):
choices = ListField(IntField())
@ -140,6 +152,16 @@ class PreferentialQuestion(ListChoiceQuestion):
class Choice(EmbeddedObject):
name = StringField()
party = StringField(default=None)
@property
def party_or_ticket(self):
if self.party is not None:
return self.party
else:
ticket = self.recurse_parents(Ticket)
if ticket:
return ticket.name
return None
class Ticket(EmbeddedObject):
name = StringField()

View File

@ -109,10 +109,14 @@
text-align: center;
}
.preferential-choice .party-name {
.preferential-choice .party-name, .preferential-choice .ticket-party-name {
font-style: italic;
}
.ticket .ticket-party-name {
display: none;
}
.ticket > .content > .party-name {
min-height: 2em;
}

View File

@ -26,7 +26,7 @@
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="question-choice-{{ loop.index0 }}" onchange="choicesChanged();">
<label for="question-choice-{{ loop.index0 }}">{{ choice }}</label>
<label for="question-choice-{{ loop.index0 }}">{{ choice.name }}{% if choice.party %} – {{ choice.party }}{% endif %}</label>
</div>
</div>
{% endfor %}

View File

@ -20,7 +20,7 @@
{% for choice in booth.answers[loop.index0].value.choices %}
<div class="item">
<i class="checkmark icon"></i>
<div class="content">{{ question.choices.__getitem__(choice) }}</div>
<div class="content">{{ question.choices.__getitem__(choice).name }}{% if question.choices.__getitem__(choice).party %}{{ question.choices.__getitem__(choice).party }}{% endif %}</div>
</div>
{% else %}
<div class="item">

View File

@ -34,12 +34,22 @@
<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="{{ loop.index0 }}">
<div class="preferential-choice" data-choiceno="{{ flat_choices.indexOf(choice) }}">
<div class="number"></div>
<div class="content">
<div class="candidate-name">{{ choice.name }}</div>
{% if (ticket and choice.party and choice.party != ticket.name) or (not ticket and choice.party) %}<div class="party-name">{{ choice.party }}</div>{% endif %}
{% 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 %}
@ -51,7 +61,7 @@
{% for choice in election.questions.__getitem__(questionNum).choices.impl %}
{% if choice.choices %}
{# Ticket #}
<div class="preferential-choice ticket" data-choiceno="{{ loop.index0 }}">
<div class="preferential-choice ticket" data-ticketno="{{ loop.index0 }}">
<div class="number"></div>
<div class="content">
<div class="party-name">{{ choice.name }}</div>
@ -71,6 +81,7 @@
<script>
var allowAdding = true;
var flat_choices = election.questions.__getitem__(booth.questionNum).flatten_choices();
function choicesChanged() {
// Recalculate numbers
@ -103,10 +114,9 @@
}
// Fill in ballot with previous selections
if (booth.answers[booth.questionNum]) {
for (var selection of booth.answers[booth.questionNum].value.choices) { // Answer already serialised
$(".preferential-choice[data-choiceno=" + selection + "]").detach().appendTo("#question-choices-selected .dragarea");
}
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();
}
@ -117,9 +127,6 @@
if ("dragarea-hint" in el.classList) {
return false;
}
//if ("ticket" in el.classList && !("party-name" in handle.classList)) {
// return false;
//}
if ($.contains(document.querySelector("#question-choices-remaining"), el)) {
return allowAdding;
}
@ -129,16 +136,33 @@
}
);
function breakTicket(ticket) {
ticket.find(".ticket-choices .preferential-choice").each(function(i, el) {
$(el).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();
});
function saveSelections() {
selections = [];
$("#question-choices-selected .preferential-choice").each(function(i, el) {
$("#question-choices-selected .preferential-choice:not(.ticket)").each(function(i, el) {
selections.push(parseInt(el.dataset.choiceno));
});
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
}
</script>

View File

@ -16,11 +16,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% set flat_choices = question.flatten_choices() %}
{% if booth.answers[loop.index0].value.choices.length > 0 %}
<div class="ui ordered list">
{% for choice in booth.answers[loop.index0].value.choices %}
<div class="item">
<div class="content">{{ question.choices.__getitem__(choice) }}</div>
<div class="content">{{ flat_choices[choice].name }}{% if flat_choices[choice].party_or_ticket %} – {{ flat_choices[choice].party_or_ticket }}{% endif %}</div>
</div>
{% endfor %}
</div>

View File

@ -55,6 +55,7 @@
booth = {
"questionNum": 0,
"answers": [],
"q_state": []
};
}
resetBooth();
@ -136,6 +137,7 @@
techDetails = '<p>Technical details: ' + err + '</p>';
}
$("#booth-content").html('<div class="ui error message"><p>We were unable to display the next page of the voting booth. For your security, your ballot selections have been cleared. Please try again. If this problem persists, contact the {{ election.kind }} administrator.</p>' + techDetails + '</div>');
console.error(err);
}
function showTemplate(template, opts, destination) {