How do I use select2-rails with simple_form?

20,773

Solution 1

The select2 plugin supports auto-completion. You can use the native auto-completion as follows:

<%= f.input_field :ac_neighborhood_ids, 
      data: { 
        placeholder: "Where do you want to live?",
        saved: @search.neighborhoods.to_json,
        url: autocomplete_neighborhood_name_searches_path
      }, 
      input_html:  { class: "span8 ac-select2" }
%>

Javscript

$(document).ready(function() {  
  $('.ac-select2').each(function() {
    var url = $(this).data('url'); 
    var placeholder = $(this).data('placeholder'); 
    var saved = jQuery.parseJSON($(this).data('saved'));
    $(this).select2({
      minimumInputLength: 2,
      multiple: true,
      placeholder : placeholder,
      allowClear: true,
      ajax: {
        url: url,
        dataType: 'json',
        quietMillis: 500,
        data: function (term) {
          return {
            name: term
          };
        },
        results: function (data) {
          return {results: data};
        }
      },

      formatResult: function (item, page) {
        return item.name; 
      },

      formatSelection: function (item, page) {
        return item.name; 
      },

      initSelection : function (element, callback) {
        if (saved) {
          callback(saved);
        }
      }

    });
  });
});

Make sure the action at autocomplete_neighborhood_name_searches_path returns a json array of hashes. Each hash should contain id and name fields. The term for auto-completion is passed via the query parameter name.

  def autocomplete_neighborhood_name
    @neighborhood = Neighborhood.select("id, name").where("name LIKE ?", "#{params[:name]}%").order(:name).limit(10)

    respond_to do |format|
      format.json { render json: @neighborhood , :only => [:id, :name] }
    end    
  end

Your search model:

class Search

  attr_accessor :ac_neighborhood_ids

  has_many :search_neighborhoods
  has_many :neighborhoods, through: :search_neighborhoods

  def ac_neighborhood_ids
    neighborhood_ids.join(",")
  end

  def ac_neighborhoods
    neighborhoods.map{|n| {:id => n.id, :name => n.name}}
  end

  def ac_neighborhood_ids=(ids)
    search_neighborhoods.clear # remove the old values
    ids.split(',').select(&:present?).map do |neighborhood_id|
      search_neighborhoods.build neighborhood_id: neighborhood_id
    end
  end

end    

Solution 2

I believe you need to attach select either to select tag (then it reads the data from it) or to input hidden tag, then you need to provide 'query' function. In your case it is attached to an input tag, and thus looks for a query function. Try setting as: :select on your f.input_field call.

Share:
20,773
marcamillion
Author by

marcamillion

Rails developer that loves finance, economics, business &amp; tech.

Updated on November 20, 2020

Comments

  • marcamillion
    marcamillion over 3 years

    This select2 jquery library looks awesome. There is a Rails gem but it is very light on the documentation. I would like to generate a simple multiple drop-down menu, using autocomplete. How do I do that?

    This is my simple_form_for call:

    <%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',', placeholder: "Where do you want to live?"}, multiple: true, id: "selectWhereToLive", class: "span8" %>
    

    I have successfully installed the select2-rails gem, but not quite sure how to get it working.

    I add this to my home.js.coffeefile:

    jQuery ->
        $('#selectWhereToLive').select2()
    

    And am getting this error:

    Uncaught query function not defined for Select2 selectWhereToLive 
    

    Thoughts?

    Edit 1:

    The above simple_form_for call is producing this HTML:

    <input class="autocomplete optional span8" data-autocomplete="/searches/autocomplete_neighborhood_name" data-delimiter="," data-placeholder="Where do you want to live?" id="selectWhereToLive" multiple="multiple" name="search[neighborhood_names][]" size="30" type="text" url="/searches/autocomplete_neighborhood_name" value="" />
    

    Indicating that the id attribute is being properly set.

    Edit 2 - Updated

    As @moonfly suggested, I tried adding as: :select to the f.input_field - both with as: :autocomplete included and not included.

    The resulting HTML without as: :autocomplete was this:

    <input name="search[neighborhood_names][]" type="hidden" value="" /><select class="select optional span8" data-delimiter="," data-placeholder="Where do you want to live?" id="selectWhereToLive" multiple="multiple" name="search[neighborhood_names][]" url="/searches/autocomplete_neighborhood_name"><option value="true">Yes</option>
    <option value="false">No</option></select>
    

    It pre-populates 2 option values 'Yes' and 'No'. Not quite sure why, but that is what it does.

    Update

    So I had changed the jquery selector to look for input#ID, and forgot. So I set that back and now it is generating the select box - but it is giving me those 2 Yes & No options. Not quite sure why it is doing that. It's not returning the values in from my url attribute.

    Edit 3

    @harish-shetty's suggestion seems to be working. But now, after it has successfully found the records via autocomplete and using the select2 menu, it is bypassing the setter method I have on my search.rb model.

    Basically, what I want to happen is, once the user has finished filling out the form - and I have all the IDs/names for the neighborhoods they want, I want to create a new record in search_neighborhoods for those IDs.

    So these are the methods I have:

    Search.rb
    
      def neighborhood_names
        neighborhoods.map(&:name).join(',')
      end
    
      # we need to put [0] because it returns an array with a single element containing
      # the string of comma separated neighborhoods
      def neighborhood_names=(names)
        names[0].split(',').each do |name|
          next if name.blank?
          if neighborhood = Neighborhood.find_by_name(name)
            search_neighborhoods.build neighborhood_id: neighborhood.id
          end
        end
      end
    

    My SearchController.rb

      def autocomplete_neighborhood_name
        @neighborhood = Neighborhood.select("id, name").where("name LIKE ?", "#{params[:name]}%").order(:name).limit(10)
    
        respond_to do |format|
          format.json { render json: @neighborhood , :only => [:id, :name] }
        end    
      end
    

    This is what a request looks like right now - which shows that no search_neighborhood records are being created:

    Started POST "/searches" for 127.0.0.1 at 2013-03-06 04:09:55 -0500
    Processing by SearchesController#create as HTML
      Parameters: {"utf8"=>"✓", "authenticity_token"=>"7SeA=", "search"=>{"boro_id"=>"", "neighborhood_names"=>"1416,1394", "property_type_id"=>"", "min_price"=>"", "max_price"=>"", "num_bedrooms"=>"", "num_bathrooms"=>""}}
      Neighborhood Load (0.5ms)  SELECT "neighborhoods".* FROM "neighborhoods" WHERE "neighborhoods"."name" = '1' LIMIT 1
       (0.3ms)  BEGIN
      SQL (0.8ms)  INSERT INTO "searches" ("amenity_id", "boro_id", "created_at", "keywords", "listing_type_id", "max_price", "min_price", "neighborhood_id", "num_bathrooms", "num_bedrooms", "property_type_id", "square_footage", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING "id"  [["amenity_id", nil], ["boro_id", nil], ["created_at", Wed, 06 Mar 2013 09:09:55 UTC +00:00], ["keywords", nil], ["listing_type_id", nil], ["max_price", nil], ["min_price", nil], ["neighborhood_id", nil], ["num_bathrooms", nil], ["num_bedrooms", nil], ["property_type_id", nil], ["square_footage", nil], ["updated_at", Wed, 06 Mar 2013 09:09:55 UTC +00:00]]
       (32.2ms)  COMMIT
    Redirected to http://localhost:3000/searches/29
    
  • marcamillion
    marcamillion about 11 years
    Can I have two as: calls? One is currently as: :autocomplete.
  • marcamillion
    marcamillion about 11 years
    I updated the question with the HTML output based on your suggestion. In short, it doesn't work :(
  • moonfly
    moonfly about 11 years
    no, only one. if you need to keep it as: :autocomplete, you'll have to define the 'query' function.
  • marcamillion
    marcamillion about 11 years
    Yah....even just as: :select doesn't work...it produces those Yes/No option value fields, for w/e reason. That is strange because the data returned from the URL call shouldn't be yes/no. It should be a name.
  • moonfly
    moonfly about 11 years
    for select you need to provide collection with all the values. check github.com/plataformatec/simple_form#collections, for example. in your case, looks like you have no other way but to create a 'query' function which will retrieve the values from /searches/autocomplete_neighbourhood_names. so, forget I suggested to do as: :select, concentrate on writing 'query'
  • marcamillion
    marcamillion about 11 years
    Ok...can you give me an example of query. Also, I know that the collections method in simple_form requires that....but because I am using the rails3-jquery-autocomplete gem - github.com/crowdint/rails3-jquery-autocomplete it works without it. Meaning, that if I don't try and use the select2() jQuery plugin, and I type in the box...it will return the right info from the db - like I want. It's just that it looks ugly and I would much prefer the presentation be handled by select2. Once I try it this way though, the correct data is not returned.
  • moonfly
    moonfly about 11 years
    well, I'm not an expert in select2, unfortunately. but you can find examples of query in select2 documentation: ivaynberg.github.com/select2/index.html#data, or even more relevant: ivaynberg.github.com/select2/index.html#ajax.
  • marcamillion
    marcamillion about 11 years
    Yeh...and that's the issue....how do I make that work with simple_form. I know doing it manually is one way....but I am already using the rails3-jquery-autocomplete gem + simple_form.
  • marcamillion
    marcamillion about 11 years
    Hrmm....I was using the rails3-jquery-autocomplete gem - github.com/crowdint/rails3-jquery-autocomplete - so that generated that autocomplete_neighborhood_name_searches_path based on a declaration I put at the top of my controllers. Should I get rid of that declaration and create the actions manually? If so, which controller should I create the action on? My Search controller? That's where autocomplete is declared now.
  • Harish Shetty
    Harish Shetty about 11 years
    That's how I have done it in the past. You should add the action in the search controller and register it as a collection action in your route file. When every-thing works you should see the auto-completion work like this example: ivaynberg.github.com/select2/#ajax
  • marcamillion
    marcamillion about 11 years
    So here is the last missing piece of the puzzle....now I finally got it working - but when it executes the search, it is bypassing my setter method in my search.rb model. I will update the question with more code.
  • marcamillion
    marcamillion about 11 years
    I also added log output from a request, that shows that it is skipping my setter method.
  • Harish Shetty
    Harish Shetty about 11 years
    The select2 plugin returns the ids, so it is better to pass rename the field to neighborhood_ids. I have updated my answer. If you need to subsequently edit the saved form, you need to add additional code to get it to work.
  • marcamillion
    marcamillion about 11 years
    Nice....one last thing...why does this return an empty array as the first result? E.g. Parameters: {"utf8"=>"✓", "authenticity_token"=>"7SekQifiGWn77fjA=", "search"=>{"boro_id"=>"", "neighborhood_ids"=>"[],1416,1411,1676"}}
  • Harish Shetty
    Harish Shetty about 11 years
    Oh, it is because of the change I did. I have updated the Search model again. Take a look.
  • Harish Shetty
    Harish Shetty about 11 years
    Updated the answer to support the loading of saved data in the Edit mode. Hopefully it works for you. I am glad that somebody else is trying to use auto-completion with select2. It took me a while to get it working. May be I should create a gem to make it easy for rest of the crowd.
  • Harish Shetty
    Harish Shetty about 11 years
    Good thing is, this solution is generic. So you will be able to provide auto-completion support for any field as long as you pass the correct class, url and saved data.
  • marcamillion
    marcamillion about 11 years
    So now I am getting a nil error - undefined method 'neighborhoods' for nil:NilClass at this line: saved: @search.neighborhoods.to_json,. By the way...I added a , because you forgot that, for that line.
  • Harish Shetty
    Harish Shetty about 11 years
    I was assuming that you had a member variable called @search in your controller. Change that to whatever variable you are using with the form. (i.e. saved: @search.neighborhoods.to_json)
  • marcamillion
    marcamillion about 11 years
    Well...here is the thing...in my create action for my Search controller, I have an instance variable called @search, but in my autocomplete_neighborhood_name action, I only have a @neighborhood instance variable. Which action are you referring to?
  • marcamillion
    marcamillion about 11 years
    Oh...and to make matters more interesting, this form is on my views/home/index.html.erb - so technically it's not on either my neighborhood or search page.
  • marcamillion
    marcamillion about 11 years
    If you ignore that error, I commented it out for now (partially because I may not need that functionality right now), I am getting this error: undefined method 'name' for #<SearchNeighborhood:0x007fd93d3914f8> at this line: search_neighborhoods.map{|n| {:id => n.id, :name => n.name}}. So I changed that to be ...:id => n.neighborhood_id, :name => n.neighborhood.name But that didn't work. That returned a generic undefined method 'name' for nil:NilClass at the same line. Thoughts?
  • Harish Shetty
    Harish Shetty about 11 years
    It looks like you have a has_many :through relationship in your Search model. I have changed the code to use a new name for the ac field. This should probably take care of the issue.
  • marcamillion
    marcamillion about 11 years
    Yes....perfect. That works and I did have a HMT in the Search model. My bad...guess I should have specified that earlier. Thanks again dude. Really appreciate the persevering with me....many a SO Answerers wouldn't. Thanks much!
  • Harish Shetty
    Harish Shetty about 11 years
    It is good that you got it to work. I will let you know when I create the gem. BTW, I updated the ac_neighborhood_ids= method to clear the old old neighborhoods. Otherwise you will accumulate the neighborhoods.
  • marcamillion
    marcamillion about 11 years
    Thanks much...never even considered that.
  • marcamillion
    marcamillion about 11 years
    Is there any reason the placeholder doesn't work? I even replaced the placeholder attribute on the select2 call with placeholder: "Where do you want to live?" i.e. a string rather than a variable that holds the value of the data-placeholder attribute and that doesn't work. So it seems, for whatever reason, the placeholder attribute on the .select2 is not passing the correct value to the field. Thoughts?
  • Harish Shetty
    Harish Shetty about 11 years
    It works for me. According to select2 documentation you can set the the data-placeholder attribute in the markup. I am able to reproduce your issue at jsfiddle.net/ZNjgc. You should file a issue at the select2 github issue list.
  • marcamillion
    marcamillion about 11 years
    I will do that. Thanks for doing JSFiddle.
  • marcamillion
    marcamillion about 11 years
    Want to take a look at the response - github.com/ivaynberg/select2/issues/1003#issuecomment-146453‌​94 In this code in your answer, it checks for the saved variable and then it returns the saved object in the callback. But for some reason, it seems to be doing that even on the first search. I also see it in this Fiddle - jsfiddle.net/ZNjgc/3 - you will see on first load, in the search field is []. Is that the saved functionality?
  • kuboon
    kuboon over 8 years
    select2 doesn't support autocomplete. if you want to use both at once, it is javascript matter, not rails.