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