In this blog, I'll show how to create complex forms in Rails 4,
where one can "add fields on the fly" by using JavaScript.
This issue on how to "add fields on the fly" inspired me
when I was learning how to create complex forms by using Ruby on Rails.
While learning, I went through the Rails Guide which covers
complex forms
creation in detailed way, apart from
showing how to
"add fields on the fly".
"Adding fields on the fly" is not covered fully,
as it's not directly supported by Rails, because
it involves the usage of JavaScript. Well, I was
intrigued by this issue, and I wanted to know how
to mix JavaScript with complex forms creation.
Following Peter Rhoades'
excellent article's steps, I decided to approach
this issue from the very start, step-by-step: First, I'll create the rails project,
and then create the Person and Address models used in this sample.
After that I'll update _form
partial
that is used for Person and Address creation.
Finally, some JavaScript is added to the project to allow "adding fields on the fly", which
also makes a more interactive look to the page.
About the models, the Person model can have many Addresses, and the Address model belongs to one Person model. By the way, those models are identical to the ones that are presented in the Rails Guide; only the JavaScript part is new. If you want to see JavaScript right away, you can advance directly to the second section of this blog. The source code for this blog is available at GitHub.
Preparation
Okay, lets start by creating a new rails project on command line:
rails new nested_forms
Then install the gems in the project bundle:
cd nested_forms
bundle install
Then generate scaffold for Person, and generate model for Address:
rails generate scaffold Person name
rails generate model Address kind street person_id:integer
Finally migrate those new models into database
bundle exec rake db:migrate
Then update your Person model by adding association:
has_many :addresses
.
Add also line accepts_nested_attributes_for :addresses
to allow Address creation in the same controller where the Person is created.
Ruby on Rails API says:
"Nested attributes allow you to save attributes on associated records through the parent."
And like mentioned in the Rails Guide:
"This creates an addresses_attributes=
method on Person that allows you to create,
update and (optionally) destroy addresses."
# file: app/models/person.rb
class Person < ActiveRecord::Base
has_many :addresses
accepts_nested_attributes_for :addresses
end
Next add belongs_to :person
association to
the generated Address model:
# file: app/models/address.rb
class Address < ActiveRecord::Base
belongs_to :person
end
Then update People controller's strong parameters in method person_params
.
This allows Address creation at the same time when Person is created.
Also update the new
action to so
that it builds two Addresses for new Person.
# file: app/controllers/people.rb
class PeopleController < ApplicationController
def new
@person = Person.new
2.times { @person.addresses.build }
end
....some code left out....
def person_params
params.require(:person).permit(:name,
:addresses_attributes => [:id, :kind, :street])
end
end
class PeopleController < ApplicationController before_action :set_person, only: [:show, :edit, :update, :destroy] # GET /people # GET /people.json def index @people = Person.all end # GET /people/1 # GET /people/1.json def show @addresses = @person.addresses end # GET /people/new def new @person = Person.new 2.times { @person.addresses.build} end # GET /people/1/edit def edit end # POST /people # POST /people.json def create @person = Person.new(person_params) respond_to do |format| if @person.save format.html { redirect_to @person, notice: 'Person was successfully created.' } format.json { render action: 'show', status: :created, location: @person } else format.html { render action: 'new' } format.json { render json: @person.errors, status: :unprocessable_entity } end end end # PATCH/PUT /people/1 # PATCH/PUT /people/1.json def update respond_to do |format| if @person.update(person_params) format.html { redirect_to @person, notice: 'Person was successfully updated.' } format.json { head :no_content } else format.html { render action: 'edit' } format.json { render json: @person.errors, status: :unprocessable_entity } end end end # DELETE /people/1 # DELETE /people/1.json def destroy @person.destroy respond_to do |format| format.html { redirect_to people_url } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_person @person = Person.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def person_params params.require(:person).permit(:name, :addresses_attributes => [:id, :kind, :street]) end end
Finally add fields_for
, label
and text_field
form helpers and some text for adding Address to the _form
partial:
# file: app/views/people/_form.html.erb
Addresses:
<ul>
<%= f.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
<%= addresses_form.label :street %>
<%= addresses_form.text_field :street %>
</li>
<% end %>
</ul>
<div class="actions">
<%= f.submit "Create Person and Addresses" %>
</div>
<%= form_for(@person) do |f| %> <% if @person.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@person.errors.count, "error") %> prohibited this person from being saved: </h2> <ul> <% @person.errors.full_messages.each do |msg| %> <li><%= msg %> </li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %>
<%= f.text_field :name %> </div> Addresses: <ul> <%= f.fields_for :addresses do |addresses_form| %> <li> <%= addresses_form.label :kind %> <%= addresses_form.text_field :kind %> <%= addresses_form.label :street %> <%= addresses_form.text_field :street %> </li> <% end %> </ul> <div class="actions"> <%= f.submit "Create Person and Addresses" %> </div> <% end %>
Now if you want to create a new Person, then you can also create two Addresses at the same time:
If you look at the generated source code below and the created Address elements, you can see that the hash key that is used for identifying an Address is an integer, and starts from 0; the second Address is identified with a hash key of 1. I have used Chrome's Developer Tools for viewing those elements below. No user entered data is shown in this view, because the form hasn't been submitted yet.
Now if you submit the data, you have created two Addresses for Person, who in this case is Bart. Whenever you want to create a Person, then those two Address fields are shown each time. Therefore, the people/new page is a bit static at the moment, but let's change that by allowing "Adding Fields on the Fly" functionality.
"Adding Fields on the Fly"
As shown previously the key value for the Address hash is created incrementally
by using integer numbers such as 0 and 1. This means that the addresses that are created
are unique. In our sample we aren't going to increment integers for hash keys
(although I think it could work);
instead let's follow the Rails Guide:
"When generating new sets of fields you must ensure the key of the associated array is unique
- the current JavaScript date (milliseconds after the epoch) is a common choice."
So let's use JavaScript
Date
object in place of added integer values.
Now we are going to make JavaScript code that creates the Address
elements shown before and uses JavaScript Date
object in place of 0 and 1 values for
keys. Also those elements can only be displayed and created on
the form after clicking an "Add address" button like shown below:
After two "Add address" button clicks and some user entered information we get the following view:
To get the functionality that was shown in the two screenshots above,
and to make JavaScript changes that were mentioned earlier,
we need to add some JavaScript to our project.
Let's add a JavaScript function called addAddressField
to app/assets/javascripts folder in a file called people.js
.
Main parts of the function code is highlighted below. You
can also view the full code by clicking the link View Full Code.
# file: app/assets/javascripts/people.js
function addAddressField() {
//create Date object
var date = new Date();
//get number of milliseconds since midnight Jan 1, 1970
//and use it for address key
var mSec = date.getTime();
//Replace 0 with milliseconds
idAttributKind =
"person_addresses_attributes_0_kind".replace("0", mSec);
nameAttributKind =
"person[addresses_attributes][0][kind]".replace("0", mSec);
//create <li> tag
var li = document.createElement("li");
....some code left out....
//create input for Kind, set it's type, id and name attribute,
//and append it to <li> element
var inputKind = document.createElement("INPUT");
inputKind.setAttribute("type", "text");
inputKind.setAttribute("id", idAttributKind);
inputKind.setAttribute("name", nameAttributKind);
li.appendChild(inputKind);
....some code left out....
//add created <li> element with its child elements
//(label and input) to myList (<ul>) element
document.getElementById("myList").appendChild(li);
//show address header
$("#addressHeader").show();
}
View Full Code
function addAddressField() { //create Date object var date = new Date(); //get number of milliseconds since midnight Jan 1, 1970 //and use it for address key var mSec = date.getTime(); //Replace 0 with milliseconds idAttributKind = "person_addresses_attributes_0_kind".replace("0", mSec); nameAttributKind = "person[addresses_attributes][0][kind]".replace("0", mSec); idAttributStreet = "person_addresses_attributes_0_street".replace("0", mSec); nameAttributStreet = "person[addresses_attributes][0][street]".replace("0", mSec); //create <li> tag var li = document.createElement("li"); //create label for Kind, set it's for attribute, //and append it to <li> element var labelKind = document.createElement("label"); labelKind.setAttribute("for", idAttributKind); var kindLabelText = document.createTextNode("Kind"); labelKind.appendChild(kindLabelText); li.appendChild(labelKind); //create input for Kind, set it's type, id and name attribute, //and append it to <li> element var inputKind = document.createElement("INPUT"); inputKind.setAttribute("type", "text"); inputKind.setAttribute("id", idAttributKind); inputKind.setAttribute("name", nameAttributKind); li.appendChild(inputKind); //create label for Street, set it's for attribute, //and append it to <li> element var labelStreet = document.createElement("label"); labelStreet.setAttribute("for", idAttributStreet); var streetLabelText = document.createTextNode("Street"); labelStreet.appendChild(streetLabelText); li.appendChild(labelStreet); //create input for Street, set it's type, id and name attribute, //and append it to <li> element var inputStreet = document.createElement("INPUT"); inputStreet.setAttribute("type", "text"); inputStreet.setAttribute("id", idAttributStreet); inputStreet.setAttribute("name", nameAttributStreet); li.appendChild(inputStreet); //add created <li> element with its child elements //(label and input) to myList (<ul>) element document.getElementById("myList").appendChild(li); //show address header $("#addressHeader").show(); }
The above JavaScript creates new <li> element, two labels, and two input elements for Address entries, which are then appended on to <ul> element of the form. Also an address header is made visible by using jQuery.
For id
attribute values you don't have
to use that long:
person_addresses_attributes_sth_kind
format;
it can be something more shorter, for instance a plain integer. However,
the format of the name
attribute value has to look like to original one:
person[addresses_attributes][sth][kind]
; otherwise the data won't be
saved in the controller. Like said in the
Ruby Rails Tutorial book,
by Michael Hartl: "These name values allow Rails
to construct an initialization hash (via the params variable)
for creating users using the values entered by the user."
In our case Person and Addresses are being created based on the values
entered by the user.
Finally, let's make some changes
to the _form
partial,
so that it uses addAddressField
function.
Now we can remove the fields_for
helper and the code
within fields_for
helper method,
and add submit_tag
and Address header to get the functionality we need.
The changes are small and are highlighted below.
# file: app/views/people/_form.html.erb
<% submit_tag "Add address", :type => "button", :id => "addAddress", :onclick => 'addAddressField()'%>
<br />
<div id="addressHeader" style="display:none">
Address information:
</div>
<br />
<ul id="myList">
</ul>
<div class="actions">
<%= f.submit "Create Person and Addresses" %>
</div>
<%= form_for(@person) do |f| %> <% if @person.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@person.errors.count, "error") %> prohibited this person from being saved: </h2> <ul> <% @person.errors.full_messages.each do |msg| %> <li><%= msg %> </li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %>
<%= f.text_field :name %> </div> <%= submit_tag "Add address", :type => 'button', :id => 'addAddress', :onclick => 'addAddressField()' %> <br> <div id="addressHeader" style="display:none">Address information: </div> <br> <ul id="myList"> </ul> <div class="actions"> <%= f.submit "Create Person and Addresses" %> <div> <% end %>
Now if you look at the generated source code again and the created Address elements, you can
see that the hash key that is used for identifying an Address
are the milliseconds from the JavaScript Date
object.
Finally we can create People and their Addresses by "Adding Fields on the Fly".
It's a very useful functionality. How can I write the javascript part in coffee-script?
VastaaPoistaThanks :-) Well about your question: I haven't used coffee-script that much, but there are some JavaScript -> CoffeeScript converters around that you can use for this purpose, for example: http://priest.meteor.com/ I just tested it and seems to work alright.
VastaaPoistaIs there a gem that does the "adding fields on the fly" part?
VastaaPoistaVery good question Enrique, I'm not sure :-) if I find one, I'll definitely provide the gem details for you.
VastaaPoistaAldaimSolutions as an offshore e-Commerce website development company provides a wide range of online business solutions in web and mobile space.Popular rails gemsRails is basically a web application framework, which is consist of everything needs to create database baked web application. It helps the developers to create websites and applications by providing structures for all codes written by them. Moreover, common repetitive tasks are simplified with the help of this technology.
PoistaWebsites made with PHP and Shopify
Best Ecommerces Company Audacity24
Websites made with PHP and Shopify
Best ruby APIS
React native and React Js
Node Js and React Js
Ok, i think the "add field on the fly" is not precise sentence for target the action. I think it should be "create field ont he fly".
VastaaPoistaWhy ? because i'm the problem to just "add fields on the fly", and not create it. What ? Yes... imagine that in your table Adresses (so handle with model Adress in Rails MVC CoC design), then inside this table, there is allready some adress... so you no have to create them, but just add one you should choose first for add to the Person...
This could be much much more complicate with Rails CoC way of see the world... then now, imagine that a Personn could have many adress, but also at an adress it could be possible to have many persons (that is a real fact happen for all famillies and no isolates people all around the world). then you have a nested relations like has_and_belongs_to many :adress
Ok... so you have a "middle" table without model named: "Adresses_Persons" who just contain foreign keys for link Adress.id to Person.id... so from this real and current situation, how to dynamically ("on the fly") just for exemple open a dialog-box (jquery easy way) with maybe a rails template inside for show (not form now... we show something) Adresses list with a checkbox on each row for selection and submit this new selected list of allready existing adress to the current Person ? that is, for me and with Rails specifically: a big big pain.
Then, you not "add things", you "create things"... with Rails, create is easy by form... add from show is not easy (or i miss something and i'm not alone in this situation).
@Jérôme Lanteri, thank you for your excellent comment & question. + please forgive me for giving such a late reply for your questions. Well, I’ll try to give some kind of answer, although it’s not a definitive answer, just a suggestion how things could work in certain situations. You mentioned: “open a dialog-box (jquery easy way) with maybe a rails template inside for show (not form now... we show something) Adresses list with a checkbox on each row for selection”. -I think this is very difficult to achieve unless the Person in question belongs to group called family, and at least one of the family members is already in the system database. Of course if there was some kind third party API service that could be used for listing potential addresses for Person, for example phone number that was entered before, - then that could be used to look up potential Address data. Anyway, I get your point which is in a sense ideal from a database design perspective, because if there are more than one identical address row in the database, then it leads to redundant data which doesn’t go hand in hand with database normalization and first normal form (https://en.wikipedia.org/wiki/First_normal_form). Of course now this isn’t the case as there’s person_id as a foreign in the db, but otherwise there could be duplicate rows in the Address table. Like you suggested, a middle table "Adresses_Persons" could work in this case (although I haven’t used it in the blog). However, if there are many Persons that have the same address (=one Address row in the database) and many "Adresses_Persons" rows, then what needs to be done if only one Person moves in that family to a new Address? -Then are we going to create one new entry to the Address table and update one "Adresses_Persons" row for that Person? -Alternative are we just going to update just one Address field that that is shared by all Persons in that family through "Adresses_Persons" table? After giving some thought to the issue, I don’t think it’s quite that straightforward, although I guess some kind of redundancy can be avoided. I think the Postcode is often used with only numerical foreign key in the Address table because some redundancy can be avoided that way. Well, I wish I could provide you with better answer :-) Also I tried to Google for some useful answers for this issue but I discovered that there are many different ways of doing this issue, so I guess there’s not just one right way of doing this but several :-)
VastaaPoistaNice blog keep updating your blog and i am waiting for your next update also keep update the blogger.
VastaaPoistaRuby on Rails Online Course Hyderabad
Really Good blog post.provided a helpful information.I hope that you will post more updates like thisRuby on Rails Online Training Bangalore
VastaaPoistaKirjoittaja on poistanut tämän kommentin.
VastaaPoistaAldaimSolutions as an offshore e-Commerce website development company provides a wide range of online business solutions in web and mobile space.Popular rails gemsRails is basically a web application framework, which is consist of everything needs to create database baked web application. It helps the developers to create websites and applications by providing structures for all codes written by them. Moreover, common repetitive tasks are simplified with the help of this technology.
VastaaPoistaWebsites made with PHP and Shopify
Best Ecommerces Company Audacity24
Websites made with PHP and Shopify
Best ruby APIS
React native and React Js
Node Js and React Js
I am regular reader, how are you everybody?
VastaaPoistaThis post posted at this site is really good on ruby on rails online training
Kirjoittaja on poistanut tämän kommentin.
VastaaPoistaRUBY ON RAILS CUSTOM WEB APPLICATION SERVICES
VastaaPoistaaz 104 interview questions
VastaaPoistascrum master interview questions
dp 900 interview questions
Mmorpg oyunları
VastaaPoistaınstagram takipci satın al
tiktok jeton hilesi
tiktok jeton hilesi
antalya saç ekimi
referans kimliği nedir
İnstagram Takipçi Satın Al
Metin2 Pvp Serverler
TAKİPÇİ
perde modelleri
VastaaPoistaMobil Onay
Mobil ödeme bozdurma
Nft Nasil Alınır
Ankara Evden Eve Nakliyat
trafik sigortasi
DEDEKTOR
Kurma websitesi
aşk kitapları
smm panel
VastaaPoistasmm panel
İş ilanları
İnstagram takipçi satın al
HIRDAVATÇI BURADA
beyazesyateknikservisi.com.tr
servis
tiktok jeton hilesi
kartal daikin klima servisi
VastaaPoistakadıköy arçelik klima servisi
ümraniye samsung klima servisi
kartal mitsubishi klima servisi
ümraniye mitsubishi klima servisi
beykoz bosch klima servisi
üsküdar bosch klima servisi
kadıköy bosch klima servisi
ümraniye daikin klima servisi
nft nasıl alınır
VastaaPoistaminecraft premium
en son çıkan perde modelleri
lisans satın al
yurtdışı kargo
uc satın al
özel ambulans
en son çıkan perde modelleri