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])