Django - javascript - ajax does not work with javascript created lines

I have three classes lets say,

class Item(models.Model):
    id = models.AutoField(primary_key=True)  # Explicitly define the id column as the primary key
    itemref = models.IntegerField(blank=True, null=True)
    code = models.CharField(max_length=150, db_collation='Turkish_CI_AS', blank=True, null=True)
    tanimlama = models.CharField(max_length=150, db_collation='Turkish_CI_AS', blank=True, null=True)
    itemname = models.CharField(max_length=150, db_collation='Turkish_CI_AS', blank=True, null=True)

class SAFiche(models.Model):
     nothing important

class SALine(models.Model):
    safiche = models.ForeignKey(SAFiche, on_delete=models.CASCADE)
    item = models.ForeignKey('accounting.Item', on_delete=models.CASCADE)



In views, I have ajax for autocomplate:

def item_autocomplete(request):

    query = request.GET.get('q', '')
    items = Item.objects.filter(
        Q(code__icontains=query) |
        Q(itemname__icontains=query) |
        Q(tanimlama__icontains=query)
    )[:10]  # Limit to 10 results

    results = []
    for item in items:
        label = f"{item.code or ''} - {item.itemname or item.tanimlama or ''}"
        results.append({
            'id': item.id,
            'label': label.strip(' -'),
        })
    return JsonResponse(results, safe=False)

I properly adjusted rest. This is my forms:

class SALineCreateForm(forms.ModelForm):
    urun_search = forms.CharField(
        label="Ürün Ara",
        required=False,
        widget=forms.TextInput(attrs={
            'placeholder': 'Ürün kodu veya adı...',
            'class': 'item-search-input border rounded p-2 w-full',
            'autocomplete': 'off'
        })
    )

    class Meta:
        model = SALine
        fields = [
            'urun', 'miktar'
        ]
        widgets = {
            'urun': forms.HiddenInput(),
            'miktar': forms.NumberInput(attrs={'class': 'w-full form-input rounded-md'}),

        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # If editing an existing line, fill the search box with the item name
        if getattr(self.instance, 'urun_id', None):
            self.fields['urun_search'].initial = str(self.instance.urun)

SALineCreateFormSet = inlineformset_factory(
    SAFiche,
    SALine,
    form=SALineCreateForm,
    extra=1,        
    can_delete=True
)

Autocomplating works well with forms created inlines. However, when I use javascript with create new lines button, urun field does auto complate. Nothing happens on the console if I write something to newly created lines urun fields. What can be the problem? How can I solve it?

urun means item in turkish.

This is how I add new item lines:

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const formsetContainer = document.getElementById('formset-container');
        const emptyFormTemplate = document.getElementById('empty-form-template').innerHTML;
        const addProductButton = document.getElementById('add-product');

        addProductButton.addEventListener('click', () => {
            // Get current form count from the management form
            const totalFormsInput = document.querySelector('#id_saline_set-TOTAL_FORMS');
            const totalForms = parseInt(totalFormsInput.value, 10);

            // Create a new form from the empty template
            const newFormHTML = emptyFormTemplate.replace(/__prefix__/g, totalForms);
            formsetContainer.insertAdjacentHTML('beforeend', newFormHTML);

            // Update the TOTAL_FORMS count
            totalFormsInput.value = totalForms + 1;
        });
    });
</script>

And this is rest of the js code which handles autocomplate:

<script>
document.addEventListener('DOMContentLoaded', () => {
    const firmSearchInput = document.getElementById("id_firm_search");
    const firmHiddenInput = document.getElementById("id_firm");
    const resultsContainer = document.getElementById("firm-search-results");
    let selectedIndex = -1; // To track the currently highlighted index

    // Fetch data from the firm_autocomplete endpoint
    async function fetchFirms(query) {
        if (!query.trim()) {
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
            return;
        }
        try {
            const response = await fetch("{% url 'operations:firm_autocomplete' %}?q=" + encodeURIComponent(query));
            const data = await response.json();
            renderResults(data);
        } catch (err) {
            console.error("Autocomplete error:", err);
        }
    }

    // Render the list of firm suggestions
    function renderResults(firms) {
        if (!firms.length) {
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
            return;
        }

        selectedIndex = -1; // Reset selected index
        resultsContainer.innerHTML = firms
            .map((firm, index) => `
                <div class="px-3 py-2 hover:bg-gray-100 cursor-pointer"
                     data-index="${index}"
                     data-firm-id="${firm.id}"
                     data-firm-label="${firm.label}">
                    ${firm.label}
                </div>
            `).join('');
        resultsContainer.classList.remove("hidden");

        // Add click handlers for each suggestion
        const items = resultsContainer.querySelectorAll("[data-firm-id]");
        items.forEach((item, index) => {
            item.addEventListener("click", () => selectFirm(index));
        });
    }

    // Select a firm by index
    function selectFirm(index) {
        const items = resultsContainer.querySelectorAll("[data-firm-id]");
        if (index >= 0 && index < items.length) {
            const selectedFirm = items[index];
            const selectedFirmId = selectedFirm.getAttribute("data-firm-id");
            const selectedFirmLabel = selectedFirm.getAttribute("data-firm-label");

            // Update hidden input with the firm ID
            firmHiddenInput.value = selectedFirmId;
            // Update the visible text box
            firmSearchInput.value = selectedFirmLabel;

            // Hide the dropdown
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
        }
    }

    // Trigger fetch on input
    firmSearchInput.addEventListener("input", (e) => {
        const query = e.target.value;
        fetchFirms(query);
    });

    // Keyboard navigation
    firmSearchInput.addEventListener("keydown", (e) => {
        const items = resultsContainer.querySelectorAll("[data-firm-id]");

        if (e.key === "ArrowDown") {
            // Move focus down
            e.preventDefault();
            selectedIndex = (selectedIndex + 1) % items.length;
            highlightItem(items, selectedIndex);
        } else if (e.key === "ArrowUp") {
            // Move focus up
            e.preventDefault();
            selectedIndex = (selectedIndex - 1 + items.length) % items.length;
            highlightItem(items, selectedIndex);
        } else if (e.key === "Enter") {
            // Select the currently highlighted item
            e.preventDefault();
            if (selectedIndex >= 0) {
                selectFirm(selectedIndex);
            }
        } else if (e.key === "Tab" && selectedIndex >= 0) {
            // Select and move to the next form element on Tab
            selectFirm(selectedIndex);
        }
    });

    // Highlight a specific item
    function highlightItem(items, index) {
        items.forEach((item, i) => {
            if (i === index) {
                item.classList.add("bg-gray-100");
            } else {
                item.classList.remove("bg-gray-100");
            }
        });
    }

    // Hide dropdown when clicking outside
    document.addEventListener("click", (e) => {
        if (!resultsContainer.contains(e.target) && e.target !== firmSearchInput) {
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
        }
    });
});
</script>

<script>
document.addEventListener('DOMContentLoaded', () => {
    // 1. For each row in the formset, we have an input with class "item-search-input"
    const itemSearchInputs = document.querySelectorAll('.item-search-input');

    itemSearchInputs.forEach((input, index) => {
        const resultsContainerId = `item-search-results-${index}`;
        const resultsContainer = document.getElementById(resultsContainerId);

        const hiddenUrunInput = input
            .closest('.formset-row')
            .querySelector('input[name$="-urun"]');

        let selectedIndex = -1; // Tracks the currently highlighted index

        // Autocomplete fetch function
        async function fetchItems(query) {
            if (!query.trim()) {
                resultsContainer.innerHTML = '';
                resultsContainer.classList.add('hidden');
                return;
            }
            try {
                const url = "{% url 'operations:item_autocomplete' %}?q=" + encodeURIComponent(query);
                const response = await fetch(url);
                const data = await response.json();
                renderResults(data);
            } catch (err) {
                console.error("Item autocomplete error:", err);
            }
        }

        // Renders the dropdown results
        function renderResults(items) {
            if (!items.length) {
                resultsContainer.innerHTML = '';
                resultsContainer.classList.add('hidden');
                return;
            }

            selectedIndex = -1; // Reset selected index
            resultsContainer.innerHTML = items.map((item, i) => `
                <div class="px-3 py-2 hover:bg-gray-100 cursor-pointer"
                     data-index="${i}"
                     data-item-id="${item.id}"
                     data-item-label="${item.label}">
                    ${item.label}
                </div>
            `).join('');
            resultsContainer.classList.remove('hidden');

            // Click handlers
            const suggestionDivs = resultsContainer.querySelectorAll('[data-item-id]');
            suggestionDivs.forEach(div => {
                div.addEventListener('click', () => {
                    selectItem(div);
                });
            });
        }

        // Handle item selection
        function selectItem(element) {
            const selectedId = element.getAttribute('data-item-id');
            const selectedLabel = element.getAttribute('data-item-label');

            // Set hidden input to item id, visible input to label
            hiddenUrunInput.value = selectedId;
            input.value = selectedLabel;

            // Hide the dropdown
            resultsContainer.innerHTML = '';
            resultsContainer.classList.add('hidden');
        }

        // Highlight a specific item
        function highlightItem(items, index) {
            items.forEach((item, i) => {
                if (i === index) {
                    item.classList.add('bg-gray-100');
                } else {
                    item.classList.remove('bg-gray-100');
                }
            });
        }

        // Listen for typing
        input.addEventListener('input', (e) => {
            fetchItems(e.target.value);
        });

        // Listen for keyboard events
        input.addEventListener('keydown', (e) => {
            const items = resultsContainer.querySelectorAll('[data-item-id]');
            if (e.key === 'ArrowDown') {
                e.preventDefault();
                if (items.length > 0) {
                    selectedIndex = (selectedIndex + 1) % items.length;
                    highlightItem(items, selectedIndex);
                }
            } else if (e.key === 'ArrowUp') {
                e.preventDefault();
                if (items.length > 0) {
                    selectedIndex = (selectedIndex - 1 + items.length) % items.length;
                    highlightItem(items, selectedIndex);
                }
            } else if (e.key === 'Enter') {
                e.preventDefault();
                if (selectedIndex >= 0 && selectedIndex < items.length) {
                    selectItem(items[selectedIndex]);
                }
            } else if (e.key === 'Tab') {
                if (selectedIndex >= 0 && selectedIndex < items.length) {
                    selectItem(items[selectedIndex]);
                }
            }
        });

        // Optional: hide dropdown if user clicks outside
        document.addEventListener('click', (e) => {
            if (!resultsContainer.contains(e.target) && e.target !== input) {
                resultsContainer.innerHTML = '';
                resultsContainer.classList.add('hidden');
            }
        });
    });
});
</script>
Вернуться на верх