DynamicNestedForms
Dynamic Nested Forms with autocomplete and using jQuery. Gem helps to make the simple dynamic control of multiple nested forms in Ruby on Rails application.
- Search for related objects using jQuery UI Autocomplete.
- Easy to add sub-objects directly to a form to create or update the main object.
- Full management of the nested form.
Installation
Dynamic Nested Forms works with Rails 5.0 onwards Add this line to your application's Gemfile:
gem "dynamic_nested_forms"
And then execute:
$ bundle install
Or install it yourself as:
$ gem install dynamic_nested_forms
Usage
Configuring Assets
Because Dynamic Nested Forms using jQuery UI Autocomplete and internal methods of gem, so add to application.js
app/assets/javascripts/application.js
//= require jquery
//= require jquery-ui
//= require dynamic_nested_forms
If you want to include default styles jQuery UI Autocomplete, add in application.css
app/assets/stylesheets/application.css
*= require jquery-ui
Configuring Models
Create related models using standard methods Rails
app/models/patient.rb
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
accepts_nested_attributes_for :appointments, allow_destroy: true
accepts_nested_attributes_for :physicians
end
app/models/physician.rb
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
app/models/appointment.rb
class Appointment < ApplicationRecord
belongs_to :patient
belongs_to :physician
end
Configuring Controllers
Create contrller for main model and additional controller relationship model
app/controllers/patients_controller.rb
class PatientsController < ApplicationController
...
# GET /patients/new
def new
@patient = Patient.new
end
# GET /patients/1/edit
def edit
end
# POST /patients
def create
@patient = Patient.new(patient_params)
if @patient.save
redirect_to @patient, notice: 'Patient was successfully created.'
else
render :new
end
end
# PATCH/PUT /patients/1
def update
if @patient.update(patient_params)
redirect_to @patient, notice: 'Patient was successfully updated.'
else
render :edit
end
end
...
end
In additional controller add index action. This will generate collections with searched objects.
-
params[:term]
- Param with insert chars to autocomplete_to_add_item in view. -
params[:added_obj]
- Optional. This param help you to save only unique nested objects.
app/controllers/appointments_controller.rb
class AppointmentsController < ApplicationController
def index
@appointments = Physician.where("name LIKE ?", "%#{params[:term]}%")
render json: @appointments.map { |u| { value: u.id, label: u.name, content: u.name }}
end
end
Strong Parameters
Don't forget to include the attributes of the associated model into the controller of the main model.
app/controllers/patients_controller.rb
class PatientsController < ApplicationController
...
def patient_params
params.require(:patient).permit(:name, appointments_attributes: [:id, :patient_id, :physician_id, :_destroy])
end
...
end
Configuring Routes
All three controller Teams, Users, and TeamUsers should be included in the file routes.rb
config/routes.rb
Rails.application.routes.draw do
resources :patients
resources :physicians
end
Configuring Views
Add helper autocomplete_to_add_item
to enable the search for objects of the associated model and add them as nested form in a main form
autocomplete_to_add_item(name, f, association, source, options = {})
source - The URL the value from search_to_add_fields to be searched.
app/views/patients/_form.html.erb
<%= form_with(model: patient, local: true) do |form| %>
<div class="field nested-container">
<%= form.label "Appointments" %>
<%= autocomplete_to_add_item "autocomplete_appointment", form, :appointments, appointments_index_path %>
<div class="nested-items">
<%= form.fields_for :appointments, @appointments do |appointment_f| %>
<%= render "appointment_item", f: appointment_f %>
<% end %>
</div>
</div>
<% end %>
app/views/patients/_appointment_item.html.erb
<div class="nested-item">
<div class="nested-content">
<% if f.object.physician %>
<%= f.object.physician.name %>
<% end %>
</div>
<%= f.hidden_field :physician_id, class: "nested-value" %>
<%= f.hidden_field :_destroy %>
<%= link_to_remove_item %>
</div>
Sylize
You can add you class to autocomplete form
app/views/patients/_form.html.erb
<%= autocomplete_to_add_item "autocomplete_appointment", form, :appointments, appointments_index_path, class: "my-class" %>
... or to link_to_remove_item
app/views/patients/_appointment_item.html.erb
<%= link_to_remove_item "Remove", class: "my-class" %>
Rename default link_to_remove_item
app/views/patients/_appointment_item.html.erb
<%= link_to_remove_item "Delete appointment" %>
... or add icon or special char to link_to_remove_item
app/views/patients/_appointment_item.html.erb
<%= link_to_remove_item "×".html_safe %>
If you need only unique objects
In additional controller add option params[:added_obj]
. This will generate collections with searched unique objects.
app/controllers/appointments_controller.rb
class AppointmentsController < ApplicationController
def index
@appointments = Physician.where("name LIKE ?", "%#{params[:term]}%").where.not(id: params[:added_obj])
render json: @appointments.map { |u| { value: u.id, label: u.name, content: u.name }}
end
end
Contributing
Fork the project and send a pull request or add an Issue on GitHub
License
The gem is available as open source under the terms of the MIT License.