lib/engine2/action.rb in engine2-1.0.5 vs lib/engine2/action.rb in engine2-1.0.6
- old
+ new
@@ -52,13 +52,13 @@
end
def invoke! handler
if rmp = @request_action_proc
action = self.class.new(node, assets, self)
- action_result = action.instance_exec(handler, *action.request_action_proc_params(handler), &rmp)
+ result = action.instance_exec(handler, *action.request_action_proc_params(handler), &rmp)
action.post_process
- response = @requestable ? (action_result || {}) : action.invoke(handler)
+ response = @requestable ? (result || {}) : action.invoke(handler)
response[:meta] = action.meta
response
else
invoke(handler)
end
@@ -70,12 +70,12 @@
def arguments args
@meta[:arguments] = args
end
- def execute time
- @meta[:execute] = time
+ def execute command
+ (@meta[:execute] ||= []) << command
end
def dynamic?
self != @static
end
@@ -187,15 +187,16 @@
if Faye::WebSocket.websocket?(handler.env)
ws = Faye::WebSocket.new(handler.env)
@ws_methods.each do |method, blk|
ws.on(method) do |evt|
begin
- if method == :message
- blk.(JSON.parse(evt.data, symbolize_names: true), ws, evt)
- else
- blk.(evt, ws, evt)
- end
+ data = method == :message ? JSON.parse(evt.data, symbolize_names: true) : evt
+ action = self.class.new(node, assets, self)
+ result = action.instance_exec(data, ws, evt, &blk)
+ result = {} unless result.is_a?(Hash)
+ result[:meta] = action.meta
+ ws.send! result unless action.meta.empty?
rescue Exception => e
ws.send! error: {exception: e, method: method}
end
end
end
@@ -220,49 +221,49 @@
@meta.merge! environment: Handler::environment, application: Engine2::SETTINGS[:name], key_separator: Engine2::SETTINGS[:key_separator], ws_methods: ActionWebSocketSupport::WS_METHODS
end
end
module ActionAPISupport
- def info field
- (@meta[:info] ||= {})[field.to_sym] ||= {}
+ def fields field
+ (@meta[:fields] ||= {})[field.to_sym] ||= {}
end
def config
@meta[:config] ||= {}
end
- def info! *fields, options
+ def fields! *fields, options
raise E2Error.new("No fields given to info") if fields.empty?
fields.each do |field|
- info(field).merge! options # rmerge ?
+ fields(field).merge! options # rmerge ?
end
end
def loc! hash
(@meta[:loc] ||= {}).merge! hash
end
def decorate list
list.each do |f|
- info(f)[:loc] ||= LOCS[f.to_sym]
+ fields(f)[:loc] ||= LOCS[f.to_sym]
end
end
def render field, options
- info! field, render: options
+ fields! field, render: options
end
def hide_fields *flds
- info! *flds, hidden: true
+ fields! *flds, hidden: true
end
def show_fields *flds
- info! *flds, hidden: false
+ fields! *flds, hidden: false
end
def field_filter *flds, filter
- info! *flds, filter: filter
+ fields! *flds, filter: filter
end
end
module ActionMenuSupport
def menu menu_name, &blk
@@ -319,27 +320,10 @@
def show_pk
show_fields *assets[:model].primary_keys
end
- def get_type_info name
- model = assets[:model]
- info = case name
- when Symbol
- model.type_info[name]
- when Sequel::SQL::QualifiedIdentifier
- assoc = model.many_to_one_associations[name.table] || model.many_to_many_associations[name.table]
- raise E2Error.new("Association #{name.table} not found for model #{model}") unless assoc
- assoc.associated_class.type_info[name.column]
- else
- raise E2Error.new("Unknown type info key: #{name} in model #{model}")
- end
-
- raise E2Error.new("Type info not found for '#{name}' in model '#{model}'") unless info
- info
- end
-
# def parent_model_name
# model = @assets[:model]
# prnt = node.parent
# while prnt && prnt.*.assets[:model] == model
@@ -429,36 +413,36 @@
@query
end
end
def find_record handler, id
- get_query[assets[:model].primary_keys_hash_qualified(split_keys(id))]
+ get_query.load assets[:model].primary_keys_hash_qualified(split_keys(id))
end
def select *args, use_pk: true, &blk
ds = assets[:model].select(*args, &blk)
ds = ds.ensure_primary_key if use_pk
- ds.setup!(@meta[:fields] = [])
+ ds.setup_query(@meta[:field_list] = [])
end
end
module ActionTabSupport
def select_tabs tabs, *args, &blk
field_tabs tabs
select *tabs.map{|name, fields|fields}.flatten, *args, &blk
end
def field_tabs hash
- @meta[:tabs] = hash.map{|k, v| {name: k, loc: LOCS[k], fields: v} }
+ @meta[:tab_list] = hash.keys
+ @meta[:tabs] = hash.reduce({}){|h, (k, v)| h[k] = {name: k, loc: LOCS[k], field_list: v}; h}
end
- def lazy_tab tab_name
- tabs = @meta[:tabs]
- raise E2Error.new("No tabs defined") unless tabs
- tab = tabs.find{|t| t[:name] == tab_name}
- raise E2Error.new("No tab #{tab_name} defined") unless tab
- tab[:lazy] = true
+ def tab *tabs, options
+ raise E2Error.new("No tabs given to info") if tabs.empty?
+ tabs.each do |tab|
+ @meta[:tabs][tab].merge! options # rmerge ?
+ end
end
end
module ActionAngularSupport
def ng_execute expr
@@ -482,11 +466,11 @@
"action.record['#{name}']"
end
def ng_info! name, *selector, expression
# expression = "'#{expression}'" if expression.is_a? String
- "action.meta.info['#{name}'].#{selector.join('.')} = #{expression}"
+ "action.meta.fields['#{name}'].#{selector.join('.')} = #{expression}"
end
def ng_call name, *args
# TODO
end
@@ -540,10 +524,21 @@
def panel_footer ftr
panel[:footer] = ftr
end
end
+ module ActionDraggableSupport
+ def draggable
+ @meta[:draggable] ||= {}
+ end
+
+ def post_run
+ super
+ draggable[:position_field] ||= 'position' if @meta[:draggable]
+ end
+ end
+
class MenuAction < Action
include ActionMenuSupport
action_type :menu
def invoke handler
@@ -581,12 +576,12 @@
def on_change field, &blk
node_name = :"#{field}_on_change"
nd = node.define_node node_name, (blk.arity > 2 ? OnChangeGetAction : OnChangePostAction)
nd.*{request &blk}
- info! field, remote_onchange: node_name
- info! field, remote_onchange_record: :true if blk.arity > 2
+ fields! field, remote_onchange: node_name
+ fields! field, remote_onchange_record: :true if blk.arity > 2
end
class OnChangeAction < Action
include ActionAPISupport, ActionAngularSupport
@@ -624,11 +619,11 @@
end
end
end
module ActionListSupport
- include ActionModelSupport, ActionAPISupport, ActionTabSupport, ActionPanelSupport, ActionMenuSupport, ActionOnChangeSupport
+ include ActionModelSupport, ActionAPISupport, ActionTabSupport, ActionPanelSupport, ActionMenuSupport, ActionOnChangeSupport, ActionDraggableSupport
attr_reader :filters, :orders
def pre_run
super
config.merge!(per_page: 10, use_count: false, selectable: true) # search_active: false,
@@ -636,11 +631,11 @@
panel_template 'scaffold/list'
panel_title "#{:list.icon} #{LOCS[assets[:model].name.to_sym]}"
loc! LOCS[:list_locs]
menu :menu do
properties break: 2, group_class: "btn-group-xs"
- option :search_toggle, icon: "search", show: "action.meta.search_fields", active: "action.ui_state.search_active", button_loc: false
+ option :search_toggle, icon: "search", show: "action.meta.search_field_list", active: "action.ui_state.search_active", button_loc: false
# divider
option :refresh, icon: "refresh", button_loc: false
option :default_order, icon: "signal", button_loc: false
divider
option :debug_info, icon: "list-alt" do
@@ -667,11 +662,11 @@
end
end
def post_run
unless panel[:class]
- panel_class case @meta[:fields].size
+ panel_class case @meta[:field_list].size
when 1..3; ''
when 4..6; 'modal-large'
else; 'modal-huge'
end
end
@@ -685,41 +680,42 @@
# raise E2Error.new("No search renderer found for field '#{type_info[:name]}'") unless renderer
# renderer.(self, type_info)
# end
def post_process
- if fields = @meta[:search_fields]
- fields = fields - static.meta[:search_fields] if dynamic?
+ model = assets[:model]
+ if fields = @meta[:search_field_list]
+ fields = fields - static.meta[:search_field_list] if dynamic?
decorate(fields)
fields.each do |name|
- type_info = get_type_info(name)
+ type_info = model.find_type_info(name)
- # render = info[name][:render]
+ # render = fields[name][:render]
# if not render
- # info[name][:render] = find_renderer(type_info)
+ # fields[name][:render] = find_renderer(type_info)
# else
- # info[name][:render].merge!(find_renderer(type_info)){|key, v1, v2|v1}
+ # fields[name][:render].merge!(find_renderer(type_info)){|key, v1, v2|v1}
# end
- info(name)[:render] ||= begin # set before :fields
+ fields(name)[:render] ||= begin # set before :field_list
renderer = DefaultSearchRenderers[type_info[:type]] || DefaultSearchRenderers[type_info[:otype]]
raise E2Error.new("No search renderer found for field '#{type_info[:name]}'") unless renderer
renderer.(self, type_info)
end
proc = SearchRendererPostProcessors[type_info[:type]] || ListRendererPostProcessors[type_info[:type]] # ?
proc.(self, name, type_info) if proc
end
end
- if fields = @meta[:fields]
- fields = fields - static.meta[:fields] if dynamic?
+ if fields = @meta[:field_list]
+ fields = fields - static.meta[:field_list] if dynamic?
decorate(fields)
fields.each do |name|
- type_info = get_type_info(name)
+ type_info = model.find_type_info(name)
proc = ListRendererPostProcessors[type_info[:type]]
proc.(self, name, type_info) if proc
end
end
@@ -729,23 +725,24 @@
def search_template template
panel[:search_template] = template
end
def sortable *flds
- flds = @meta[:fields] if flds.empty?
- info! *flds, sort: true
+ flds = @meta[:field_list] if flds.empty?
+ fields! *flds, sort: true
end
def search_live *flds
- flds = @meta[:search_fields] if flds.empty?
- info! *flds, search_live: true
+ flds = @meta[:search_field_list] if flds.empty?
+ fields! *flds, search_live: true
end
def searchable *flds
+ @meta.delete(:tab_list)
@meta.delete(:tabs)
search_template 'scaffold/search'
- @meta[:search_fields] = *flds
+ @meta[:search_field_list] = *flds
end
def searchable_tabs tabs
searchable *tabs.map{|name, fields|fields}.flatten
field_tabs tabs
@@ -758,12 +755,12 @@
def filter name, &blk
(@filters ||= {})[name] = blk
end
def filter_case_insensitive name
- raise E2Error.new("Field '#{name}' needs to be a string one") unless get_type_info(name)[:otype] == :string
- filter(name){|query, hash, handler| query.where(name.ilike("%#{hash[name]}%")) }
+ raise E2Error.new("Field '#{name}' needs to be a string one") unless assets[:model].find_type_info(name)[:otype] == :string
+ filter(name){|handler, query, hash| query.where(name.ilike("%#{hash[name]}%")) }
end
def order name, &blk
(@orders ||= {})[name] = blk
end
@@ -792,11 +789,11 @@
end
def validate_and_approve handler, record, parent_id
static.before_approve(handler, record)
record.valid?
- validate_record(handler, record)
+ validate_record(handler, record, parent_id)
if record.errors.empty?
static.after_approve(handler, record)
true
else
false
@@ -826,14 +823,14 @@
def validate name, &blk
(@validations ||= {})[name] = blk
end
- def validate_record handler, record
+ def validate_record handler, record, parent_id
@validations.each do |name, val|
unless record.errors[name]
- result = val.(record, handler)
+ result = val.(handler, record, parent_id)
record.errors.add(name, result) if result
end
end if @validations
end
@@ -842,11 +839,11 @@
execute "action.errors || [action.parent().invoke(), action.panel_close()]"
end
def post_run
super
- validate_fields *node.parent.*.meta[:fields] unless validate_fields
+ validate_fields *node.parent.*.meta[:field_list] unless validate_fields
end
end
module ActionSaveSupport
include ActionApproveSupport
@@ -857,20 +854,20 @@
attr_accessor :validate_only
end
end
def validate_and_approve handler, record, parent_id, validate_only = self.class.validate_only
- if validate_only then
+ if validate_only
super(handler, record, parent_id)
else
record.skip_save_refresh = true
record.raise_on_save_failure = false
model = assets[:model]
assoc = assets[:assoc]
new_assoc = record.new? && assoc && assoc[:type]
- save = lambda do|c|
+ save = lambda do |c|
if super(handler, record, parent_id)
if new_assoc == :one_to_many
handler.permit parent_id
assoc[:keys].zip(split_keys(parent_id)).each{|k, v|record[k] = v}
end
@@ -963,20 +960,20 @@
def record handler, record
end
def post_process
- if fields = @meta[:fields]
- fields = fields - static.meta[:fields] if dynamic?
+ if fields = @meta[:field_list]
+ model = assets[:model]
+ fields = fields - static.meta[:field_list] if dynamic?
decorate(fields)
fields.each do |name|
- # type_info = model.type_info.fetch(name)
- type_info = get_type_info(name)
+ type_info = model.find_type_info(name)
- info(name)[:render] ||= begin
+ fields(name)[:render] ||= begin
renderer = DefaultFormRenderers[type_info[:type]] # .merge(default: true)
raise E2Error.new("No form renderer found for field '#{type_info[:name]}' of type '#{type_info[:type]}'") unless renderer
renderer.(self, type_info)
end
@@ -985,15 +982,15 @@
end
assoc = assets[:assoc]
if assoc && assoc[:type] == :one_to_many
# fields.select{|f| assoc[:keys].include? f}.each do |key|
- # # hide_fields(key) if self[:info, key, :hidden] == nil
- # info! key, disabled: true
+ # # hide_fields(key) if self[:fields, key, :hidden] == nil
+ # fields! key, disabled: true
# end
assoc[:keys].each do |key|
- info! key, disabled: true if fields.include? key
+ fields! key, disabled: true if fields.include? key
end
end
end
super
@@ -1007,11 +1004,11 @@
def template
Templates
end
def hr_after field, message = '-'
- info! field, hr: message
+ fields! field, hr: message
end
end
module ActionCreateSupport
include ActionFormSupport
@@ -1020,12 +1017,12 @@
action.action_type :create
end
def pre_run
super
- panel_title LOCS[:create_title]
- node.parent.*.menu(:menu).option_at 0, node.name, icon: "plus-sign", button_loc: false
+ panel_title "#{LOCS[:create_title]} - #{LOCS[assets[:model].table_name]}"
+ node.parent.*.menu(:menu).option_at 0, node.name, icon: "plus-sign", button_loc: false if node.parent.*.is_a?(ListAction)
hide_pk unless assets[:model].natural_key
end
def record handler, record
@@ -1056,11 +1053,11 @@
action.action_type :modify
end
def pre_run
super
- panel_title LOCS[:modify_title]
+ panel_title "#{LOCS[:modify_title]} - #{LOCS[assets[:model].table_name]}"
node.parent.*.menu(:item_menu).option node.name, icon: "pencil", button_loc: false
end
def record handler, record
modify_record(handler, record)
@@ -1082,11 +1079,11 @@
end
def post_run
super
assets[:model].primary_keys.each do |key| # pre_run ?
- info! key, disabled: true
+ fields! key, disabled: true
end
end
end
module ActionViewSupport
@@ -1097,11 +1094,11 @@
end
def pre_run
super
panel_template 'scaffold/view'
- panel_title LOCS[:view_title]
+ panel_title "#{LOCS[:view_title]} - #{LOCS[assets[:model].table_name]}"
panel[:backdrop] = true
menu(:panel_menu).option :cancel, icon: "remove"
node.parent.*.menu(:item_menu).option node.name, icon: "file", button_loc: false
end
@@ -1124,16 +1121,17 @@
handler.halt_not_found LOCS[:no_entry]
end
end
def post_process
- if fields = @meta[:fields]
- fields = fields - static.meta[:fields] if dynamic?
+ if fields = @meta[:field_list]
+ model = assets[:model]
+ fields = fields - static.meta[:field_list] if dynamic?
decorate(fields)
fields.each do |name|
- type_info = get_type_info(name)
+ type_info = model.find_type_info(name)
proc = ListRendererPostProcessors[type_info[:type]]
proc.(self, name, type_info) if proc
end
end
@@ -1172,91 +1170,96 @@
end
end
(FormRendererPostProcessors ||= {}).merge!(
boolean: lambda{|action, field, info|
- action.info(field)[:render].merge! true_value: info[:true_value], false_value: info[:false_value]
- action.info(field)[:dont_strip] = info[:dont_strip] if info[:dont_strip]
+ action.fields(field)[:render].merge! true_value: info[:true_value], false_value: info[:false_value]
+ action.fields(field)[:dont_strip] = info[:dont_strip] if info[:dont_strip]
},
date: lambda{|action, field, info|
- action.info(field)[:render].merge! format: info[:format], model_format: info[:model_format]
+ action.fields(field)[:render].merge! format: info[:format], model_format: info[:model_format]
if date_to = info[:other_date]
- action.info(field)[:render].merge! other_date: date_to #, format: info[:format], model_format: info[:model_format]
+ action.fields(field)[:render].merge! other_date: date_to #, format: info[:format], model_format: info[:model_format]
action.hide_fields date_to
elsif time = info[:other_time]
- action.info(field)[:render].merge! other_time: time
+ action.fields(field)[:render].merge! other_time: time
action.hide_fields time
end
},
time: lambda{|action, field, info|
- action.info(field)[:render].merge! format: info[:format], model_format: info[:model_format]
+ render = action.fields(field)[:render]
+ render[:type] ||= info[:otype] == :string ? :string : :number
+ render.merge! format: info[:format], model_format: info[:model_format]
},
decimal_date: lambda{|action, field, info|
FormRendererPostProcessors[:date].(action, field, info)
- action.info! field, type: :decimal_date
+ action.fields! field, type: :decimal_date
},
decimal_time: lambda{|action, field, info|
FormRendererPostProcessors[:time].(action, field, info)
- action.info! field, type: :decimal_time
+ action.fields! field, type: :decimal_time
},
datetime: lambda{|action, field, info|
- action.info(field)[:render].merge! date_format: info[:date_format], time_format: info[:time_format], date_model_format: info[:date_model_format], time_model_format: info[:time_model_format]
+ action.fields(field)[:render].merge! date_format: info[:date_format], time_format: info[:time_format], date_model_format: info[:date_model_format], time_model_format: info[:time_model_format]
},
currency: lambda{|action, field, info|
- action.info(field)[:render].merge! symbol: info[:symbol]
+ action.fields(field)[:render].merge! symbol: info[:symbol]
},
# date_range: lambda{|action, field, info|
- # action.info[field][:render].merge! other_date: info[:other_date], format: info[:format], model_format: info[:model_format]
+ # action.fields[field][:render].merge! other_date: info[:other_date], format: info[:format], model_format: info[:model_format]
# action.hide_fields info[:other_date]
- # action.info[field][:decimal_date] = true if info[:validations][:decimal_date]
+ # action.fields[field][:decimal_date] = true if info[:validations][:decimal_date]
# },
list_select: lambda{|action, field, info|
- action.info(field)[:render].merge! list: info[:list]
+ render = action.fields(field)[:render]
+ render.merge! values: info[:values]
+ render.merge! max_length: info[:max_length], max_length_html: info[:max_length_html], separator: info[:separator] if info[:multiselect]
},
many_to_one: lambda{|action, field, info|
- field_info = action.info(field)
+ field_info = action.fields(field)
field_info[:assoc] = :"#{info[:assoc_name]}!"
field_info[:fields] = info[:keys]
field_info[:type] = info[:otype]
(info[:keys] - [field]).each do |of|
- f_info = action.info(of)
+ f_info = action.fields(of)
f_info[:hidden] = true
f_info[:type] = action.assets[:model].type_info[of].fetch(:otype)
end
},
file_store: lambda{|action, field, info|
- action.info(field)[:render].merge! multiple: info[:multiple]
+ action.fields(field)[:render].merge! multiple: info[:multiple]
},
star_to_many_field: lambda{|action, field, info|
- field_info = action.info(field)
+ field_info = action.fields(field)
field_info[:assoc] = :"#{info[:assoc_name]}!"
}
)
(ListRendererPostProcessors ||= {}).merge!(
boolean: lambda{|action, field, info|
- action.info! field, type: :boolean # move to action ?
- action.info(field)[:render] ||= {}
- action.info(field)[:render].merge! true_value: info[:true_value], false_value: info[:false_value]
+ action.fields! field, type: :boolean # move to action ?
+ action.fields(field)[:render] ||= {}
+ action.fields(field)[:render].merge! true_value: info[:true_value], false_value: info[:false_value]
},
list_select: lambda{|action, field, info|
- action.info! field, type: :list_select
- action.info(field)[:render] ||= {}
- action.info(field)[:render].merge! list: info[:list]
+ action.fields! field, type: :list_select
+ render = (action.fields(field)[:render] ||= {})
+ render.merge! values: info[:values]
+ render.merge! multiselect: true if info[:multiselect]
},
datetime: lambda{|action, field, info|
- action.info! field, type: :datetime
+ action.fields! field, type: :datetime
},
decimal_date: lambda{|action, field, info|
- action.info! field, type: :decimal_date
+ action.fields! field, type: :decimal_date
},
decimal_time: lambda{|action, field, info|
- action.info! field, type: :decimal_time
+ action.fields! field, type: :decimal_time
},
# date_range: lambda{|action, field, info|
- # action.info[field][:type] = :decimal_date if info[:validations][:decimal_date] # ? :decimal_date : :date
+ # action.fields[field][:type] = :decimal_date if info[:validations][:decimal_date] # ? :decimal_date : :date
# }
)
(SearchRendererPostProcessors ||= {}).merge!(
many_to_one: lambda{|action, field, info|
@@ -1267,25 +1270,25 @@
action.check_static_action
model = model.many_to_one_associations[field.table].associated_class
keys = info[:keys].map{|k| model.table_name.q(k)}
end
- field_info = action.info(field)
+ field_info = action.fields(field)
field_info[:assoc] = :"#{info[:assoc_name]}!"
field_info[:fields] = keys
field_info[:type] = info[:otype]
(keys - [field]).each do |of|
- f_info = action.info(of)
+ f_info = action.fields(of)
raise E2Error.new("Missing searchable field: '#{of}' in model '#{action.assets[:model]}'") unless f_info
f_info[:hidden_search] = true
f_info[:type] = model.type_info[of].fetch(:otype)
end
},
date: lambda{|action, field, info|
- action.info(field)[:render] ||= {}
- action.info(field)[:render].merge! format: info[:format], model_format: info[:model_format] # Model::DEFAULT_DATE_FORMAT
+ action.fields(field)[:render] ||= {}
+ action.fields(field)[:render].merge! format: info[:format], model_format: info[:model_format] # Model::DEFAULT_DATE_FORMAT
},
decimal_date: lambda{|action, field, info|
SearchRendererPostProcessors[:date].(action, field, info)
}
)
@@ -1311,18 +1314,19 @@
password: lambda{|action, info| Templates.password(info[:length])},
# date_range: lambda{|action, info| Templates.date_range},
boolean: lambda{|action, info| Templates.checkbox_buttons(optional: !info[:required])},
currency: lambda{|action, info| Templates.currency},
list_select: lambda{|action, info|
- length = info[:list].length
- if length <= 3
+ length = info[:values].length
+ max_length = info[:values].map(&:last).max_by(&:length).length
+ if info[:multiselect]
+ Templates.list_bsmselect(max_length)
+ elsif length <= 3
Templates.list_buttons(optional: !info[:required])
elsif length <= 15
- max_length = info[:list].max_by{|a|a.last.length}.last.length
Templates.list_bsselect(max_length, optional: !info[:required])
else
- max_length = info[:list].max_by{|a|a.last.length}.last.length
Templates.list_select(max_length, optional: !info[:required])
end
},
star_to_many_field: lambda{|action, info| Templates.scaffold},
many_to_one: lambda{|action, info|
@@ -1342,10 +1346,10 @@
decimal_date: lambda{|action, info| SearchTemplates.date_range},
integer: lambda{|action, info| SearchTemplates.integer_range},
string: lambda{|action, info| SearchTemplates.input_text},
boolean: lambda{|action, info| SearchTemplates.checkbox_buttons},
list_select: lambda{|action, info|
- length = info[:list].length
+ length = info[:values].length
if length <= 3
SearchTemplates.list_buttons
elsif length <= 15
# max_length = info[:list].max_by{|a|a.last.length}.last.length
SearchTemplates.list_bsselect(multiple: info[:multiple])