mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-13 15:15:03 +00:00
77
core/static/bundled/core/dynamic-formset-index.ts
Normal file
77
core/static/bundled/core/dynamic-formset-index.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
interface Config {
|
||||||
|
/**
|
||||||
|
* The prefix of the formset, in case it has been changed.
|
||||||
|
* See https://docs.djangoproject.com/fr/stable/topics/forms/formsets/#customizing-a-formset-s-prefix
|
||||||
|
*/
|
||||||
|
prefix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/style/useNamingConvention: It's the DOM API naming
|
||||||
|
type HTMLFormInputElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
||||||
|
|
||||||
|
document.addEventListener("alpine:init", () => {
|
||||||
|
/**
|
||||||
|
* Alpine data element to allow the dynamic addition of forms to a formset.
|
||||||
|
*
|
||||||
|
* To use this, you need :
|
||||||
|
* - an HTML element containing the existing forms, noted by `x-ref="formContainer"`
|
||||||
|
* - a template containing the empty form
|
||||||
|
* (that you can obtain jinja-side with `{{ formset.empty_form }}`),
|
||||||
|
* noted by `x-ref="formTemplate"`
|
||||||
|
* - a button with `@click="addForm"`
|
||||||
|
* - you may also have one or more buttons with `@click="removeForm(element)"`,
|
||||||
|
* where `element` is the HTML element containing the form.
|
||||||
|
*
|
||||||
|
* For an example of how this is used, you can have a look to
|
||||||
|
* `counter/templates/counter/product_form.jinja`
|
||||||
|
*/
|
||||||
|
Alpine.data("dynamicFormSet", (config?: Config) => ({
|
||||||
|
init() {
|
||||||
|
this.formContainer = this.$refs.formContainer as HTMLElement;
|
||||||
|
this.nbForms = this.formContainer.children.length as number;
|
||||||
|
this.template = this.$refs.formTemplate as HTMLTemplateElement;
|
||||||
|
const prefix = config?.prefix ?? "form";
|
||||||
|
this.$root
|
||||||
|
.querySelector(`#id_${prefix}-TOTAL_FORMS`)
|
||||||
|
.setAttribute(":value", "nbForms");
|
||||||
|
},
|
||||||
|
|
||||||
|
addForm() {
|
||||||
|
this.formContainer.appendChild(document.importNode(this.template.content, true));
|
||||||
|
const newForm = this.formContainer.lastElementChild;
|
||||||
|
const inputs: NodeListOf<HTMLFormInputElement> = newForm.querySelectorAll(
|
||||||
|
"input, select, textarea",
|
||||||
|
);
|
||||||
|
for (const el of inputs) {
|
||||||
|
el.name = el.name.replace("__prefix__", this.nbForms.toString());
|
||||||
|
el.id = el.id.replace("__prefix__", this.nbForms.toString());
|
||||||
|
}
|
||||||
|
const labels: NodeListOf<HTMLLabelElement> = newForm.querySelectorAll("label");
|
||||||
|
for (const el of labels) {
|
||||||
|
el.htmlFor = el.htmlFor.replace("__prefix__", this.nbForms.toString());
|
||||||
|
}
|
||||||
|
inputs[0].focus();
|
||||||
|
this.nbForms += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeForm(container: HTMLDivElement) {
|
||||||
|
container.remove();
|
||||||
|
this.nbForms -= 1;
|
||||||
|
// adjust the id of remaining forms
|
||||||
|
for (let i = 0; i < this.nbForms; i++) {
|
||||||
|
const form: HTMLDivElement = this.formContainer.children[i];
|
||||||
|
const inputs: NodeListOf<HTMLFormInputElement> = form.querySelectorAll(
|
||||||
|
"input, select, textarea",
|
||||||
|
);
|
||||||
|
for (const el of inputs) {
|
||||||
|
el.name = el.name.replace(/\d+/, i.toString());
|
||||||
|
el.id = el.id.replace(/\d+/, i.toString());
|
||||||
|
}
|
||||||
|
const labels: NodeListOf<HTMLLabelElement> = form.querySelectorAll("label");
|
||||||
|
for (const el of labels) {
|
||||||
|
el.htmlFor = el.htmlFor.replace(/\d+/, i.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
<noscript><link rel="stylesheet" href="{{ static('bundled/fontawesome-index.css') }}"></noscript>
|
<noscript><link rel="stylesheet" href="{{ static('bundled/fontawesome-index.css') }}"></noscript>
|
||||||
|
|
||||||
<script src="{{ url('javascript-catalog') }}"></script>
|
<script src="{{ url('javascript-catalog') }}"></script>
|
||||||
<script type="module" src={{ static("bundled/core/navbar-index.ts") }}></script>
|
<script type="module" src="{{ static("bundled/core/navbar-index.ts") }}"></script>
|
||||||
<script type="module" src={{ static("bundled/core/components/include-index.ts") }}></script>
|
<script type="module" src="{{ static("bundled/core/components/include-index.ts") }}"></script>
|
||||||
<script type="module" src="{{ static('bundled/alpine-index.js') }}"></script>
|
<script type="module" src="{{ static('bundled/alpine-index.js') }}"></script>
|
||||||
<script type="module" src="{{ static('bundled/htmx-index.js') }}"></script>
|
<script type="module" src="{{ static('bundled/htmx-index.js') }}"></script>
|
||||||
<script type="module" src="{{ static('bundled/country-flags-index.ts') }}"></script>
|
<script type="module" src="{{ static('bundled/country-flags-index.ts') }}"></script>
|
||||||
|
|||||||
@@ -291,7 +291,8 @@ ScheduledProductActionFormSet = forms.modelformset_factory(
|
|||||||
absolute_max=None,
|
absolute_max=None,
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
can_delete_extra=False,
|
can_delete_extra=False,
|
||||||
extra=2,
|
extra=0,
|
||||||
|
min_num=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,44 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
<script type="module" src="{{ static("bundled/core/dynamic-formset-index.ts") }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% macro action_form(form) %}
|
||||||
|
<fieldset x-data="{action: '{{ form.task.initial }}'}">
|
||||||
|
{{ form.non_field_errors() }}
|
||||||
|
<div class="row gap-2x margin-bottom">
|
||||||
|
<div>
|
||||||
|
{{ form.task.errors }}
|
||||||
|
{{ form.task.label_tag() }}
|
||||||
|
{{ form.task|add_attr("x-model=action") }}
|
||||||
|
</div>
|
||||||
|
<div>{{ form.trigger_at.as_field_group() }}</div>
|
||||||
|
</div>
|
||||||
|
<div x-show="action==='counter.tasks.change_counters'" class="margin-bottom">
|
||||||
|
{{ form.counters.as_field_group() }}
|
||||||
|
</div>
|
||||||
|
{%- if form.DELETE -%}
|
||||||
|
<div class="row gap">
|
||||||
|
{{ form.DELETE.as_field_group() }}
|
||||||
|
</div>
|
||||||
|
{%- else -%}
|
||||||
|
<button
|
||||||
|
class="btn btn-grey"
|
||||||
|
@click.prevent="removeForm($event.target.closest('fieldset'))"
|
||||||
|
>
|
||||||
|
<i class="fa fa-minus"></i>{% trans %}Remove this action{% endtrans %}
|
||||||
|
</button>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- for field in form.hidden_fields() -%}
|
||||||
|
{{ field }}
|
||||||
|
{%- endfor -%}
|
||||||
|
<hr />
|
||||||
|
</fieldset>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
<h2>{% trans name=object %}Edit product {{ name }}{% endtrans %}</h2>
|
<h2>{% trans name=object %}Edit product {{ name }}{% endtrans %}</h2>
|
||||||
@@ -25,34 +64,20 @@
|
|||||||
</em>
|
</em>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div x-data="dynamicFormSet" class="margin-bottom">
|
||||||
{{ form.action_formset.management_form }}
|
{{ form.action_formset.management_form }}
|
||||||
{%- for action_form in form.action_formset.forms -%}
|
<div x-ref="formContainer">
|
||||||
<fieldset x-data="{action: '{{ action_form.task.initial }}'}">
|
{%- for f in form.action_formset.forms -%}
|
||||||
{{ action_form.non_field_errors() }}
|
{{ action_form(f) }}
|
||||||
<div class="row gap-2x margin-bottom">
|
|
||||||
<div>
|
|
||||||
{{ action_form.task.errors }}
|
|
||||||
{{ action_form.task.label_tag() }}
|
|
||||||
{{ action_form.task|add_attr("x-model=action") }}
|
|
||||||
</div>
|
|
||||||
<div>{{ action_form.trigger_at.as_field_group() }}</div>
|
|
||||||
</div>
|
|
||||||
<div x-show="action==='counter.tasks.change_counters'" class="margin-bottom">
|
|
||||||
{{ action_form.counters.as_field_group() }}
|
|
||||||
</div>
|
|
||||||
{%- if action_form.DELETE -%}
|
|
||||||
<div class="row gap">
|
|
||||||
{{ action_form.DELETE.as_field_group() }}
|
|
||||||
</div>
|
|
||||||
{%- endif -%}
|
|
||||||
{%- for field in action_form.hidden_fields() -%}
|
|
||||||
{{ field }}
|
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</fieldset>
|
</div>
|
||||||
{%- if not loop.last -%}
|
<template x-ref="formTemplate">
|
||||||
<hr class="margin-bottom">
|
{{ action_form(form.action_formset.empty_form) }}
|
||||||
{%- endif -%}
|
</template>
|
||||||
{%- endfor -%}
|
<button @click.prevent="addForm()" class="btn btn-grey">
|
||||||
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
<i class="fa fa-plus"></i>{% trans %}Add action{% endtrans %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p><input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -3757,6 +3757,10 @@ msgstr ""
|
|||||||
"votre cotisation. Si vous ne renouvelez pas votre cotisation, il n'y aura "
|
"votre cotisation. Si vous ne renouvelez pas votre cotisation, il n'y aura "
|
||||||
"aucune conséquence autre que le retrait de l'argent de votre compte."
|
"aucune conséquence autre que le retrait de l'argent de votre compte."
|
||||||
|
|
||||||
|
#: counter/templates/counter/product_form.jinja
|
||||||
|
msgid "Remove this action"
|
||||||
|
msgstr "Retirer cette action"
|
||||||
|
|
||||||
#: counter/templates/counter/product_form.jinja
|
#: counter/templates/counter/product_form.jinja
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Edit product %(name)s"
|
msgid "Edit product %(name)s"
|
||||||
@@ -3784,6 +3788,10 @@ msgstr ""
|
|||||||
"Les actions automatiques vous permettent de planifier des modifications du "
|
"Les actions automatiques vous permettent de planifier des modifications du "
|
||||||
"produit à l'avance."
|
"produit à l'avance."
|
||||||
|
|
||||||
|
#: counter/templates/counter/product_form.jinja
|
||||||
|
msgid "Add action"
|
||||||
|
msgstr "Ajouter une action"
|
||||||
|
|
||||||
#: counter/templates/counter/product_list.jinja
|
#: counter/templates/counter/product_list.jinja
|
||||||
msgid "Product list"
|
msgid "Product list"
|
||||||
msgstr "Liste des produits"
|
msgstr "Liste des produits"
|
||||||
|
|||||||
Reference in New Issue
Block a user