如何使用 formset_media_js 处理 inlineformset_factory 中的 JavaScript 事件

How to handle JavaScript event inside a inlineformset_factory with formset_media_js

我有一个用 formset_media_js 实现的 inlineformset_factory,这两个本身工作正常。我需要实现的是能够处理 inlineformset_factory.

中的某些复选框和输入字段的启用和禁用状态

我有一个 javascript 适用于页面加载时创建的第一组表单集,但是当用户添加新的表单集时 javascript 不起作用。

如何处理用户使用 javascript 添加的新表单集输入字段?

如果选中 "is chapter",则禁用 "is subchapter" 和 "quantity",默认情况下 inlineformset_fatory 在页面加载时创建 1 个表单集,在此表单集上 javascript 有效。但是当用户添加另一个带有按钮 "Add another Budget Item" 的表单集时,javascript 不再起作用。例如,如果我将 inlineformser_factory 配置为在页面加载时创建 3 个表单集,则 javascript 适用于这 3 个表单集,但不适用于用户添加的表单集。

forms.py :在此 forms.py 我有每次用户添加表单集时创建的 inlineformset_factory。

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:这个表格在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:在这个模板上我有 javascript 我控制启用和禁用状态复选框和输入字段。我认为通过在迭代表单集的 for 循环中调用脚本,我将能够控制用户添加的表单集的输入字段。仅处理在页面加载时创建的表单集。

{% 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 %}

这是终于搞定的javascript,是朋友写的..谢谢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>