stimulus-rails-nested-form
stimulus-rails-nested-form copied to clipboard
Add support many nested forms
In one of my pet project, I've found the issue. That component doesn't work if you need to have more than one nested model form. I've fixed and also save the compatibility with one model nested form.
I second this pull request. 😅 I'm currently using it on a few sites and it's working great.
Thanks @NikitaNaumenko for the PR
Can you fix conflicts so I can review it?
@guillaumebriday yep, I'll do it
@guillaumebriday done!
Hi.
Hi @guillaumebriday do you have an idea of when this may get merged?
Thanks
Hey folks. Maybe I have this wrong, but it looks like you can use multiple nested forms now. I should probably have commented on this ticket, but I've left notes here: https://github.com/stimulus-components/stimulus-rails-nested-form/issues/8#issuecomment-942239527
Basically, just moving the controller attributes out of the form_with
proper and into separate divs
seems to do the trick. Just verified that it's working in an app I've working on, to make sure I wasn't going mad.
I think the problem is this line
const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime().toString())
Since using /g
will replace every ocurrence of NEW_RECORD
then deep nested controller scopes will receive the parent timestamp id.
I think the problem is this line
const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime().toString())
Since using/g
will replace every ocurrence ofNEW_RECORD
then deep nested controller scopes will receive the parent timestamp id.
Allowing to customize the NEW_RECORD placeholder for each nested form builder worked for me.
static values = {
wrapperSelector: {
type: String,
default: '.nested-form-wrapper'
},
newRecordPlaceholder: {
type: String,
default: 'NEW_RECORD'
},
}
add(e) {
e.preventDefault()
const regex = new RegExp(`${this.newRecordPlaceholderValue}`, 'g')
const content = this.templateTarget.innerHTML.replace(regex, new Date().getTime().toString())
this.targetTarget.insertAdjacentHTML('beforebegin', content)
}
In my case the first form receives a child_index placeholder of "NEW_RECORD_1" and the deep nested receives "NEW_RECORD_2" for example.
I think the problem is this line
const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime().toString())
Since using/g
will replace every ocurrence ofNEW_RECORD
then deep nested controller scopes will receive the parent timestamp id.Allowing to customize the NEW_RECORD placeholder for each nested form builder worked for me.
static values = { wrapperSelector: { type: String, default: '.nested-form-wrapper' }, newRecordPlaceholder: { type: String, default: 'NEW_RECORD' }, } add(e) { e.preventDefault() const regex = new RegExp(`${this.newRecordPlaceholderValue}`, 'g') const content = this.templateTarget.innerHTML.replace(regex, new Date().getTime().toString()) this.targetTarget.insertAdjacentHTML('beforebegin', content) }
In my case the first form receives a child_index placeholder of "NEW_RECORD_1" and the deep nested receives "NEW_RECORD_2" for example.
Using a custom stimulus value solves this issue but maybe a smarter way to set this id could bring a better experience. Any ideas?
Can confirm @aquilarafa 's idea works for many layers of nested forms.
@ferrisoxide I think the issue is when you need to have a nested form that has 2 layers or more, the template
in the second layer will be created with name value like this:
policy[agents_attributes][1692332505676][overrides_attributes][1692332505676][override_type]
Notice the same id for both parent and child records. This will make the form only submit the last item created in the 2nd layer, because all inputs in that layer will have the same name.
The correct template name value should be something like this:
policy[agents_attributes][1692332505676][overrides_attributes][NEW_OVERRIDE][override_type]
For a slightly "smarter" solution, I guess since we can get child_index value from the form builder:
# will return 'NEW_RECORD'
fields_for_form_builder.options[:child_index]
We can add a hidden field to the template, then figure out the given child index from the Stimulus controller?