DynamicFieldsFor is a Rails plugin which provides the dynamic association fieldsets to your forms without pain. And it does nothing else.
The main features are:
- Doesn't break the HTML layout - no wrappers, additional divs etc;
- Works with fields block, i.e. doesn't require the separated partial for them;
- Doesn't provide new form helpers, but extends the existing one;
- Simple and predictable interface and behavior;
- Doesn't require any special HTML entities inside templates;
- Supports Simple Form.
- Supports not ActiveRecord models
- Supports nested dynamic fields
- Ruby >= 2.2.2
- rails >= 5.0.0
- jquery-rails
- activerecord-devkit,
association_soft_build
feature
For older versions of ruby and rails - please use gem version 1.1.0
.
Add to your Gemfile:
gem 'dynamic-fields-for'
Run the bundle command to install it.
Add to app/assets/javascripts/application.js
:
//= require dynamic-fields-for
Lets say that we have the models:
class User < ActiveRecord::Base
has_many :roles
end
class Role < ActiveRecod::Base
belongs_to :user
validates :user, presence: true
end
First, apply inverse_of
to User's :roles
associations, otherwise there is no chance to pass
the validation of Role's user presence on user creation:
class User < ActiveRecord::Base
has_many :roles, inverse_of: :user
end
Add to User model:
accepts_nested_attributes_for :roles, allow_destroy: true
Skip allow_destroy
definition if you don't need to use remove_fields_link
helper).
Take care about strong parameters in controller like this:
params.require(:user).permit(roles_attributes: [:id, :_destroy])
It is important to permit id
role's parameter, don't miss it. As for _destroy
,
skip it if you don't need to use remove_fields_link
helper.
Then, in view:
= form_for resource do |f|
= f.text_field :user_name
= f.fields_for :roles, dynamic: true do |rf|
= rf.text_field :role_name
= rf.remove_fields_link 'Remove role'
= f.add_fields_link :roles, 'Add role'
= f.submit
DynamicFieldsFor supports SimpleForm:
= simple_form_for resource do |f|
= f.input :user_name
= f.simple_fields_for :roles, dynamic: true do |rf|
= rf.input :role_name
= rf.remove_fields_link 'Remove role'
= f.add_fields_link :roles, 'Add role'
= f.submit
To use DynamicFieldsFor with not ActiveRecord, it's necessary to define two methods in your model, {association}_soft_build
and {association}_attributes=
:
class EmailForm
include ActiveAttr::Model
attribute :recipients, type: Object, default: []
def recipients_attributes=(attributes)
self.recipients = attributes.values.map{ |attrs| recipients_soft_build(attrs) }
end
def recipients_soft_build(attrs = {})
Recipient.new(attrs)
end
end
class Recipient
include ActiveAttr::Model
attribute :email
validates :email, presence: true
end
Template will stay to be as usual:
= form_for resource do |f|
= f.fields_for :recipients, dynamic: true do |rf|
= rf.text_field :email
= rf.remove_fields_link 'Remove recipient'
= f.add_fields_link :recipients, 'Add recipient'
= f.submit
There are the events which will be triggered on add_fields_link
click, in actual order:
dynamic-fields:before-add-into
touched to dynamic fields parent node;dynamic-fields:after-add
touched to each first-level elements which were inserted;dynamic-fields:after-add-into
touched to dynamic fields parent node;
Like that, these events will be triggered on add_fields_link
click, in actual order:
dynamic-fields:before-remove-from
touched to dynamic fields parent node;dynamic-fields:before-remove
touched to each first-level elements which are going to be removed;dynamic-fields:after-remove-from
touched to dynamic fields parent node;
Typical callback for dynamic fields parent node looks like:
$(document).on('dynamic-fields:after-add-into', function(event){
$(event.target).find('li').order();
})
As for first-level elements, compatible callbacks
will be triggered to each of them. To deal with this,
use $.find2
javascript helper, which provided by DynamicFieldsFor:
$('#some_id').find2('.some_class');
// doing the same as...
$('#some_id').find('.some_class').add($('#some_id').filter('.some_class'));
Typical event callback first-level elements should look like:
$(document).on('dynamic-fields:after-add', function(event){
$(event.target).find2('.datepicker').datetimepicker();
})
MIT License. Copyright (c) 2015 Sergey Tokarenko