Skip to content

Instantly share code, notes, and snippets.

@fabn
Created October 13, 2015 09:24
Show Gist options
  • Save fabn/f084ac6aa572d4055588 to your computer and use it in GitHub Desktop.
Save fabn/f084ac6aa572d4055588 to your computer and use it in GitHub Desktop.
ActiveAdmin completion inputs for huge associations
# Vendored javascript files
#= require select2
# Configure defaults for all select2 inputs
$.fn.select2.defaults.set 'theme', 'classic'
$.fn.select2.defaults.set 'placeholder', 'Type to search'
# document ready function
jQuery ->
$('.ajax-select').select2
ajax:
dataType: 'json'
delay: 250
data: (params)-> q: params.term, locale: currentLocale()
processResults: (data)-> results: _.map data, (t)-> t.disabled = (t.data? && t.data.disabled); t
cache: true
minimumInputLength: 2
# Custom formatters omitted since they are pretty specific.
templateResult: formatResult
templateSelection: formatResult
# Used to show an ajax select/multiselect using select2 javascript plugin
class AjaxSelectInput < Formtastic::Inputs::SelectInput
def input_html_options
html_options = super # Get options passed via input_html
html_options[:placeholder] ||= 'Type to search'
# Css classes for input element
html_options[:class] = ['ajax-select']
# Add data with object type we need to complete, make them overridable using html_options
html_options[:data] ||= {}
html_options[:data][:'allow-clear'] ||= !required?
# Allow shortcut when using with formtastic, i.e. f.input :foo, as: :ajax_select, completion_path: '/foo'
html_options[:data][:'ajax-url'] ||= options[:completion_path] || template.completion_path(type: data_type)
# Return built options
html_options
end
# Always override collection option, this method is important to select2 since its result will be used
# to initialize the selection. When resource has no value the collection should be an empty array, in this
# way no options are given to select2. When value is present it should be returned to populate the initial
# selection of select2 input, this is useful when form is in edit mode
def collection_from_options
# Allow to be overridden in some cases
return super if options.has_key?(:collection)
# Retrieve the association collection or single element as array if belongs to
multiple? ? object.send(method) : Array(object.send(method))
end
# Return data type for the current method, i.e. author => person, categories => category
def data_type
reflection_for(method).klass.model_name.to_s
end
# If the form object has any value for the field, works with single and multiple fields
def has_value?
object.send(method).present?
end
end
class CompletionsController < ApplicationController
respond_to :json
before_action :authenticate_admin_user!
before_action :check_requested_type!
# Mapped in routes with get '/complete', to: 'completions#index', as: :completion
def index
respond_with @model.completions(params[:q], locale: params[:locale].presence)
end
private
def check_requested_type!
@model = params[:type].camelize.constantize rescue nil
head :not_found unless @model.try(:respond_to?, :completions)
end
end
class FilterAjaxInput < ActiveAdmin::Inputs::FilterSelectInput
# Add select2 css classes
def input_html_options
ajax_options = {
class: 'ajax-select',
data: {
:'ajax-url' => template.completion_path(type: data_type),
:'allow-clear' => multiple?
}
}
super.merge(ajax_options)
end
# Return input name used by metasearch
def input_name
multiple? ? "#{method}_id_in" : super()
end
# Do not return any item when building select, values are loaded by ajax
def collection
value = template.params[:q].try(:[], input_name)
return [] unless value.present?
klass = reflection_for(method).klass
klass.where(id: value).map { |o| [send_or_call(label_method, o), send_or_call(value_method, o)] }
end
def data_type
options[:complete_as] || reflection_for(method).klass.model_name.to_s
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment