Skip to content

reformgroup/dynamic_nested_forms

Repository files navigation

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.

Dynamic Nested Forms screenshot

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 "&times;".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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published