Skip to content

Instantly share code, notes, and snippets.

@smoil
Last active March 2, 2020 00:32
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save smoil/5061616 to your computer and use it in GitHub Desktop.
Save smoil/5061616 to your computer and use it in GitHub Desktop.
Ajax select/multi select in Rails using Select2
class Account < ActiveRecord::Base
def self.search(name = nil)
# search logic
end
end
class AccountsController < ApplicationController
def search
# this returns a hash of search results
records = RecordSearcher.call(Account.by_name, params)
render json: records.to_json, callback: params[:callback]
end
end
# bind to selectors
jQuery ->
account_args =
url: "/accounts/search.json"
options:
placeholder: "Search for an account"
$(".account-select").ajaxSelect(account_args.url, account_args.options)
$(".account-multi-select").ajaxSelect(
account_args.url,
placeholder: account_args.options
multiple: true
)
/ form input for user.stringy_account_ids, note data is set to pre-render exisiting results
= f.label :stringy_account_ids, "Accounts"
= f.hidden_field :stringy_account_ids, class: "account-multi-select", data: { records: f.object.accounts.select(["accounts.id","accounts.name"]) }
# customize options to suit your needs and/or forgo creating a first class jquery function
jQuery.fn.ajaxSelect = (url, options) ->
defaults =
placeholder: "Search for a record"
formatter: (record) ->
record.name
multiple: false
allow_clear: true
settings = $.extend(defaults, options)
this.select2
initSelection: (elm, callback) ->
results = $(elm).data "records"
callback(results)
placeholder: settings.placeholder
allowClear: settings.allow_clear
minimumInputLength: 3
multiple: settings.multiple
ajax:
url: url
dataType: "jsonp"
quietMillis: 100
data: (term, page) ->
query: term
limit: 10
page: page
results: (data, page) ->
more = (page * 10) < data.total
results: data.records
more: more
formatResult: settings.formatter
formatSelection: settings.formatter
# a simple class to generate search results, you can provide a block if you have different data attributes
class RecordSearcher
attr_reader :records
def initialize(records, params = {})
unless records.respond_to? :search
raise ArgumentError, "records must repond to .search"
end
default_params = {
query: nil,
page: nil,
limit: nil
}
params.reverse_merge!(default_params)
@records = records.search(params[:query]).page(params[:page])
.per(params[:limit])
end
def call(&block)
{
total: records.total_count,
records: records.map do |record|
if block_given?
block.call(record)
else
{ name: record.name, id: record.id }
end
end
}
end
def self.call(records, params = {}, &block)
new(records, params).call(&block)
end
end
# invoking in a class will add methods stringy_whatever_ids, and stringy_whatever_ids=
module StringyAssociationIds
def stringy_ids(association)
define_method("stringy_#{association}_ids=") do |comma_seperated_ids|
self.send("#{association}_ids=", comma_seperated_ids.to_s.split(","))
end
define_method("stringy_#{association}_ids") do
send("#{association}_ids").join(",")
end
end
end
ActiveRecord::Base.extend(StringyAssociationIds)
class User < ActiveRecord::Base
has_many :accounts
stringy_ids :account
end
@Myhka
Copy link

Myhka commented Mar 15, 2013

This is great, thanks for posting. I haven't had any luck attaching this to a form field in a form without an object. I have a search form_tag which sends a get request without an object, so . I was using select2 without ajax successfully by calling plain .select2 on this input:
<%= select_tag(:location_id, options_from_collection_for_select(@Locations, :id, :name, @location_ids), :class => "location_multi", :multiple => true)%>
Any idea how one would apply your logic to a hidden_field_tag to get it to submit multiple values correctly?

In case anyone else running into similar issues comes across this page, I've pasted below how you would alter @smoil's approach if multiple: false.
form:

<%= f.label :location_id, "Location" %> <%= f.hidden_field :location_id, class: 'select2-ajax-location', data: {locationid: f.object.location && f.object.location_id, locationname: f.object.location && f.object.location.name} %>
javascript: $(".select2-ajax-location").select2({ initSelection: function (elm, callback){ callback({"id":elm.data("locationid"), "name":elm.data("locationname")}); }, placeholder: "Start typing..", minimumInputLength: 2, ajax: { url: "/locations.json", dataType: 'json', quietMillis: 200, data: function (term, page) { return { q: term, page_limit: 10, page: page, }; }, results: function (data, page) { var more = (page \* 10) < data.total; // whether or not there are more results available return {results: data.locations, more: more}; } }, formatResult: function(location) { var markup = ""; markup += "
" + location.name + "
"; markup += "
"; return markup; }, formatSelection: function(location) { return location.name; }, dropdownCssClass: "bigdrop" // apply css that makes the dropdown taller });

@aniketstiwari
Copy link

Can you make a tutorial on it because it is not self-explanatory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment