We’veÂ had a few projects at Panoptic Development recently that made use of the RailsAdmin gem. Out of the box, RA satisfied about 95% of what we neededÂ to do to complete eachÂ project, but there were a fewÂ edge requirements that we either had to make do without or find workaroundsÂ for. Don’t get me wrong, RA is a wonderful utility for quickly spinning up an administrative area for your Rails app. It’s loaded with really slick bells-and-whistles, and it has an incredibly flexible DSL for configuration. However, there are a few things it just doesn’t seem to be able to handle as-is and for which I’veÂ searched high and low for solutions for the lastÂ 9 months. I’m happy to report that I finally cracked one of these problems, and it wasn’t even all thatÂ complicated in the end.
The problem was this: For any given
Changeset#model value, only a few valuesÂ are valid for the
Change#field attribute. Let’s start with the two models:
[gist id=82713f9847187b3f2be7 file=changeset.rb]
[gist id=82713f9847187b3f2be7 file=change.rb]
In RailsAdmin lingo, both of the
Change#field attributes are presented as
enum fields. Here’s how this looks in RailsAdmin:
Out-of-the-box, you can provide RA with a list of possible valuesÂ for any given enum field and it will create a
<select>Â elementÂ with those values as options, then transform it into a combobox widget. When the enum field is part of a nested
has_many association form,Â the
<select> element and options are drawn once in a hidden “blueprint”
<div> which is then copied for each newÂ associated object you add. In other words, the enum values are evaluated once at runtime, and that’s that. What I needed was for these two enum fields to be linked, with the options available in the select lists of the children to beÂ dependent on the selected valueÂ ofÂ the select list in the parent, and to do it dynamically.
The first step in solving this was to create a way that we can get a list of valid
Change#field values for a given
[gist id=82713f9847187b3f2be7 file=changes_controller.rb]
[gist id=82713f9847187b3f2be7 file=routes.rb]
With that done it was then just a matter of reading through the RA source code, reverse engineering how it implements its filtered selectÂ widget and rebuilding the
<select> elements with the new options:
[gist id=82713f9847187b3f2be7 file=ui.coffee]
updateAttributes accepts one argument,
selects, which defaults to all of the nested
Change#field fields in the form. We setup a couple events so that it will be called anytime either of the two enum fields’ value changes. When it is called it fires off an AJAX request to our new controller method and sets upÂ a promise to to be run if the AJAX request completes successfully.Â When that happens it iterates over the array of valid
field values that were returned and creates new
<option> tags for each of them. Finally there’s a pretty long chain of function calls that: destroys the existing filteredSelect widget, finds and then removes all of the current
<option> tags in the select except for the first one (it’s an empty option), appends the new
<option> elements, creates a newÂ filteredSelect widget (which picks up the new options), and sets the selected value to whatever was selected before.
The two change event triggers handle a couple different scenarios. The first case is if the
Changeset#model field changes, in which case we update all existing nested
<select> elements. The second case is when RA’s customÂ
nested:fieldAdded event is triggered after a new nested association fieldset is generated (i.e. after the “Add a new Changed Field” button is pressed), and we update only that new
Perhaps this could be spun off into a new custom field type for RailsAdmin at some point. For now, I’m just happy it works for this particular project.