#403 Dynamic Forms pro
Learn how to add fields to a form dynamically using another form, complete with custom field types. It's fieldception!
- Download:
- source codeProject Files in Zip (66 KB)
- mp4Full Size H.264 Video (25 MB)
- m4vSmaller H.264 Video (14 MB)
- webmFull Size VP8 Video (18.7 MB)
- ogvFull Size Theora Video (27.9 MB)
Resources
terminal
rails g scaffold ProductType name --skip-stylesheets rails g model ProductField name field_type required:boolean product_type:belongs_to rails g migration add_type_to_products product_type_id:integer properties:text rake db:migrate
models/product_type.rb
class ProductType < ActiveRecord::Base attr_accessible :name, :fields_attributes has_many :fields, class_name: "ProductField" accepts_nested_attributes_for :fields, allow_destroy: true end
models/product.rb
class Product < ActiveRecord::Base attr_accessible :name, :price, :product_type_id, :properties belongs_to :product_type serialize :properties, Hash validate :validate_properties def validate_properties product_type.fields.each do |field| if field.required? && properties[field.name].blank? errors.add field.name, "must not be blank" end end end end
product_types/_form.html.erb
<%= f.fields_for :fields do |builder| %> <%= render 'field_fields', f: builder %> <% end %> <%= link_to_add_fields "Add Field", f, :fields %>
product_types/_field_fields.html.erb
<fieldset> <%= f.select :field_type, %w[text_field check_box shirt_sizes] %> <%= f.text_field :name, placeholder: "field_name" %> <%= f.check_box :required %> <%= f.label :required %> <%= f.hidden_field :_destroy %> <%= link_to "remove", '#', class: "remove_fields" %> </fieldset>
products/index.html.erb
<%= form_tag new_product_path, method: :get do %> <%= select_tag :product_type_id, options_from_collection_for_select(ProductType.all, :id, :name) %> <%= submit_tag "New Product", name: nil %> <% end %>
products_controller.rb
def new @product = Product.new(product_type_id: params[:product_type_id]) end
products/_form.html.erb
<%= f.hidden_field :product_type_id %> <%= f.fields_for :properties, OpenStruct.new(@product.properties) do |builder| %> <% @product.product_type.fields.each do |field| %> <%= render "products/fields/#{field.field_type}", field: field, f: builder %> <% end %> <% end %>
products/fields/_text_field.html.erb
<div class="field"> <%= f.label field.name %><br /> <%= f.text_field field.name %> </div>
products/fields/_check_box.html.erb
<div class="field"> <%= f.check_box field.name %> <%= f.label field.name %> </div>
products/fields/_shirt_sizes.html.erb
<div class="field"> <%= f.label field.name %><br /> <%= f.select field.name, %w[S M L XL], {}, {multiple: true} %> </div>
helpers/application_helper.rb
module ApplicationHelper def link_to_add_fields(name, f, association) new_object = f.object.send(association).klass.new id = new_object.object_id fields = f.fields_for(association, new_object, child_index: id) do |builder| render(association.to_s.singularize + "_fields", f: builder) end link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")}) end end
assets/javascripts/product_types.js.coffee
$(document).on 'click', 'form .remove_fields', (event) -> $(this).prev('input[type=hidden]').val('1') $(this).closest('fieldset').hide() event.preventDefault() $(document).on 'click', 'form .add_fields', (event) -> time = new Date().getTime() regexp = new RegExp($(this).data('id'), 'g') $(this).before($(this).data('fields').replace(regexp, time)) event.preventDefault()