How do I use select2-rails with simple_form?
Asked Answered
W

3

15

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
Woollen answered 28/2, 2013 at 21:47 Comment(1)
After all, Edit3 is completely another problem. I suggest you to post another question. I guess you need permit neighborhood_names on controller.Miki
T
24

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    
Timmytimocracy answered 6/3, 2013 at 5:58 Comment(22)
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.Woollen
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/#ajaxTimmytimocracy
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.Woollen
I also added log output from a request, that shows that it is skipping my setter method.Woollen
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.Timmytimocracy
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"}}Woollen
Oh, it is because of the change I did. I have updated the Search model again. Take a look.Timmytimocracy
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.Timmytimocracy
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.Timmytimocracy
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.Woollen
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)Timmytimocracy
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?Woollen
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.Woollen
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?Woollen
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.Timmytimocracy
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!Woollen
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.Timmytimocracy
Thanks much...never even considered that.Woollen
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?Woollen
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.Timmytimocracy
I will do that. Thanks for doing JSFiddle.Woollen
Want to take a look at the response - github.com/ivaynberg/select2/issues/1003#issuecomment-14645394 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?Woollen
P
1

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.

Pouter answered 28/2, 2013 at 23:1 Comment(9)
Can I have two as: calls? One is currently as: :autocomplete.Woollen
I updated the question with the HTML output based on your suggestion. In short, it doesn't work :(Woollen
no, only one. if you need to keep it as: :autocomplete, you'll have to define the 'query' function.Pouter
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.Woollen
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'Pouter
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.Woollen
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.Pouter
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.Woollen
select2 doesn't support autocomplete. if you want to use both at once, it is javascript matter, not rails.Miki
M
0

Use as: :select with collection is regular way to for select2. Binding autocomplete on select2 is a javascript matter.

This is my code sample. but does not have autocompletion. https://gist.github.com/kuboon/f692d9a844c0ff5877c8

Miki answered 20/11, 2015 at 6:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.