On rajoute un formulaire drag & drop pour les votes à classement

This commit is contained in:
Tom Hubrecht 2021-04-20 10:00:03 +02:00
parent 5c7d992dc0
commit 6d29267ef8
2 changed files with 173 additions and 11 deletions

View file

@ -4,29 +4,105 @@
{% block extra_head %} {% block extra_head %}
<script> <script>
const nb_options = {{ nb_options }};
var ranks_used = 1;
var rank_zones = new Array(nb_options + 1);
var ranked = 0;
function getLabelText($input) {
var label = $input.closest('.field').querySelector('.label label').innerHTML;
return label.substring(0, label.length - 1).trim();
}
// Fonction auxilliaires gérant le drag & drop
function dragstart_handler(event) {
event.dataTransfer.setData('text/plain', event.target.id);
}
function dragover_handler(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}
function drop_handler(event) {
event.preventDefault();
// On récupère l'id de la tuile à déplacer
const data = event.dataTransfer.getData('text/plain');
var $target = event.target.closest('.drop-zone');
if ($target.id == 'rank-add') {
ranks_used += 1;
// Si on a autant de rangs que d'option, on cache le bouton +
if (ranks_used == nb_options) {
$target.parentElement.classList.add('is-hidden');
}
$target = rank_zones[ranks_used];
$target.parentElement.classList.remove('is-hidden');
}
// On déplace l'option
var $tile = document.getElementById(data);
$target.appendChild($tile);
// On enregistre le rang dans le formulaire
const rank = $target.dataset.rank;
var $input = document.getElementById($tile.dataset.input);
$input.value = rank;
// On modifie ranked pour savoir combien d'option on a classé
ranked += ($target.id == 'unranked' ? -1 : 1);
// On décale pour éviter les rangs vides
for (let i = 1; i <= ranks_used && i < nb_options; i++) {
// On a au moins le tag avec le numéro du rang
if (rank_zones[i].childElementCount == 1) {
while (rank_zones[i + 1].childElementCount > 1) {
let $tile = rank_zones[i + 1].lastChild;
let $input = document.getElementById($tile.dataset.input);
$input.value = i.toString();
rank_zones[i].append($tile);
}
}
}
// On cache la dernière zone si elle est vide, mias on garde au moins la première
if (ranks_used > 1 && rank_zones[ranks_used].childElementCount == 1) {
// On affiche le bouton + si besoin
if (ranks_used == nb_options) {
let $add_rank = document.getElementById('rank-add');
$add_rank.parentElement.classList.remove('is-hidden')
}
rank_zones[ranks_used].parentElement.classList.add('is-hidden');
ranks_used -= 1;
}
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Affiche le modal et remplit le récapitulatif
document.getElementById('confirm-button').addEventListener('click', () => { document.getElementById('confirm-button').addEventListener('click', () => {
var $modal_body = document.getElementById('modal-body'); var $modal_body = document.getElementById('modal-body');
var ranks = new Array({{ nb_options }} + 1); var ranks = new Array(nb_options + 1);
(document.querySelectorAll('.control .input') || []).forEach($input => { (document.querySelectorAll('.control .input') || []).forEach($input => {
var rank = parseInt($input.value); var rank = parseInt($input.value) || nb_options;
if ($input.value == '') {
rank = {{ nb_options }};
}
var option = $input.closest('.field').querySelector('.label label').innerHTML; var option = getLabelText($input)
option = option.substring(0, option.length - 1).trim();
if (rank > 0 && rank <= {{ nb_options }}) { if (rank > 0 && rank <= nb_options) {
ranks[rank] = (ranks[rank] || []).concat([option]); // TODO: chercher une meilleure méthode ranks[rank] = (ranks[rank] || []).concat([option]);
} else {
ranks[nb_options] = (ranks[nb_options] || []).concat([option]);
} }
}); });
var table_rows = ''; var table_rows = '';
for (let i = 1; i <= {{ nb_options }}; i++) { for (let i = 1; i <= nb_options; i++) {
var option_list = ''; var option_list = '';
if (!(typeof ranks[i] === 'undefined')) { if (!(typeof ranks[i] === 'undefined')) {
@ -49,7 +125,55 @@
${table_rows} ${table_rows}
</tbody> </tbody>
</table>`; </table>`;
});
// Change le mode de remplissge de formulaire (input vs drag & drop)
document.getElementById('change-method').addEventListener('click', () => {
var $hide = document.getElementById('hide-form');
var $drag_zone = document.getElementById('drag-zone');
var $method_button = document.getElementById('change-method');
// On échange ce qui est visible
$hide.classList.toggle('is-hidden');
$drag_zone.classList.toggle('is-hidden');
if ($hide.classList.contains('is-hidden')) {
$method_button.innerHTML = "{% trans "Utiliser le formulaire classique" %}";
} else {
$method_button.innerHTML = "{% trans "Utiliser le cliquer-déposer" %}";
}
});
// Initialise les éléments pour le formulaire interactif
var $unranked = document.getElementById('unranked');
for (let i = 1; i <= nb_options; i++) {
rank_zones[i] = document.getElementById(`rank-${i}`);
}
(document.querySelectorAll('.control .input') || []).forEach($input => {
var option = getLabelText($input);
// On créé une tuile avec le nom de l'option
var $tile = document.createElement('div');
$tile.classList.add('tile', 'is-parent', 'is-flex-grow-0');
$tile.id = `tile-${$input.id}`;
$tile.dataset.input = $input.id;
$tile.innerHTML = `<p class="tile is-child notification is-primary">${option}</p>`;
$tile.setAttribute('draggable', true);
$tile.addEventListener('dragstart', dragstart_handler);
// On rajoute la tuile dans le classement ou dans les non classées
const rank = parseInt($input.value);
if (!(typeof rank === 'undefined') && rank > 0 && rank <= nb_options) {
rank_zones[rank].appendChild($tile);
} else {
$unranked.appendChild($tile);
// On enlève les valeurs non règlementaires
$input.value = '';
}
}); });
}); });
@ -58,5 +182,42 @@
{% block vote_form %} {% block vote_form %}
{% include "forms/formset.html" %} <div class="tile is-ancestor">
<div class="tile is-parent">
<a id="change-method" class="tile is-child notification has-text-centered is-size-5 is-primary">
{% trans "Utiliser le formulaire classique" %}
</a>
</div>
</div>
<div id="drag-zone" class="tile is-ancestor">
<div class="tile is-vertical">
{% for i in range_options %}
<div class="tile is-parent is-flex-grow-0 {% if not forloop.first %}is-hidden{% endif %}" ondrop="drop_handler(event)" ondragover="dragover_handler(event)">
<div id="rank-{{ i }}" class="tile is-child notification is-primary is-light pl-3 pt-3 drop-zone" data-rank="{{ i }}">
<div class="tag is-medium is-primary">{% blocktrans %}Rang {{ i }}{% endblocktrans %}</div>
</div>
</div>
{% endfor %}
<div class="tile is-parent is-flex-grow-0" ondrop="drop_handler(event)" ondragover="dragover_handler(event)">
<div id="rank-add" class="tile is-child notification has-text-centered drop-zone">
<span class="icon-text subtitle has-text-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>{% trans "Ajouter un rang" %}</span>
</span>
</div>
</div>
</div>
<div id="unranked" class="tile is-vertical drop-zone" ondrop="drop_handler(event)" ondragover="dragover_handler(event)" data-rank="">
</div>
</div>
<div id="hide-form" class="block is-hidden">
{% include "forms/formset.html" %}
</div>
{% endblock %} {% endblock %}

View file

@ -529,6 +529,7 @@ class VoteView(OpenElectionOnlyMixin, DetailView):
context["q_index"] = questions.index(self.object) + 1 context["q_index"] = questions.index(self.object) + 1
context["q_total"] = len(questions) context["q_total"] = len(questions)
context["nb_options"] = self.object.options.count() context["nb_options"] = self.object.options.count()
context["range_options"] = range(1, context["nb_options"] + 1)
context["vote_rule"] = VOTE_RULES[self.object.type].format(**context) context["vote_rule"] = VOTE_RULES[self.object.type].format(**context)
return context return context