Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.8k views
in Technique[技术] by (71.8m points)

django - How to handle JavaScript event inside a inlineformset_factory with formset_media_js

I have an inlineformset_factory implemented with formset_media_js, these two by itself are working ok. What I need to implement is to be able to handle the enable and disable state of some checkboxes and input fields that are inside the inlineformset_factory.

I have a javascript that works on the first group of formset created on page load, but when a new formset is added by the user the javascript is not working.

How can I handle the new formsets input fields added by the user with javascript?

If "is chapter" is checked then "is subchapter" and "quantity" are disabled, by default the inlineformset_fatory creates 1 formset on page load, on this formset the javascript works. But when the user adds another formset with button "Add another Budget Item" the javascript is no longer working. If for example, I configure the inlineformser_factory to create 3 formset on page load the javascript works on those 3 formset but not on the formsets added by the user.

enter image description here

forms.py : at this forms.py i have the inlineformset_factory that is created every time the user adds a formset.

from django import forms
from django.forms import inlineformset_factory

from djangoformsetjs.utils import formset_media_js
from accounts.models import ProjectManager
from projects.models import Project, BudgetModel, BudgetModelItems


class CreateBudgetItem(forms.ModelForm):
    class Media(object):
        js = formset_media_js

    class Meta:
        model = BudgetModelItems
        fields = ('budget_model',)
        widgets = {
            'budget_item_description': forms.Textarea(attrs={'rows': 2, 'cols': 50}),
            'budget_item_item': forms.NumberInput(attrs={'size': 6}),
            'budget_item_quantity': forms.NumberInput(attrs={'size': 6}),
        }


BudgetItemFormset = inlineformset_factory(BudgetModel, BudgetModelItems,
                                          form=CreateBudgetItem,
                                          fields=('budget_model', 'budget_item_item',
                                                  'budget_item_description', 'budget_item_unit',
                                                  'budget_item_quantity', 'budget_item_is_chapter',
                                                  'budget_item_is_subchapter'),
                                          extra=1,
                                          can_delete=True,
                                          can_order=True
                                          )

views.py

from django.shortcuts import render, redirect
from django.forms import formset_factory

from accounts.models import ProjectManager
from projects.forms.create_project import CreateProjectForm
from projects.forms.create_budgetmodel import BudgetFormset, ProjectForBudgetModel
from projects.forms.create_budgetitem import CreateBudgetItem, BudgetItemFormset
from projects.models import BudgetModel, Project


def create_budget_item(request):
    user = request.user.projectmanager
    projects = Project.objects.filter(project_manager_id=user)
    models = BudgetModel.objects.none()

    project_form = ProjectForBudgetModel(user)
    budget_item_form = CreateBudgetItem()
    formset = BudgetItemFormset()

    for project in projects:
        models |= BudgetModel.objects.filter(project_id=project.pk)
        budget_item_form.fields['budget_model'].queryset = models

    if request.method == 'POST':
        project_form = ProjectForBudgetModel(user, request.POST)
        budget_item_form = CreateBudgetItem(request.POST)
        if project_form.is_valid() and budget_item_form.is_valid():
            # project_id = project_form.cleaned_data['project']
            budget_model_id = budget_item_form.cleaned_data['budget_model']
            formset = BudgetItemFormset(request.POST, instance=budget_model_id)
            if formset.is_valid():
                formset.save()

    context = {'project_form': project_form,
               'bi_form': budget_item_form,
               'formset': formset,
               'models': models}
    return render(request, 'projects/create_budget_items.html', context)

budget_item_form.html: this form is called (included) at create_budget_items.html

<div data-formset-form>
    <div class="card">
        <div class="card-body">
            <div class="row">
                <div class="col">
                    <table class="table">
                        <thead class="thead-light">
                        <tr>
                            <th scope="col">Item</th>
                            <th scope="col">Description</th>
                            <th scope="col">Unit</th>
                            <th scope="col">Quantity</th>
                            <th scope="col">Is Chapter</th>
                            <th scope="col">Is SubChapter</th>
                        </tr>
                        </thead>
                        <tbody>
                        <tr>
                            <th>{{ form.budget_item_item }}</th>
                            <td>{{ form.budget_item_description }}</td>
                            <td>{{ form.budget_item_unit }}</td>
                            <td>{{ form.budget_item_quantity }}</td>
                            <td>{{ form.budget_item_is_chapter }}</td>
                            <td>{{ form.budget_item_is_subchapter }}</td>
                        </tr>
                        </tbody>
                    </table>
                </div>
                <div class="col-md-auto">
                    {% if form.ORDER %}
                        <div class="row mt-1">
                            <div class="d-none">{{ form.ORDER }}</div>
                            <button class="btn btn-info btn-block" type="button" data-formset-move-up-button>
                                {% trans 'Move up' %}
                            </button>
                        </div>
                        <div class="row mt-1">
                            <button class="btn btn-info btn-block" type="button" data-formset-move-down-button>
                                {% trans 'Move down' %}
                            </button>
                        </div>
                    {% endif %}
                </div>
                <div class="col col-lg-2 mt-1">
                    {% if form.DELETE %}
                        <div class="d-none">{{ form.DELETE }}</div>
                        <button type="button" class="btn btn-danger btn-block h-100" data-formset-delete-button>
                            {% trans 'Delete' %}
                        </button>
                    {% endif %}
                </div>
            </div>
        </div>
    </div>
</div>

create_budget_items.html: On this template I have the javascript where I control the enable and disable states of checkboxes and input fields. I thought that by calling the script inside the for loop where the formset is being iterated I was going to be able to control the input fields of the formsets added by the user. Is only working on the formsets created on page load.

{% block dashboard_head %}
    {{ formset.media }}
    <script type="text/javascript">
        function trackDisabled(trigger_id, ...targets) {
            const checkbox = document.getElementById(trigger_id);
            checkbox.addEventListener('change', e => {
                console.log(e.target.checked);
                {#console.log(trigger_id);#}
                {#console.log(...targets);#}
                if (e.target.checked === true) {
                    targets.forEach(x => {
                        const element = document.getElementById(x);
                        element.disabled = true;
                        element.checked = false;
                        element.value = ''
                    })
                } else {
                    targets.forEach(x => document.getElementById(x).disabled = false)
                }
            })
        }
    </script>

{% endblock dashboard_head %}

{% block dashboard_content %}
    <h1>Create Budget Items</h1>
    <form method="post">
        {% csrf_token %}
        {{ project_form.project }}

        <select name="budget_model" id="id_budget_model" class="form-control">
            {% with value=bi_form.budget_model.value %}
                {% for model in models %}
                    <option value="{{ model.pk }}" class="{{ model.project.pk }}"
                            {% if model.pk|slugify == value|slugify %}selected="selected"{% endif %}>
                        {{ model.budget_name }}
                    </option>
                {% endfor %}
            {% endwith %}
        </select>

        {% load formset_tags %}
        <div id="formset" data-formset-prefix="{{ formset.prefix }}">
            {{ formset.management_form }}
            <div data-formset-body>
                {% for form in formset %}
                    {% include "projects/budget_item_form.html" with form=form only %}
                    <script>
                        trackDisabled(
                            '{{ form.budget_item_is_chapter.auto_id }}',
                            '{{ form.budget_item_is_subchapter.auto_id }}',
                            '{{ form.budget_item_quantity.auto_id }}'
                        );
                        console.log('{{ form.budget_item_is_chapter.auto_id }}');
                    </script>
                    {{ form.errors }}
                {% endfor %}
            </div>
            <script type="form-template" data-formset-empty-form>
                {% escapescript %}
                    {% include "projects/budget_item_form.html" with form=formset.empty_form only %}
                {% endescapescript %}
            </script>
            <div class="row mt-3 mr-1 ml-1">
                <!-- This button will add a new form when clicked -->
                <div class="col text-center">
                    <input class="w-75 btn btn-info" type="button"
                           value="{% trans 'Add another Budget Item' %}" data-formset-add>
                </div>
                <div class="col text-center">
                    <button class="w-75 btn btn-success" type="submit">
                        {% trans 'Create Models' %}
                    </button>
                </div>
            </div>
        </div>
    </form>

{% endblock dashboard_content %}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

This is the javascript that finally worked out, was written by a friend.. Thank you FunkyBob!

<script>
    function isChapter() {
        const root = document.getElementById('formset');
        const prefix = root.dataset.formsetPrefix;
        console.log({root, prefix});
        // listen for all input changes
        root.addEventListener('change', ev => {
            // check if it matches out name pattern
            console.log(ev.target.name);
            console.log(ev.target.checked, !ev.target.checked);
            console.log(`${prefix}-(\d+)-budget_item_is_chapter`);
            let m = ev.target.name.match(RegExp(`${prefix}-(\d+)-budget_item_is_chapter`));
            // if it's not {prefix}-*-budget_item_is_chapter ignore
            if (!m) return;
            console.log(m);
            let idx = m[1]; // the matched regex group
            // Find the related fields, and set them as enabled/disabled
            root.querySelector(`[name="${prefix}-${idx}-budget_item_is_subchapter"]`).disabled = ev.target.checked;
            root.querySelector(`[name="${prefix}-${idx}-budget_item_is_subchapter"]`).checked = false;
            root.querySelector(`[name="${prefix}-${idx}-budget_item_unit"]`).disabled = ev.target.checked;
            root.querySelector(`[name="${prefix}-${idx}-budget_item_unit"]`).value = ev.target.checked;
            root.querySelector(`[name="${prefix}-${idx}-budget_item_quantity"]`).disabled = ev.target.checked;
            root.querySelector(`[name="${prefix}-${idx}-budget_item_quantity"]`).value = ev.target.checked;
            console.log("Done!")
        });
    }
    isChapter();
</script>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...