Giving super powers to Rails nested forms with Vue.js — Part 2
After my first attempt to use VueJS with Rails nested forms I ended up with mix feelings. I really liked the code, but the exponential fields bug made me very discouraged.
The improvements in code clarity was so great that it didn't let me give up, so I keep thinking about the integration problems.
The first thing I noticed was the way I was rendering the form. It rendered all the fields, with it's errors, and this was being used as the template in VueJS. This caused the exponential bug: VueJS data multiplied by the rendered fields. So the first lesson was:
- Always render the form in a way that can be used as a template by VueJS
I made some little changes to the build_nested_form method in my controller to follow the first lesson:
I exposed the method as a helper and also added the ignore_params. Now I construct my form with = simple_form_for build_nested_form(true), url: nested_form_good_path do |f|
This is like always render the form as in the new action. This worked very well, except for the errors. When I add a category/deadline, I don't want it to have the error messages. This lead to the second lesson:
- Avoid errors information outputed by form helpers
This means that your HTML must have the markup to add error classes, like has-error and also the VueJS markup to iterate over the errors of a specific field. For example, this is the code to generate the category input field:
I'm using the compact_input_group wrapper as an inheritance from my real application. It's original function was make the error message more compact. Now I'm using it to not add the has-error class in the template. It’s messing the VueJS magic :/
But all this didn't come easy. I'm generating the errors with errors= #{@nested_form.errors.to_json}
and if you take a look at the generated HTML you will get:
It's far from ideal to mark proper fields with it's errors, but fortunately Rails 5 merged a new feature for has_many options, it's called index_errors. I configured it globally with ActiveRecord::Base.index_nested_attributes_errors = true
and get this cool output:
I don't program much in JavaScript, so it take me while to realize how to put these errors inside my nested form JSON representation. I find a really simple solution, and I guess it's secure, using eval
:
I'm also finishing a pull-request to active_type, so it also support index_errors.
You can see the live example in https://vuejs-nested-form.herokuapp.com and dig into it's code https://github.com/cerdiogenes/vuejs-nested-form. The good part also show my initial motivation to use VueJS: keep a column in sync.
Conclusion
Want to use VueJS in dynamic nested forms? Follow these rules:
- Always render a form that can be used as a template (probably this is the same form used in the new action)
- Use your own markup and VueJS magic for errors
- Use index_errors in your relationships
Have fun!