DynamicFieldsFor
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
Alternatives
Dependencies
- Ruby >= 2.2.2
- rails >= 5.0.0
- jquery-rails
-
activerecord-devkit,
association_soft_buildfeature
For older versions of ruby and rails - please use gem version 1.1.0.
Getting started
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-forUsage
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
endFirst, 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
endAdd to User model:
accepts_nested_attributes_for :roles, allow_destroy: trueSkip 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.submitDynamicFieldsFor 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.submitNot ActiveRecord models
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
endTemplate 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.submitJavaScript events
There are the events which will be triggered on add_fields_link click, in actual order:
-
dynamic-fields:before-add-intotouched to dynamic fields parent node; -
dynamic-fields:after-addtouched to each first-level elements which were inserted; -
dynamic-fields:after-add-intotouched to dynamic fields parent node;
Like that, these events will be triggered on add_fields_link click, in actual order:
-
dynamic-fields:before-remove-fromtouched to dynamic fields parent node; -
dynamic-fields:before-removetouched to each first-level elements which are going to be removed; -
dynamic-fields:after-remove-fromtouched 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();
})License
MIT License. Copyright (c) 2015 Sergey Tokarenko