Adding and removing DateFields in Django formsets dynamically

I am encountering a unique issue specifically related to the django-bootstrap-datepicker-plus package.

Within my Todo list application, I am aiming to enable tasks to appear on multiple specific dates. I have successfully set up my model, configured my form with formset, and implemented JavaScript for dynamic add/remove functionality.

The challenge I am facing involves the cloning process of my DateField affecting the DatePicker divs, as illustrated in the code snippet below:

# Revised Model
from django.db import models
from datetime import time

class Task(models.Model):

    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=50, default="")
    description = models.CharField(max_length=500, default="")
    entry_date = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)
    specific_dates = models.ManyToManyField('SpecificDate', blank=True)
    due_until = models.TimeField(auto_now_add=False, default=time(12, 0))

class SpecificDate(models.Model):
    todo = models.ForeignKey(Task, on_delete=models.CASCADE)
    date = models.DateField(auto_now_add=False, blank=True, null=True)

    class Meta:
        unique_together = ('todo', 'date')
/* Partial Code Block: To avoid redundancy, omitted the forms.py and task_form.html snippets */ /* JavaScript Section - Modified for clarity */

Upon clicking the 'add' button, the resulting HTML structure within the formset is displayed as shown below:

<!-- Updated Formset Structure Post-Add Action -->
<div id="formset-container">
    <input type="hidden" name="specificdate_set-TOTAL_FORMS" value="2" id="id_specificdate_set-TOTAL_FORMS">
    <input type="hidden" name="specificdate_set-INITIAL_FORMS" value="0" id="id_specificdate_set-INITIAL_FORMS">
    <input type="hidden" name="specificdate_set-MIN_NUM_FORMS" value="0" id="id_specificdate_set-MIN_NUM_FORMS">
    <input type="hidden" name="specificdate_set-MAX_NUM_FORMS" value="1000" id="id_specificdate_set-MAX_NUM_FORMS">

    <!-- Sample Date Input From Cloned Form -->
    <div id="formset-date" class="formset-date d-flex align-items-center">
        <div class="flex-grow-1 mr-2">
            <label for="id_specificdate_set-0-date" class=""> Date </label>
            
            /* Additional Structure Details Omitted for Brevity */
            
            <input type="text" class="datepickerinput form-control" id="id_specificdate_set-0-date" data-dbdp-config="{...}" data-dbdp-debug="" data-name="specificdate_set-0-date">
            <input type="hidden" name="null" value="">
        
        </div>
          
        /* Buttons for Add/Remove Actions Within Cloned Form Set */
       
      </div>
      
    </div>

    /* Subsequent Cloned Form Snippet For Illustration */
    
</div>

Observing closely, it appears that despite updating the dataset.name property during processing, the rendered HTML does not reflect these changes. The primary query remains: How can the correct dataset.names be maintained in the respective <input> fields to ensure accurate field elements on form submission?

Answer №1

After some trial and error, I finally managed to solve my problem with a functional JavaScript code. This code even handles the task of disabling all add/remove buttons except for the last one. It may not be the most elegant solution, but it gets the job done.

document.addEventListener('DOMContentLoaded', function() {
    const formsetContainer = document.getElementById('formset-container');
    const formsetPrefix = 'specificdate_set';
    const totalFormsElement = document.getElementById('id_' + formsetPrefix + '-TOTAL_FORMS');

    function updateLabels() {
        $('.formset-date').each(function(index) {
            $(this).find('label').text(`Date ${index + 1}`);
        });
    }

    function manageAddRemoveButtons() {
        let formsetDivs = formsetContainer.querySelectorAll('.formset-date');
        formsetDivs.forEach((div, index) => {
            // disable add button for all but the last row
            div.querySelector('.add-date').disabled = (
                index !== formsetDivs.length - 1);
            // disable remove button for all but the last
            // row, but not if only one row is present
            div.querySelector('.remove-date').disabled = (
                formsetDivs.length === 1) || (
                    index !== formsetDivs.length - 1);
        });
    }

    function adjustNamesAndIds(newForm, newIndex) {
        const regex = /^(.+)-\d+-(.+)$/;

        newForm.querySelectorAll('[id], label, .datepickerinput').forEach(element => {
            if (element.tagName === 'BUTTON') return; // Skip buttons entirely

            ['id', 'htmlFor', 'name'].forEach(attribute => {
                if (element[attribute]) {
                    const match = element[attribute].match(regex);
                    if (match) {
                        element[attribute] = `${match[1]}-${newIndex}-${match[2]}`;
                    }
                }
            });

            if (element.classList.contains('datepickerinput')) {
                const $datepickerInput = $(element);
                if ($datepickerInput.length && $datepickerInput.data('name')) {
                    const nameMatch = $datepickerInput.data('name').match(regex);
                    if (nameMatch) {
                        $datepickerInput.attr('name', `${nameMatch[1]}-${newIndex}-${nameMatch[2]}`);
                    }
                }
            }
        });
    }

    function cloneForm() {
        let totalForms = parseInt(totalFormsElement.value, 10);
        let newFormIndex = totalForms;
        totalFormsElement.value = totalForms + 1;

        let newForm = formsetContainer.querySelector('.formset-date:last-of-type').cloneNode(true);
        if (newForm && typeof newFormIndex !== 'undefined') {
            adjustNamesAndIds(newForm, newFormIndex);
            formsetContainer.appendChild(newForm);
            // Remove Extra Hidden Fields
            let inputName = `${formsetPrefix}-${totalForms-1}-date`;
            newForm.querySelectorAll(`input[type="hidden"][name="${inputName}"]`).forEach(
                hiddenInput => hiddenInput.remove()
            ); 
        } else {
            console.error('Error cloning form: newForm or newFormIndex is invalid.');
        }
    }

    formsetContainer.addEventListener('click', function(event) {
        if (event.target.classList.contains('add-date')) {
            event.preventDefault();
            cloneForm();
        } else if (event.target.classList.contains('remove-date')) {
            event.preventDefault();
            let formToRemove = event.target.closest('.formset-date');
            formToRemove.remove();
        }
        updateLabels();
        manageAddRemoveButtons();
    });

    updateLabels(); 
    manageAddRemoveButtons();
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The execution of the celery task does not occur concurrently

I am working on a form that uploads a file and then processes it. Since the processing time is considerable, I have implemented celery for asynchronous task execution. Additionally, I am using signals to trigger the task. views.py def handle_file_upload( ...

Puppeteer patiently waits for the keyboard.type function to complete typing a lengthy text

Currently, I am utilizing puppeteer for extracting information from a particular website. There is only one straightforward issue with the code snippet below: await page.keyboard.type(data) await page.click(buttonSelector) The initial line involves typin ...

Displaying selected checkbox values in a URL with parameters using PHP and JavaScript

I am looking to extract the selected checkbox value and append it to the browser URL with a parameter name. Below is my HTML code: <div style="width:200px; float:left"> Price <div> <input type="checkbox" name="price" value="0-1 ...

AngularJS implemented to trigger a popup alert after a certain duration of time has elapsed since the

Can we create a popup alert that says "Error Contacting Server" when the http request does not receive any response? .controller('items_ctrl',['$scope','$http',function($scope,$http){ $scope.shop_id=localStorage.getItem(" ...

When a specific item is selected from a drop-down menu, text boxes and drop-downs will dynamically appear and change

In the current version of my code, there is a single drop-down menu with three options for the user to select from. If "Complete" is chosen, a text box should appear. If "Abandon" or "Transfer" is selected, a separate drop-down menu needs to be displayed. ...

How to access variables with dynamic names in Vue.js

I'm curious if it's possible to dynamically access variables from Vue’s data collection by specifying the variable name through another variable. For instance, consider the following example: Here are some of the variables/properties: var sit ...

Navigating a loop in javascript: tips and techniques

I have a challenge with three boxes that are supposed to fade in, shake, and then fade out. The IDs of each box are stored in an array and a loop is used to traverse them. However, the loop only displays the first item. I have tried various methods in Jav ...

jQuery offset(coords) behaves inconsistently when called multiple times

I am attempting to position a div using the jQuery offset() function. The goal is to have it placed at a fixed offset from another element within the DOM. This is taking place in a complex environment with nested divs. What's puzzling is that when I ...

The variable was not properly sent to the backing bean

I am experiencing an issue with a certain piece of code. I have a variable called fieldToStore that is defined. When I alert it or check its value in the firebug console, everything seems fine. However, when I pass it to myBean, it always ends up being und ...

What is the reason behind the lack of access for modal to an external controller?

Imagine having this code in your main HTML file: <body ng-app="app" ng-controller="session as vmSession"> ... <!-- Modal content goes here --> </body> Inside the modal, you have a link: <a ui-sref="siteManagement({ site: vmSession.u ...

Unable to import a text file using a semicolon as delimiter in d3

I'm just starting to learn JavaScript and the D3 library. I recently explored the "d3-fetch" module (available at https://github.com/d3/d3-fetch) and the d3.dsv command API (find it here: https://github.com/d3/d3-dsv). However, I am struggling to unde ...

Extension for Chrome

My goal is to develop a Chrome extension that, when a specific button in the extension is clicked, will highlight the current tab. However, I'm encountering some challenges. Currently, I have document.getElementById("button").addEventListen ...

Assurance Code Evaluation

Recently, I've dived into the world of nodeJS and I'm getting acquainted with promises. After looking at the code below, it seems to me that it could be enhanced by integrating the retry logic within getValue2. It's important to note that ...

I am facing an issue with updating the state dynamically within a useState object

Within my useState, there is an object containing a variety of values. I have an input field and am dynamically setting its value to useState[key], which works fine. However, the issue arises when I try to update these values. When I call onChange and use ...

Error message: The specified file or directory named '-stylus' could not be found

Encountered an error message while using stylus with npm on my Linux machine. After installing nodejs from GitHub, I executed the command npm install -g stylus autoprefixer-stylus and received the following error log: npm ERR! Error: EACCES, unlink ' ...

Save data for specific days in MongoDB

I'm currently developing a route to store the user's history for the last n days using Node.js and MongoDB. I need to keep track of the count field. Here's what I had in mind for my schema: count: [ type: Number default: 0 ] Each ar ...

Choose a single asset from the list of values stored in the Map

I'm looking to implement something similar to the following: let myMap = new Map<string, any>(); myMap.set("aaa", {a: 1, b: 2, c:3}); myMap.set("bbb", {a: 1, b: 2, c:6}); myMap.set("ccc", {a: 1, b: 2, c:9}); let cs = myMap.values().map(x => ...

Enable content to be displayed when hovering over it with a mouse in

As a newcomer to JavaScript, I am eager to learn how to add an interactive feature to my menu item. I want to create an info box that pops up when the menu item is clicked, and can also be displayed when hovering over it. Below is the script I have so far, ...

Is it possible for a search box selection toggle to reveal a hidden information box underneath it containing all the compiled data?

Improve the user experience on my website by implementing a search feature for US states and Canadian territories. When visitors type in their selection, I want them to be able to click on an icon that will reveal relevant information about that choice. T ...

Validation error occurred while attempting to send form data to the Contact Form 7 API through Next.js

Having trouble sending data to the Contact Form 7 API and encountering an error while submitting the form: {into: "#", status: "validation_failed", message: "One or more fields have an error. Please check and try again.", post ...