Sunday, February 12, 2012

Dynamically create and populate select boxes for awesome_nested_set gem rails 3


Here is quick overview how I made dynamically creating and populating select fields, instead of using one select to display all levels. I am using it for selecting one category when creating a new user. gem awesome_nested_set

View code (new user)

<div id="user_category_select"> 
<span id="loading" style="display:none;">Loading...</span>
<%= f.select :user_category_id, category_children(@categories), { :include_blank => true } %>
</div>


Helper

def category_children(objects)
    if(objects)
      output = Array.new
      objects.each do |o|
        output << [o.name, o.id]
      end
      output
    end
  end


controller

def new
      @categories = UserCategory.roots
end
def get_nested_categories
    if(params[:id])
      @categories = UserCategory.find(params[:id]).children
      if(!@categories.empty?)
        respond_to do |format|
          format.js
        end
      else
        render :nothing => true
      end
    else
      render :nothing => true
    end
  end


Javascript

// get actual values from first select
    var real_id = $('#user_category_select select').attr("id");
    var real_name = $('#user_category_select select').attr("name");

    $('#user_category_select select').live('change', function(){
        remove_select_after($(this).next("select"));
        if($(this).val()) {
            manage_select_class($("#user_category_select select"), $(this), real_id, real_name);
            $("#user_category_select #loading").show();
            $.get("get_nested_categories",
                    { id: $(this).val() }, function() { $("#user_category_select #loading").hide(); }, "script");
        } else {
            manage_select_class($("#user_category_select select"), $(this).prev("select"), real_id, real_name);
        }
    });

    // remove all next selects to generate new
    function remove_select_after(object) {
        if(object.length) {
            var next = object.next("select");
            object.remove();
            if(next.length) remove_select_after(next);
        }
    }

    // remove all ids and names and add to current one
    // since there is just one category value to send
    function manage_select_class(remove, assign, id, name) {
        remove.attr("name", ""); remove.attr("id", "");
        assign.attr("name", name); assign.attr("id", id);
    }

get_nested_categories.js

$("#user_category_select").append("<%= escape_javascript(select :not, :selected, category_children(@categories), { :include_blank => true }) %> ");

I am sure that someone could re-factor some code better, especially JS, so fell free to post it in comments


3 comments:

  1. Thanks for the help, i was looking out for this solution.

    But could you pls elaborate more on the javascript code?
    where exactly do we need to save the code you posted?

    ReplyDelete
  2. Aditya,

    Tell me what part of the JS you don't understand and i'll try to make it clearer. Did you build anything so far?

    ReplyDelete
  3. def category_children(objects)
    objects.map { |o| [o.label, o.id] } if (objects)
    end

    ReplyDelete