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
JJD
  • 50,076
  • 60
  • 203
  • 339
marcamillion
  • 32,933
  • 55
  • 189
  • 380
  • After all, Edit3 is completely another problem. I suggest you to post another question. I guess you need permit neighborhood_names on controller. – kuboon Nov 20 '15 at 06:20

3 Answers3

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    
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
  • Hrmm....I was using the `rails3-jquery-autocomplete` gem - https://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. – marcamillion Mar 06 '13 at 06:37
  • 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: http://ivaynberg.github.com/select2/#ajax – Harish Shetty Mar 06 '13 at 07:19
  • 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 Mar 06 '13 at 09:17
  • I also added log output from a request, that shows that it is skipping my setter method. – marcamillion Mar 06 '13 at 09:31
  • 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. – Harish Shetty Mar 06 '13 at 17:51
  • 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"}}` – marcamillion Mar 06 '13 at 18:39
  • Oh, it is because of the change I did. I have updated the Search model again. Take a look. – Harish Shetty Mar 06 '13 at 18:42
  • 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 Mar 06 '13 at 18:53
  • 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. – Harish Shetty Mar 06 '13 at 18:57
  • 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. – marcamillion Mar 06 '13 at 18:59
  • 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`) – Harish Shetty Mar 06 '13 at 19:02
  • 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 Mar 06 '13 at 19:07
  • 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 Mar 06 '13 at 19:08
  • 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 #` 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? – marcamillion Mar 06 '13 at 19:18
  • 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. – Harish Shetty Mar 06 '13 at 19:30
  • 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! – marcamillion Mar 06 '13 at 20:28
  • 1
    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. – Harish Shetty Mar 07 '13 at 00:22
  • Thanks much...never even considered that. – marcamillion Mar 07 '13 at 17:20
  • 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? – marcamillion Mar 07 '13 at 20:35
  • 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 http://jsfiddle.net/ZNjgc/. You should file a issue at the select2 github issue list. – Harish Shetty Mar 07 '13 at 20:57
  • I will do that. Thanks for doing JSFiddle. – marcamillion Mar 08 '13 at 20:45
  • Want to take a look at the response - https://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 - http://jsfiddle.net/ZNjgc/3/ - you will see on first load, in the search field is `[]`. Is that the `saved` functionality? – marcamillion Mar 08 '13 at 21:11
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.

moonfly
  • 1,810
  • 11
  • 14
  • Can I have two `as:` calls? One is currently `as: :autocomplete`. – marcamillion Feb 28 '13 at 23:05
  • I updated the question with the HTML output based on your suggestion. In short, it doesn't work :( – marcamillion Feb 28 '13 at 23:10
  • no, only one. if you need to keep it as: :autocomplete, you'll have to define the 'query' function. – moonfly Feb 28 '13 at 23:11
  • 1
    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. – marcamillion Feb 28 '13 at 23:12
  • for select you need to provide collection with all the values. check https://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' – moonfly Feb 28 '13 at 23:19
  • 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 - https://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. – marcamillion Feb 28 '13 at 23:22
  • well, I'm not an expert in select2, unfortunately. but you can find examples of `query` in select2 documentation: http://ivaynberg.github.com/select2/index.html#data, or even more relevant: http://ivaynberg.github.com/select2/index.html#ajax. – moonfly Feb 28 '13 at 23:42
  • 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 Mar 01 '13 at 00:01
  • select2 doesn't support autocomplete. if you want to use both at once, it is javascript matter, not rails. – kuboon Nov 20 '15 at 06:22
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

kuboon
  • 9,557
  • 3
  • 42
  • 32