Sławosz Sławiński home

Add new resource with ajax

In Railscast 258 Ryan Bates shows how to use Token Fields, javascript plugin which helps adding entries for many and many to many association. In this article I will exetend railscast and show you how to create not existing entries with ajax.

You can download application for this tutorial from github or use it on heroku to see how it works. I started with application created by Ryan in 258 episode.

To add authors we can go to /author/new url, but when we already on /books/new we rather want to render form with ajax. To render form we will use facebox, writen in jquery by Chris Wanstrath. So, simply download facebox from github, decompress and place facebox.css in public/stylesheets, facebox.js in public/javascripts and loading.gif and closelabel.png in public/images

Now, point write paths for images in public/javascripts/facebox.js:

loadingImage: '/images/loading.gif',
closeImage: '/images/closelabel.png',

and place stylesheet and javascript in our layout app/views/layouts/application.html.erb:

<%= stylesheet_link_tag "application", "token-input-facebook","facebox" %>
<%= javascript_include_tag :defaults, "jquery.tokeninput","facebox" %>

Now it is time to show form for new author resource on facebox.

Lets add link in app/views/books/_form.html.erb partial:

<%= f.label :author_tokens, "Authors" %><%= link_to 'Add new author', new_author_path, :remote => true %><br />

It looks like a normal link, but with one exception: it has a remote option, indicating, that we want perform an ajax request with this link. This is new way in Rails 3 to generate ajax request and I think it is great. It simply adds a html data-remote='true' attribute to our link. Javascript code located in public/javascripts/rails.js binds onclick event on such link. Whe link is clicked, an ajax request is performed to links url, in our case to new_author_path. So now, we have rewrite new method in AuthorsController, which is our link destination. You can read more about Rails 3 unobtrusive javascript in Simone Carletti blog.

With Rails it is so simple :) We had to add only a new template in js format, app/views/authors/new.js.erb. When we click link, method new will recognize that request is in js format and render js template. So now, in template we will write code, which will render new action on facebox:

$.facebox('<%= escape_javascript(render :template => 'books/new.html') %>');

Code above generate a form on facebox with app/views/books/new.html.erb template. We have to add write new.html to generate html. Much more often in rich ajax Rails application we use partials, but I would like to show how to achieve our goal with minimal effort.

If you would like to know more about using facebox, see public/javascripts/facebox.js. Every use case is described in this file.

Now, we want to send our form and create a new author.
Normaly, we can use

:remote => true

option in our form, but we only use remote form in facebox. So, let’s add data-remote attribute to ajax form in facebox. To do it, we will add following to app/views/books/new.js.erb:

$('#facebox form').data('remote','true');
This will add attribute data-remote='true' to our form, and with this attribute, public/javascripts/rails.js will process our form with ajax. After submiting, the request goes to method create in AuthorsController and is looking for js template. In this template, we need only close facebok. So, we create file app/views/authors/create.js.erb and write:
$(document).trigger('close.facebox');

Thus we would like to perform other actions in html and js request in this method, we use respond_to block and redirect on html format and render template on js format:

def create
  @author = Author.new(params[:author])
  if @author.save
    respond_to do |format|
      format.html { redirect_to @author, :notice => "Successfully created author." }
      format.js
    end
  else
    render :action => 'new'
  end
end

Now it works, but it need some more improvements. When @author instance cannot be save, we should render form again in facebox. In app/models/author.rb we add validadion:

validates :name, :presence => true

Now we cannot add new no-name author. When we try, we got validation error, which we want to show in facebox.

At first we need to change create action to use js format in else section:

def create
  @author = Author.new(params[:author])
  if @author.save
    respond_to do |format|
      format.html { redirect_to @author, :notice => "Successfully created author." }
      format.js
    end
  else
    respond_to do |format|
      format.html { render :action => 'new' }
      format.js
    end
  end
end

And finally change app/views/books/create.js.erb:

<% if @author.save %>
  $(document).trigger('close.facebox')
<% else %>
  $('#facebox .content').html('<%= escape_javascript(render :template => 'authors/new') %>')
  $('#facebox form').data('remote','true');
<% end %>

Now we have fully working form on facebox, with minimal changes in our code. We can do last small improvement, remove link ‘Back to List’. In facebox link is completely unnecessary. After checking source of app/views/authors/new.html.erb we simply need remove last <p> tag. In jQuery, it’s simple:

$('#facebox .content p:last').remove();

We add this line at the end app/views/authors/new.js.erb and at the end in else section in app/views/authors/create.js.erb. Now we are done.

See our application working on heroku, and check the
repo on github.
All changes you can see in cf13adacdff595059dc6eba0fcb33c0204ad6714

I hope this tutorial will be helpfull for You. Any feedback is welcome.

Tweet
blog comments powered by Disqus
Fork me on GitHub