From 39dbdabcd39059432405a4d11769f4f4c70213ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Tue, 21 Mar 2023 14:12:09 +0100 Subject: [PATCH] widgets: add MultiSelectWidget (#75655) from combo.apps.dataviz --- .gitignore | 1 + gadjo/forms/__init__.py | 0 gadjo/forms/widgets.py | 46 +++++++++++++++ gadjo/static/css/gadjo.multiselectwidget.scss | 35 +++++++++++ gadjo/static/js/gadjo.multiselectwidget.js | 58 +++++++++++++++++++ .../gadjo/widgets/multiselectwidget.html | 17 ++++++ 6 files changed, 157 insertions(+) create mode 100644 gadjo/forms/__init__.py create mode 100644 gadjo/forms/widgets.py create mode 100644 gadjo/static/css/gadjo.multiselectwidget.scss create mode 100644 gadjo/static/js/gadjo.multiselectwidget.js create mode 100644 gadjo/templates/gadjo/widgets/multiselectwidget.html diff --git a/.gitignore b/.gitignore index 6a59f8d..e9415b3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /gadjo.egg-info /gadjo/locale/fr/LC_MESSAGES/django.mo /gadjo/static/css/gadjo.css +/gadjo/static/css/gadjo.multiselectwidget.css /gadjo/static/css/icons node_modules MANIFEST diff --git a/gadjo/forms/__init__.py b/gadjo/forms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gadjo/forms/widgets.py b/gadjo/forms/widgets.py new file mode 100644 index 0000000..254e17c --- /dev/null +++ b/gadjo/forms/widgets.py @@ -0,0 +1,46 @@ +import django +from django import forms + + +class MultiSelectWidget(forms.MultiWidget): + template_name = 'gadjo/widgets/multiselectwidget.html' + + class Media: + js = ('js/gadjo.multiselectwidget.js',) + css = {'all': ('css/gadjo.multiselectwidget.css',)} + + def __init__(self, attrs=None): + self.attrs = attrs + widgets = [forms.Select(attrs=attrs)] + super().__init__(widgets, attrs) + + def get_context(self, name, value, attrs): + if not isinstance(value, list): + value = [value] + + self.widgets = [] + for _ in range(max(len(value), 1)): + self.widgets.append(forms.Select(attrs=self.attrs, choices=self.choices)) + + # all subwidgets must have the same name + if django.VERSION >= (3, 1): + self.widgets_names = [''] * len(self.widgets) + return super().get_context(name, value, attrs) + else: + context = super().get_context(name, value, attrs) + subwidgets = context['widget']['subwidgets'] + for widget in subwidgets: + widget['name'] = widget['name'].rsplit('_', 1)[0] + return context + + def decompress(self, value): + return value or [] + + def value_from_datadict(self, data, files, name): + values = [x for x in data.getlist(name) if x] + + # remove duplicates while keeping order + return list(dict.fromkeys(values)) + + def id_for_label(self, id_): + return id_ diff --git a/gadjo/static/css/gadjo.multiselectwidget.scss b/gadjo/static/css/gadjo.multiselectwidget.scss new file mode 100644 index 0000000..641dd93 --- /dev/null +++ b/gadjo/static/css/gadjo.multiselectwidget.scss @@ -0,0 +1,35 @@ +.gadjo-multi-select-widget { + &--field { + margin-bottom: 0.2em; + } + + &--select-button-container { + display: flex; + gap: 0.5em; + } + + &--field { + select { + min-width: 0; + } + + button { + margin-top: auto; + margin-bottom: auto; + } + + &:first-of-type .gadjo-multi-select-widget--button-remove { + display: none; + } + } + + &--button-add::before { + content: "\f067"; /* plus */ + font-family: FontAwesome; + } + + &--button-remove::before { + content: "\f068"; /* minus */ + font-family: FontAwesome; + } +} diff --git a/gadjo/static/js/gadjo.multiselectwidget.js b/gadjo/static/js/gadjo.multiselectwidget.js new file mode 100644 index 0000000..1c05872 --- /dev/null +++ b/gadjo/static/js/gadjo.multiselectwidget.js @@ -0,0 +1,58 @@ +const multiSelectWidget = (function () { + const addRow = function () { + const widget = this.closest('.gadjo-multi-select-widget') + event.preventDefault() + + /* get last row node */ + const rows = widget.querySelectorAll('.gadjo-multi-select-widget--field') + const lastRow = rows[rows.length - 1] + + /* clone the row */ + const newRow = lastRow.cloneNode(true) + + /* set new label and ids */ + const rowLabel = widget.dataset.rowLabel + const newLabel = rowLabel + ' ' + rows.length + newRow.querySelector('label').textContent = newLabel + + const rowId = widget.dataset.rowId + const newId = rowId + '_' + rows.length + newRow.querySelector('label').setAttribute('for', newId) + newRow.querySelector('select').setAttribute('id', newId) + + /* add new row after the last row */ + lastRow.parentNode.insertBefore(newRow, lastRow.nextSibling) + + const removeButton = newRow.querySelector('.gadjo-multi-select-widget--button-remove') + removeButton.addEventListener('click', removeRow) + } + + const removeRow = function (event) { + event.preventDefault() + const field = this.closest('.content') + let row = this.closest('.gadjo-multi-select-widget--field') + row.remove() + field.dispatchEvent(new Event('change')) + } + + const init = function (container) { + const widgets = container.querySelectorAll('.gadjo-multi-select-widget') + if (!widgets.length) return + + widgets.forEach(function (widget) { + const deletBtn = widget.querySelector('.gadjo-multi-select-widget--button-remove') + const addBtn = widget.querySelector('.gadjo-multi-select-widget--button-add') + + addBtn.removeEventListener('click', addRow) + addBtn.addEventListener('click', addRow) + deletBtn.removeEventListener('click', removeRow) + deletBtn.addEventListener('click', removeRow) + }) + } + + return { + init, + } +})() + +window.addEventListener('DOMContentLoaded', () => multiSelectWidget.init(document)) diff --git a/gadjo/templates/gadjo/widgets/multiselectwidget.html b/gadjo/templates/gadjo/widgets/multiselectwidget.html new file mode 100644 index 0000000..36b937f --- /dev/null +++ b/gadjo/templates/gadjo/widgets/multiselectwidget.html @@ -0,0 +1,17 @@ +{% load i18n %} + +
+
+ {% for widget in widget.subwidgets %} +
+ +
+ {% include widget.template_name %} + +
+
+ {% endfor %} +
+ + +
-- 2.39.2