Rails – How to use modal form to add object in one model, then reflect that change on main page

ajaxjqueryruby-on-rails

I'm working on a Rails app and I've come across a situation where I'm unsure of the cleanest way to proceed.

I posted a question on SO with code samples and such – it has received no answers, and the more I think about the problem, the more I think I might be approaching this the wrong way. (See the SO question at https://stackoverflow.com/questions/9521319/how-to-reference-form-when-rendering-partial-from-js-erb-file)

So, in more of a generic architecture type question: Right now I have a form where a user can add a new recipe. The form also allows the user to select ingredients (it uses a collection_select which contains Ingredient.all). The catch is – I'd like the user to be able to add a new ingredient on the fly, without leaving the recipe form.

Using a hidden div and some jQuery/AJAX, I have a link the user can click to popup a modal form containing ingredients/new.html.erb which is a simple form. When that form is submitted, I call ingredients/create.js.erb to validate the ingredient was saved and hide the modal div. Now I am back to my recipe form, but my collection_select hasn't updated.

It seems I have a few choices here:

  1. try and re-render the collection_select portion of the form so it grabs a new list of ingredients. This was the method I was attempting when I wrote the SO question. The problem I run into is the partial I use for the collection_select needs the parent form passed in, and when I try and render from the JS file I don't know how to pass it the form object.

  2. Reload the recipe form. This works (the collection_select now contains the new ingredient), but the user loses any progress they made on the recipe form. I would need a way to persist the form data – I thought about manually passing the values back and forth, but that is sloppy and there has to be a better way…

  3. Try and manually insert the tags using jQuery – this would be simple, but because I'm allowing for multiple ingredients to be added, I can't be certain what ID to target.

Now, I can't be the only person to have this issue – so is there an easier way I'm missing? I like option 2 above, but I don't know if there's an easy way to grab the entire params hash as if I had submitted the main recipes form.

Hopefully someone can point me in the right direction so I can find an answer to this… If this doesn't make any sense at all, let me know – I can post code samples if you want, but most of the pertinent code is up on the SO question.

Thanks!

Best Answer

I haven't done this with creating new items, but I have done something similar for edits. I'm not sure if this is the 'cleanest' way of doing it, but it worked for me.

The following works with Rails 3.1, jQuery and coffeescript. If you're using Rails 3.2, I think you now have to put .erb on the end of the coffeescript file. You can use js2coffee to convert the below easily (if you're not using coffeescript).

I defined the dialog code (in /app/views/drops/edit.js.coffee) as:

$("#edit-drop-form").dialog
  autoOpen: true
  height: 460
  width: 380
  title: 'Edit Drop'
  buttons:
    "Cancel": ->
      $("#edit-drop-form").dialog "close"
    "Save": ->
      $.post "/drops/<%= @drop.id %>.json", $("#edit-drop-form form").serializeArray(), (data, text, xhr) ->
        if (xhr.status == 200)
          # Update the DOM here!
          $("#notice").empty().append("Drop updated successfully!")
          $("#edit-drop-form").dialog "close"
  open: ->
    $("#edit-drop-form").html "<%= escape_javascript( render('form') ) %>"

In the above example, the 'form' partial will be rendered in the popup and a json handler in the 'update' action of my Drops controller will process the request.

To change this for creating new items, add a 'json' handler for the create action and have a similar javascript file to the above (but called new.js.coffee). You'll also need to adjust the path for the 'post' to the create action.

Where my comment says "Update the DOM here", is where you'll want to update your select. Simply create a json handler for the 'index' action of IngredientsController, call '$.get "/ingredients.json"' and replace the contents of your select based on the data that returns.