0

In my Laravel App I have a form that may contain dynamic fields. On "Add Contact" button click group of fields is added from template. Some of the fields have to be validated so I added rules dynamically for each of this fields with .rules() method. When I create a new entity that these dynamic fields should be part of it`s working fine for me:

enter image description here

When I edit an entity, I create these field groups from the json string that I get from the controller. Fields with values are created, but for some reason, the rules do not apply to these dynamically created fields. When I try to apply a rule to such a field, I get an error:

Uncaught TypeError: Cannot read property 'settings' of undefined

I found out that this happens when I add rules to dynamic fields:

let firstName = $('#first-name-' + elementNumber);
let jobTitle = $('#job-title-' + elementNumber);
let contactEmail = $('#contact-email-' + elementNumber);

firstName.rules("add", {
    required: true
});

jobTitle.rules("add", {
    required: true
});

contactEmail.rules("add", {
    required: true,
    email: true
});

My code:

let buildingForm = $('#building_form');
let addContactButton = $('#add_contact');
let contactsContainer = $('#contacts_container');
var elementNumber = 1;

let model = "{{ $model->building_id ?? '' }}"

if (model) {
    let buildingContacts = '{!! $buildingContacts ?? "" !!}'

    if (buildingContacts) {
        buildingContacts = JSON.parse(buildingContacts)

        $.each(buildingContacts, function (index, value) {
            addContact(value)
            elementNumber++
        });
    }
}

addContactButton.on('click', function () {
    addContact()
    elementNumber++
});

buildingForm.validate({
    ignore: ""
});

function addContact(value = null) {
    let contactLayout = getContactLayout(elementNumber, value);
    contactsContainer.prepend(contactLayout);

    let firstName = $('#first-name-' + elementNumber);
    let jobTitle = $('#job-title-' + elementNumber);
    let contactEmail = $('#contact-email-' + elementNumber);

    firstName.rules("add", {
        required: true
    });

    jobTitle.rules("add", {
        required: true
    });

    contactEmail.rules("add", {
        required: true,
        email: true
    });

    $('#delete-contact-button-' + elementNumber).on('click', function (e) {
        $.confirm({
            title: 'Confirm action',
            content: 'Are you sure you want to delete Contact?',
            buttons: {
                confirm: function () {
                    deleteContact(e.target.id);
                },
                cancel: function () {
                    $.alert('Canceled');
                },
            }
        });
    });
}

function deleteContact(buttonId) {
    let buttonOneContactContainer = $('#' + buttonId).closest('div.one-contact-container');
    buttonOneContactContainer.remove();

}

function getContactLayout(elementNumber, value = null) {
    return `
        <div class="p-t-10 p-b-10 p-l-10 p-r-10 m-t-10 border one-contact-container" id="one-contact-container-${elementNumber}">
            <div class="row">
                <div class="col-sm-6">
                    <div class="form-group row">
                        <label for="first-name-${elementNumber}" class="col-md-4 label-right">First Name <span class="required">*</span></label>
                        <div class="col-md-8">
                            <input name="contacts[first_name][${elementNumber}]" id="first-name-${elementNumber}" type="text" value="${value ? value.first_name : ''}" class="form-control first-name-input">
                        </div>
                    </div>
                </div>
                <div class="col-sm-6">
                    <div class="form-group row">
                        <label for="last-name-${elementNumber}" class="col-md-4 label-right">Last Name</label>
                        <div class="col-md-8">
                            <input name="contacts[last_name][${elementNumber}]" id="last-name-${elementNumber}" type="text" value="${value ? value.last_name : ''}" class="form-control">
                        </div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-6">
                    <div class="form-group row">
                        <label for="job-title-${elementNumber}" class="col-md-4 label-right">Job Title <span class="required">*</span></label>
                        <div class="col-md-8">
                            <input name="contacts[job_title][${elementNumber}]" id="job-title-${elementNumber}" type="text" value="${value ? value.job_title : ''}" class="form-control job-title-input">
                        </div>
                    </div>
                </div>
                <div class="col-sm-6">
                    <div class="form-group row">
                        <label for="contact-email-${elementNumber}" class="col-md-4 label-right">Email <span class="required">*</span></label>
                        <div class="col-md-8">
                            <input name="contacts[contact_email][${elementNumber}]" id="contact-email-${elementNumber}" type="text" value="${value ? value.email : ''}" class="form-control email-input">
                        </div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-6">
                    <div class="form-group row">
                        <label for="office-phone-${elementNumber}" class="col-md-4 label-right">Office Phone</label>
                        <div class="col-md-8">
                            <input name="contacts[office_phone][${elementNumber}]" id="office-phone-${elementNumber}" type="text" value="${value ? value.office_phone : ''}" class="form-control">
                        </div>
                    </div>
                </div>
                <div class="col-sm-6">
                    <div class="form-group row">
                        <label for="mobile-phone-${elementNumber}" class="col-md-4 label-right">Mobile Phone</label>
                        <div class="col-md-8">
                            <input name="contacts[mobile_phone][${elementNumber}]" id="mobile-phone-${elementNumber}" type="text" value="${value ? value.mobile_phone : ''}" class="form-control">
                        </div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-12">
                    <div class="form-group row">
                        <label for="contact-notes-${elementNumber}" class="col-md-2 label-right">Notes</label>
                        <div class="col-md-10">
                            <textarea name="contacts[contact_notes][${elementNumber}]" id="contact-notes-${elementNumber}" class="form-control">${value ? value.notes : ''}</textarea>
                        </div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-12 test-right">
                    <span class="btn-danger btn pull-right delete-contact" id="delete-contact-button-${elementNumber}">Delete contact</span>
                </div>
            </div>
        </div>
    `;
}

So, creating new fields and generating existing ones from json strings fulfills the same function.

JSON example:

[{
    "building_contact_id": 781,
    "building_id": 76516,
    "job_title": "Officer",
    "first_name": "John",
    "last_name": "",
    "mobile_phone": "",
    "office_phone": "",
    "email": "john.doe@mail.com",
    "notes": "",
    "created_at": "2020-06-02T13:42:32.000000Z",
    "updated_at": "2020-06-02T13:42:32.000000Z"
}]
Vlodko
  • 303
  • 1
  • 2
  • 12
  • `firstName = $('#first-name-' + elementNumber)` is not a "dynamic" variable - it gets matching elements that exist at the time. So `firstName.rules` will only apply to elements that exist at the time it runs. – freedomn-m Jun 02 '20 at 15:21

1 Answers1

0

Needed to be wrapped in a timeout:

setTimeout(function () {
    if (firstName.length) {
        firstName.rules("add", {
            required: true
        });
    }

    if (jobTitle.length) {
        jobTitle.rules("add", {
            required: true
        });
    }

    if (contactEmail.length) {
        contactEmail.rules("add", {
            required: true
        });
    }
}, 100)
Vlodko
  • 303
  • 1
  • 2
  • 12
  • Instead of timers, which are gambling on something being done in time, use a callback function, which guarantees that the function is complete before continuing. See: https://stackoverflow.com/a/21518470/594235 – Sparky Jun 02 '20 at 20:21
  • The conditional seems completely pointless... `if (firstName.length){...}` If this conditional fails, you're still in the same situation with no rules on these fields. – Sparky Jun 02 '20 at 20:23