Skip to content

Instantly share code, notes, and snippets.

@tomasc
Last active October 7, 2018 12:15
Show Gist options
  • Save tomasc/6cefaa701702f0126b70b2b04cbd7926 to your computer and use it in GitHub Desktop.
Save tomasc/6cefaa701702f0126b70b2b04cbd7926 to your computer and use it in GitHub Desktop.
module Modulor
module ResourceSelect
class BaseController < ApplicationController
def index
authorize! :read, resource_class
respond_to do |format|
format.json do
render json: collection_query,
serializer: ActiveModel::Serializer::CollectionSerializer,
each_serializer: serializer_class
end
end
end
private
def collection_query
raise NotImplementedError, 'controller has to defined collection_query'
end
def resource_class
raise NotImplementedError, 'controller has to defined resource_class'
end
def serializer_class
raise NotImplementedError, 'controller has to defined serializer_class'
end
def filter_params
permitted_attributes = %i()
params.permit(*permitted_attributes)
end
def query
params.permit(:query)[:query]
end
def max_options
params.permit(:max_options)[:max_options]
end
end
end
end
module Modulor
module ResourceSelect
class BaseSerializer < ApplicationSerializer
attribute :class_name do
object.model_name.to_s
end
attribute :class_name_human do
object.model_name.human
end
end
end
endp
module Modulor
module ResourceSelect
class CrudResourcesController < BaseController
private
def collection_query
Modulor::ResourceSelect::CrudResourcesQuery.call(
resource_class.accessible_by(current_ability),
{ search_results: search_results }.merge(filter_params.to_h)
)
end
def search_results
@search_results ||= Modulor::ResourceSelect::CrudResourcesSearch.call(
resource_class,
query,
{
ability: current_ability,
search_size: max_options
}.merge(filter_params.to_h)
)
end
def resource_class
resource_class_name.constantize
end
def resource_class_name
params[:resource_class_name]
end
def serializer_class
Modulor::ResourceSelect::CrudResourcesSerializer
end
end
end
end
module Modulor
module ResourceSelect
class CrudResourcesSerializer < BaseSerializer
attribute :to_s do
object.to_s
end
end
end
end
export default class ResourceSelectInput extends Modulor.Plugin
@defaults =
debug: false
@selector: "input" + "bems(modulor_resource_select_input)"
on_init: ->
selectize = await `import('selectize/dist/js/standalone/selectize.js' /* webpackChunkName: "selectize" */)`
@Handlebars = await `import('handlebars/dist/handlebars.min.js' /* webpackChunkName: "handlebars" */)`
console.error('missing options data') unless !!@get_options()
console.error('missing value field') unless !!@get_value_field()
console.error('missing rendering template') unless !!@get_rendering_template()
@$element.selectize
create: false
dropdownParent: 'body' # so that the dropdown can go out of modal dialog
delimiter: ' '
options: @get_options()
maxItems: @get_max_items()
maxOptions: @get_max_options()
plugins: @get_selectize_plugins()
valueField: @get_value_field()
render:
item: (item, escape) => @get_rendering_template()(item)
option: (item, escape) => @get_rendering_template()(item)
score: (query) -> (item) -> 1
load: (query, callback) =>
return callback() unless !!@get_path()
return callback() unless !!query.length
$.ajax
dataType: 'json'
data:
query: encodeURIComponent(query)
max_options: @get_max_options()
resource_class_name: @get_resource_class_name()
resource_class_names: @get_resource_class_names()
url: @get_path()
type: 'GET'
error: -> callback()
success: (res) =>
instance = @get_instance()
# clear options
instance.options = instance.sifter.items = {}
callback(res['data'])
on_destroy: ->
if !!@get_instance()
@$element.selectize('destroy')
# ---------------------------------------------------------------------
set_resource_class_name: (resource_class_name) ->
return if resource_class_name == @get_resource_class_name()
if !!@get_instance()
@get_instance().clearCache()
@get_instance().clearOptions()
@$element.data 'resource-class-name', resource_class_name
# ---------------------------------------------------------------------
get_instance: -> @element.selectize
get_options: -> @$element.data('options').data
get_max_items: -> @$element.data('max-items')
get_max_options: -> @$element.data('max-options')
get_path: -> @$element.data('path')
get_rendering_template: -> @Handlebars.compile(@$element.data('rendering-template'), noEscape: true)
get_resource_class_name: -> @$element.data('resource-class-name')
get_resource_class_names: -> @$element.data('resource-class-names')
get_selectize_plugins: -> @$element.data('selectize-plugins')
get_value_field: -> @$element.data('value-field')
ResourceSelectInput.register()
# = form.input :page, as: :resource_select
#
# Options:
# input_html: {
# max_items: 1, # max number of items that can be selected
# max_options: 5, # max number options to be displayed in the dropdown
# options: [], # JSON data of the available options
# path: modulor.modulor_resource_select_pages_path, # path to JSON endpoint providing available options based on `query` param
# relation_name: …
# relation_is_many: … # inferred from the relation by default
# rendering_template: '<div>{{attributes.title}}</div>' # Handlebars template displayed in dropdown
# resource_class_name: %w(Modulor::Page) # base class name
# resource_class_names: %w(ExhibitionPage CoursePage) # filter class names
# selectize_plugins: %w(restore_on_backspace remove_button)
# serializer_class_name: …
# value_field: 'id', # value of which get sent to the server
# }
class ResourceSelectInput < SimpleForm::Inputs::Base
concerning :Tags do
def input(_wrapper_options)
@builder.text_field(
relation_attribute_name,
merged_input_html_options.except(
:options, :path, :max_options, :rendering_template, :plugins, :value_field, :resource_class_name
)
).html_safe
end
private
def merged_input_html_options
input_html_options.tap do |options|
merged_classes = options.fetch(:class, []).concat(dom_classes)
merged_data = options.fetch(:data, {}).deep_merge(dom_data)
options[:class] = merged_classes
options[:data] = merged_data
end
end
end
concerning :Dom do
private
def dom_classes
[
Bem.bem(:modulor_selectize),
Bem.bem(:modulor_resource_select_input)
]
end
def dom_data
{
max_items: max_items,
max_options: max_options,
options: selectize_options,
path: path,
rendering_template: rendering_template,
resource_class_name: resource_class_name,
resource_class_names: resource_class_names,
selectize_plugins: selectize_plugins,
value_field: value_field
}.delete_if { |_, v| v.blank? }
end
end
concerning :SelectizeOptions do
private
def selectize_options
input_html_options.fetch(:options, default_selectize_options)
end
def default_selectize_options
ActiveModelSerializers::SerializableResource.new(
Array(relation),
each_serializer: serializer_class,
serializer: ActiveModel::Serializer::CollectionSerializer
).as_json
end
end
concerning :Path do
private
def path
input_html_options.fetch(:path, default_path)
end
def default_path
params = {}
params = params.merge(locale: ::I18n.locale) if multiple_locales?
if resource_class <= Modulor::Page
Modulor::Engine.routes.url_helpers.modulor_resource_select_pages_path(params)
elsif CrudResourceListModule.resource_classes.include?(resource_class)
Modulor::Engine.routes.url_helpers.modulor_resource_select_crud_resources_path(params)
end
end
def multiple_locales?
::I18n.available_locales.count > 1
end
end
concerning :Max do
private
def max_items
input_html_options.fetch(:max_items, default_max_items)
end
def default_max_items
1 unless relation_is_many?
end
def max_options
input_html_options.fetch(:max_options, default_max_options)
end
def default_max_options
5
end
end
concerning :RenderingTemplate do
private
def rendering_template
input_html_options.fetch(:rendering_template, default_rendering_template)
end
def default_rendering_template
if resource_class <= Modulor::Page
%(<div class='#{Bem.bem(:modulor_selectize, Modulor::Page)}'>
<span class='#{Bem.bem(Modulor::SubjectType)}' data-subject-type='{{attributes.class-name}}'>{{attributes.class-name-human}}</span>
<div class='#{Bem.bem(:modulor_selectize, Modulor::Page, :title)}'>{{attributes.title}}</div>
<div class='#{Bem.bem(:modulor_selectize, Modulor::Page, :path)}'>/{{attributes.path}}</div>
</div>)
elsif resource_class <= Modulor::Attachment::Glyph
%(<div class='#{Bem.bem(:modulor_selectize, :modulor_attachment_glyph)}'>
<span class='#{Bem.bem(Modulor::SubjectType)}' data-subject-type='{{attributes.class-name}}'>{{attributes.class-name-human}}</span>
<div class='#{Bem.bem(:modulor_selectize, :modulor_attachment_glyph, :to_s)}'>{{attributes.to-s}}</div>
<div class='#{Bem.bem(:modulor_selectize, :modulor_attachment_glyph, :inline_svg)}'>{{attributes.inline-svg}}</div>
</div>)
elsif CrudResourceListModule.resource_classes.include?(resource_class)
%(<div class='#{Bem.bem(:modulor_selectize, :modulor_crud_resource)}'>
<span class='#{Bem.bem(Modulor::SubjectType)}' data-subject-type='{{attributes.class-name}}'>{{attributes.class-name-human}}</span>
<div class='#{Bem.bem(:modulor_selectize, :modulor_crud_resource, :to_s)}'>{{attributes.to-s}}</div>
</div>)
end
end
end
concerning :Plugins do
private
def selectize_plugins
input_html_options.fetch(:plugins, default_selectize_plugins)
end
def default_selectize_plugins
[]
end
end
concerning :ValueField do
private
def value_field
input_html_options.fetch(:value_field, default_value_field)
end
def default_value_field
'id'
end
end
concerning :Resource do
private
def resource_class_name
input_html_options.fetch(:resource_class_name, default_resource_class_name)
end
def resource_class
resource_class_name.constantize
end
def default_resource_class_name
@builder.object.relations[attribute_name.to_s].try(:class_name)
end
def resource_class_names
input_html_options.fetch(:resource_class_names, nil)
end
end
concerning :Serializer do
private
def serializer_class_name
input_html_options.fetch(:serializer_class_name, default_serializer_class_name)
end
def default_serializer_class_name
if resource_class <= Modulor::Page
Modulor::ResourceSelect::PagesSerializer.to_s
elsif resource_class <= Modulor::Attachment::Glyph
Modulor::ResourceSelect::GlyphSerializer.to_s
elsif CrudResourceListModule.resource_classes.include?(resource_class)
Modulor::ResourceSelect::CrudResourcesSerializer.to_s
end
end
def serializer_class
serializer_class_name.constantize
end
end
concerning :Relation do
private
def relation_attribute_name
input_html_options.fetch(:relation_attribute_name, default_relation_attribute_name)
end
def default_relation_attribute_name
@builder.object.relations[attribute_name.to_s].try(:key) ||
attribute_name
end
def relation_name
input_html_options.fetch(:relation_name, default_relation_name)
end
def default_relation_name
attribute_name
end
def relation
@builder.object.send(relation_name)
end
def relation_is_many?
input_html_options.fetch(:relation_is_many, default_relation_is_many?)
end
def default_relation_is_many?
[Mongoid::Association::Embedded::EmbedsMany,
Mongoid::Association::Referenced::HasMany,
Mongoid::Association::Referenced::HasAndBelongsToMany].any? { |cls| relation.is_a?(cls) } ||
[Array].any? { |cls| @builder.object.fields[relation_attribute_name.to_s].options[:type] == cls if @builder.object.fields[relation_attribute_name.to_s] } ||
[Array].any? { |cls| @builder.object.send(relation_attribute_name).class if @builder.object.respond_to?(relation_attribute_name) }
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment