Dynamic fields and adding onchange calculations

I have created a form which has a more than one section and is controlled by a number field that the users can select.
# of Options: {1, 2, 3, 4, 5}
Once they select the appropriate number, the number of sections is displayed:
IE: User selects 2, the form displays Section1 and Section2.
I also have a dynamic function that controls fields within to be required or not when they are displayed or hides and clears them when they are hidden.
There are two fields 'start date' and 'end date' in each section that I would now like to add a calculation to that will then fill out a third field (IE: number of nights).
I have tried multiple methods but can not get the dynamic fields to be recognized so that I can manipulate their values. The values just show as null.
And, of course, if I try and manipulate it just says: "TypeError: Cannot read properties of undefined (reading 'apply')".

Even if I'm not doing it dynamically, I can't get at the values.
I have logged out the fields themselves and I can see the controls. It just can not get to the values.
Is this even something that is possible?

Code Sample:

const hotelDates = {
    Hotel1: ['Hotel1_ArrivalDate', 'Hotel1_DepartureDate'],
    Hotel2: ['Hotel2_ArrivalDate', 'Hotel2_DepartureDate'],
    Hotel3: ['Hotel3_ArrivalDate', 'Hotel3_DepartureDate'],
    Hotel4: ['Hotel4_ArrivalDate', 'Hotel4_DepartureDate'],
    Hotel5: ['Hotel5_ArrivalDate', 'Hotel5_DepartureDate']
};

handleHotelGrids = (gridCount) => {
    if(gridCount === null){
        return;
    }
    
    // set ALL items to hidden first (should also reset each field and turn off requiredness)
    for(i = 1;  i <= 5; i++){
        const elName = `.grid-hotel${i}`;
        if(!$(elName).is(':hidden')){
            $(`.grid-hotel${i}`).hide();
            setFieldRequirednessByGrid(`.grid-hotel${i}`);   
        }
    }
    
    if(gridCount < 1 ){
        return;
    }
    
    // iterate from 1 to gridCount to show and turn on requiredness
    for(i = 1; i <= gridCount; i++){
        $(`.grid-hotel${i}`).show();
        var exclusions = exclusionArrays[`hotel${i}Exclusions`];
        setFieldRequirednessByGrid(`.grid-hotel${i}`, exclusions, true);
		
		// for each hotel section, ensure that the onchange for the dates calculates on change
        const currentHotelDates = hotelDates[`Hotel${i}`];
        
        // since these are all dynamic, iterate over each one per the number selected 
        // and add in 'onchange' functionality to be able to calculate the nights based on what is chosen
        setOnChangeForHotelNights(currentHotelDates);
    }
};

setOnChangeForHotelNights = (currentHotelDates) => {
    console.log('calculateHotelNights ...');
    console.log(`currentHotelDates: `, currentHotelDates);
    currentHotelDates.forEach(el => {
        // fd.field(el).$on('change', el=>{console.log(`current element: `,el)}); // current element:  Mon May 26 2025 00:00:00 GMT-0400 (Eastern Daylight Time)
        fd.field(el).$on('change', calculateHotelNights(currentHotelDates));
    });
};

calculateHotelNights = (currentHotelDates) => {
    // determine both controls
    let arrivalDateControl;
    let departureDateControl;
    currentHotelDates.forEach(el => {
        console.log(`el name `, el);
        if(el.indexOf('Arrival') != -1){
            arrivalDateControl = fd.field(el);
        } else {
            departureDateControl = fd.field(el);
        }
    });
    
    console.log(`arrival date control: `, arrivalDateControl); // control information is available (although, notably, there is no 'value' node)
    console.log(`departure date control: `, departureDateControl); // control information is available (although, notably, there is no 'value' node)
    console.log(`arrivalDate.value: ${arrivalDateControl.value}`); // null
    console.log(`departureDate.value: ${departureDateControl.value}`); // null
    
    // check that both controls contain values
    if(arrivalDateControl.value != "" && arrivalDateControl.value != null && departureDateControl.value != "" && departureDateControl.value != null){
        let endDate = new Date(departureDateControl.value);
        let startDate = new Date(arrivalDateControl.value);
        
        let timeDifference = endDate.getTime() - startDate.getTime();
        let dayDifference = Math.round(timeDifference) / (1000 * 3600 * 24);
        fd.field('Hotel1_NumberOfNights').value = dayDifference;
    } else {
        fd.field('Hotel1_NumberOfNights').value = 0;
    }
};

fd.field('Hotel_Count').$on('change', (count)=> {
    handleHotelGrids(count);
});

Dear @Michael_A_Strauss,
Definitely possible, but the code will need to be simplified, here's what I have - Hotel_NumberOfNights

Here's the code I've used, based on your code:

//handle grids
const handleHotelGrids = (gridCount) => {
    if (gridCount === null) return;
    
    // Loop through all 5 hotel grids
    for (let i = 1; i <= 5; i++) {
        const shouldShow = i <= gridCount;
        const gridElement = $(`.grid-hotel${i}`);
        
        if (shouldShow) {
            // Show grid and make fields required
            gridElement.show();
            fd.field(`Hotel${i}_ArrivalDate`).required = true;
            fd.field(`Hotel${i}_DepartureDate`).required = true;
        } else {
            // Hide grid and make fields not required
            gridElement.hide();
            fd.field(`Hotel${i}_ArrivalDate`).required = false;
            fd.field(`Hotel${i}_DepartureDate`).required = false;
            // Optional: Clear the field values when hiding
            fd.field(`Hotel${i}_ArrivalDate`).value = null;
            fd.field(`Hotel${i}_DepartureDate`).value = null;
            fd.field(`Hotel${i}_NumberOfNights`).value = null;
        }
        
        // NumberOfNights should always be disabled (calculated field)
        fd.field(`Hotel${i}_NumberOfNights`).disabled = true;
    }
};

fd.rendered(() => {
    handleHotelGrids(fd.field('Hotel_Count').value);
    fd.field('Hotel_Count').$on('change', handleHotelGrids);
});

//handle date calculations
const loadLuxon = async () => {
    // Load external library
    await $.getScript('https://cdn.jsdelivr.net/npm/luxon@3.5.0/build/global/luxon.min.js');
}

const updateHotelNights = (hotelNumber) => {
    const arrivalDate = fd.field(`Hotel${hotelNumber}_ArrivalDate`).value;
    const departureDate = fd.field(`Hotel${hotelNumber}_DepartureDate`).value;

    if (arrivalDate && departureDate) {
        const startDate = luxon.DateTime.fromISO(arrivalDate.toISOString());
        const endDate = luxon.DateTime.fromISO(departureDate.toISOString());

        // Calculate the difference between two dates
        const dateDiff = endDate.diff(startDate, 'days').as('days');
        // Populate the field with the calculated value
        fd.field(`Hotel${hotelNumber}_NumberOfNights`).value = dateDiff;
    }
}

const setupHotelListeners = () => {
    // Loop through hotels 1-5
    for (let i = 1; i <= 5; i++) {
        // Create update function for this specific hotel
        const updateFunction = () => updateHotelNights(i);
        
        // Add event listeners for both arrival and departure dates
        fd.field(`Hotel${i}_ArrivalDate`).$on('change', updateFunction);
        fd.field(`Hotel${i}_DepartureDate`).$on('change', updateFunction);
        
        // Calculate on form load
        updateFunction();
    }
}

fd.rendered(() => {
    loadLuxon().then(() => {
        setupHotelListeners();
    });
});

Thank @Nikita_Kurguzov. I will give this a try.
My apologies. I did not get a notification that there was a reply to my post.

Michael

@Nikita_Kurguzov - Works great! Thanks so much!

1 Like