engine2 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -0
  3. data/Rakefile +1 -1
  4. data/app/{engine2actions.coffee → actions.coffee} +192 -142
  5. data/app/app.coffee +0 -0
  6. data/app/app.css +4 -0
  7. data/app/engine2.coffee +87 -175
  8. data/app/modal.coffee +133 -0
  9. data/bower.json +2 -1
  10. data/conf/message.yaml +0 -0
  11. data/conf/message_pl.yaml +0 -0
  12. data/config.coffee +11 -2
  13. data/engine2.gemspec +2 -2
  14. data/lib/engine2.rb +12 -10
  15. data/lib/engine2/action.rb +1290 -134
  16. data/lib/engine2/{meta/array_meta.rb → action/array.rb} +4 -3
  17. data/lib/engine2/{meta/decode_meta.rb → action/decode.rb} +16 -15
  18. data/lib/engine2/action/delete.rb +65 -0
  19. data/lib/engine2/action/form.rb +16 -0
  20. data/lib/engine2/{meta/infra_meta.rb → action/infra.rb} +118 -85
  21. data/lib/engine2/action/link.rb +117 -0
  22. data/lib/engine2/{meta/list_meta.rb → action/list.rb} +61 -62
  23. data/lib/engine2/action/save.rb +30 -0
  24. data/lib/engine2/action/view.rb +8 -0
  25. data/lib/engine2/action_node.rb +221 -0
  26. data/lib/engine2/core.rb +120 -77
  27. data/lib/engine2/handler.rb +9 -10
  28. data/lib/engine2/model.rb +4 -20
  29. data/lib/engine2/models/Files.rb +2 -1
  30. data/lib/engine2/models/UserInfo.rb +6 -3
  31. data/lib/engine2/post_bootstrap.rb +3 -2
  32. data/lib/engine2/pre_bootstrap.rb +1 -0
  33. data/lib/engine2/scheme.rb +98 -47
  34. data/lib/engine2/templates.rb +1 -0
  35. data/lib/engine2/type_info.rb +6 -4
  36. data/lib/engine2/version.rb +2 -1
  37. data/package.json +12 -10
  38. data/public/favicon.ico +0 -0
  39. data/public/img/ajax-loader-dark.gif +0 -0
  40. data/public/img/ajax-loader.gif +0 -0
  41. data/views/fields/blob.slim +0 -0
  42. data/views/fields/bs_select.slim +0 -0
  43. data/views/fields/bsselect_picker.slim +0 -0
  44. data/views/fields/bsselect_picker_opt.slim +0 -0
  45. data/views/fields/checkbox.slim +0 -0
  46. data/views/fields/checkbox_buttons.slim +0 -0
  47. data/views/fields/checkbox_buttons_opt.slim +0 -0
  48. data/views/fields/currency.slim +0 -0
  49. data/views/fields/date.slim +0 -0
  50. data/views/fields/date_range.slim +0 -0
  51. data/views/fields/date_time.slim +0 -0
  52. data/views/fields/datetime.slim +0 -0
  53. data/views/fields/decimal.slim +0 -0
  54. data/views/fields/decimal_date.slim +0 -0
  55. data/views/fields/decimal_time.slim +0 -0
  56. data/views/fields/email.slim +0 -0
  57. data/views/fields/file_store.slim +7 -7
  58. data/views/fields/input_text.slim +0 -0
  59. data/views/fields/integer.slim +0 -0
  60. data/views/fields/list_bsselect.slim +0 -0
  61. data/views/fields/list_bsselect_opt.slim +0 -0
  62. data/views/fields/list_buttons.slim +0 -0
  63. data/views/fields/list_buttons_opt.slim +0 -0
  64. data/views/fields/list_select.slim +0 -0
  65. data/views/fields/list_select_opt.slim +0 -0
  66. data/views/fields/password.slim +0 -0
  67. data/views/fields/radio_checkbox.slim +0 -0
  68. data/views/fields/scaffold.slim +0 -0
  69. data/views/fields/scaffold_picker.slim +0 -0
  70. data/views/fields/select_picker.slim +0 -0
  71. data/views/fields/select_picker_opt.slim +0 -0
  72. data/views/fields/text_area.slim +1 -0
  73. data/views/fields/time.slim +0 -0
  74. data/views/fields/typeahead_picker.slim +0 -0
  75. data/views/index.slim +3 -3
  76. data/views/infra/index.slim +0 -0
  77. data/views/infra/inspect.slim +20 -0
  78. data/views/modals/close_m.slim +0 -0
  79. data/views/modals/confirm_m.slim +0 -0
  80. data/views/modals/empty_m.slim +0 -0
  81. data/views/modals/menu_m.slim +1 -1
  82. data/views/modals/yes_no_m.slim +0 -0
  83. data/views/panels/menu_m.slim +1 -1
  84. data/views/scaffold/confirm.slim +0 -0
  85. data/views/scaffold/fields.slim +0 -0
  86. data/views/scaffold/form.slim +0 -0
  87. data/views/scaffold/form_collapse.slim +0 -0
  88. data/views/scaffold/form_tabs.slim +0 -0
  89. data/views/scaffold/list.slim +0 -0
  90. data/views/scaffold/message.slim +0 -0
  91. data/views/scaffold/search.slim +0 -0
  92. data/views/scaffold/search_collapse.slim +0 -0
  93. data/views/scaffold/search_tabs.slim +0 -0
  94. data/views/scaffold/view.slim +0 -0
  95. data/views/scaffold/view_collapse.slim +0 -0
  96. data/views/scaffold/view_tabs.slim +0 -0
  97. data/views/search_fields/bsmselect_picker.slim +0 -0
  98. data/views/search_fields/bsselect_picker.slim +0 -0
  99. data/views/search_fields/checkbox.slim +0 -0
  100. data/views/search_fields/checkbox2.slim +0 -0
  101. data/views/search_fields/checkbox_buttons.slim +0 -0
  102. data/views/search_fields/date_range.slim +0 -0
  103. data/views/search_fields/decimal_date_range.slim +0 -0
  104. data/views/search_fields/input_text.slim +0 -0
  105. data/views/search_fields/integer.slim +0 -0
  106. data/views/search_fields/integer_range.slim +0 -0
  107. data/views/search_fields/list_bsmselect.slim +0 -0
  108. data/views/search_fields/list_bsselect.slim +0 -0
  109. data/views/search_fields/list_buttons.slim +0 -0
  110. data/views/search_fields/list_select.slim +0 -0
  111. data/views/search_fields/scaffold_picker.slim +0 -0
  112. data/views/search_fields/select_picker.slim +0 -0
  113. data/views/search_fields/typeahead_picker.slim +0 -0
  114. metadata +41 -42
  115. data/lib/engine2/meta.rb +0 -1216
  116. data/lib/engine2/meta/delete_meta.rb +0 -60
  117. data/lib/engine2/meta/form_meta.rb +0 -15
  118. data/lib/engine2/meta/link_meta.rb +0 -134
  119. data/lib/engine2/meta/save_meta.rb +0 -50
  120. data/lib/engine2/meta/view_meta.rb +0 -7
  121. data/public/__sinatra__/404.png +0 -0
  122. data/public/__sinatra__/500.png +0 -0
data/bower.json CHANGED
@@ -3,7 +3,8 @@
3
3
  "version": "1.0.0",
4
4
  "dependencies": {
5
5
  "angular-strap": "^2.3.10",
6
- "angular": "^1.5.8"
6
+ "angular": "^1.5.8",
7
+ "angular-websocket": "^2.0.0"
7
8
  },
8
9
  "overrides": {
9
10
  "angular": {
File without changes
File without changes
@@ -24,11 +24,15 @@ exports.config =
24
24
  joinTo:
25
25
  'engine2vendor.js': /^node_modules|bower_components/
26
26
  'engine2.js': /^app/
27
+ order:
28
+ before: [
29
+ "app/engine2.coffee"
30
+ ]
27
31
 
28
32
  stylesheets:
29
33
  joinTo:
30
34
  'engine2vendor.css': /^node_modules/
31
- 'engine2app.css': /^app/
35
+ 'engine2.css': /^app/
32
36
  order:
33
37
  before: [
34
38
  /bootstrap.css$/
@@ -50,7 +54,12 @@ exports.config =
50
54
  fix = "$modal.$element = compileData.link(modalScope, function(clonedElement, scope) {});"
51
55
  find: fix.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")
52
56
  replace: "#{fix}$modal.$backdrop = backdropElement;"
53
- )
57
+ ),
58
+ files: [/vendor\.css$/]
59
+ match: (
60
+ find: "../fonts"
61
+ replace: "fonts"
62
+ )
54
63
  ]
55
64
 
56
65
  copycat:
@@ -17,14 +17,14 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ["lib"]
18
18
 
19
19
  spec.add_dependency "sequel", '~> 4'
20
- spec.add_dependency "rack-contrib", '~> 1.4'
21
20
  if defined? JRUBY_VERSION
22
21
  spec.add_dependency 'jdbc-sqlite3', '~> 3.8'
23
22
  else
24
23
  spec.add_dependency 'sqlite3', '~> 1.3'
25
24
  end
26
- spec.add_dependency "sinatra", '~> 1.4'
25
+ spec.add_dependency "sinatra", '~> 2.0'
27
26
  spec.add_dependency 'slim', '~> 3.0'
27
+ spec.add_dependency 'faye-websocket', '~> 0.10'
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 1.11"
30
30
  spec.add_development_dependency "rake", "~> 11"
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen-string-literal: true
2
3
 
3
4
  require 'yaml'
4
5
  require 'logger'
@@ -7,6 +8,7 @@ require 'sinatra'
7
8
  require 'json'
8
9
  require 'slim'
9
10
  require 'engine2/version'
11
+ require 'faye/websocket'
10
12
 
11
13
  %w[
12
14
  core.rb
@@ -14,19 +16,19 @@ require 'engine2/version'
14
16
  type_info.rb
15
17
  model.rb
16
18
  templates.rb
17
- meta.rb
18
19
  action.rb
20
+ action_node.rb
19
21
  scheme.rb
20
22
 
21
- meta/array_meta.rb
22
- meta/list_meta.rb
23
- meta/view_meta.rb
24
- meta/form_meta.rb
25
- meta/save_meta.rb
26
- meta/delete_meta.rb
27
- meta/decode_meta.rb
28
- meta/link_meta.rb
29
- meta/infra_meta.rb
23
+ action/array.rb
24
+ action/list.rb
25
+ action/view.rb
26
+ action/form.rb
27
+ action/save.rb
28
+ action/delete.rb
29
+ action/decode.rb
30
+ action/link.rb
31
+ action/infra.rb
30
32
  ].each do |f|
31
33
  load "engine2/#{f}"
32
34
  end
@@ -1,213 +1,1369 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Engine2
4
-
5
- class Action < BasicObject
6
- ACCESS_FORBIDDEN ||= ->h{false}
7
- attr_reader :parent, :name, :number, :actions, :recheck_access
8
- attr_reader :meta_proc
5
+ class Action
6
+ attr_reader :node, :meta, :assets, :static, :invokable
9
7
 
10
8
  class << self
11
- attr_accessor :count
9
+ def action_type at = nil
10
+ at ? @action_type = at : @action_type
11
+ end
12
+
13
+ def http_method hm = nil
14
+ hm ? @http_method = hm : @http_method
15
+ end
12
16
 
13
- def default_meta
14
- Class.new(InlineMeta){meta_type :inline}
17
+ def inherited cls
18
+ cls.http_method http_method
19
+ end
20
+
21
+ def inherit
22
+ Class.new self do
23
+ action_type superclass.action_type
24
+ end
15
25
  end
16
26
  end
17
27
 
18
- def initialize parent, name, meta_class, assets
19
- Action.count += 1
20
- @number = Action.count
21
- @parent = parent
22
- @name = name
23
- @meta = meta_class.new(self, assets)
24
- @actions = {}
28
+ http_method :get
29
+
30
+ def initialize node, assets, static = self
31
+ @meta = {}
32
+ @node = node
33
+ @assets = assets
34
+ @static = static
35
+ end
36
+
37
+ def http_method
38
+ @http_method # || (raise E2Error.new("No http method for action #{self.class}"))
25
39
  end
26
40
 
27
- def * &blk
28
- @meta_proc = @meta_proc ? @meta_proc.chain(&blk) : blk if blk
29
- @meta
41
+ def action_type
42
+ @action_type || (raise E2Error.new("No action_type for action #{self.class}"))
30
43
  end
31
44
 
32
- alias :meta :*
45
+ def check_static_action
46
+ raise E2Error.new("Static action required") if dynamic?
47
+ end
33
48
 
34
- def access! &blk
35
- ::Kernel.raise E2Error.new("Access for action #{name} already defined") if @access_block
36
- @access_block = blk
49
+ def define_invoke &blk
50
+ check_static_action
51
+ self.class.class_eval{define_method :invoke, &blk}
37
52
  end
38
53
 
39
- def access_forbidden!
40
- access! &ACCESS_FORBIDDEN
54
+ def invoke! handler
55
+ if rmp = @request_action_proc
56
+ action = self.class.new(node, assets, self)
57
+ action_result = action.instance_exec(handler, *action.request_action_proc_params(handler), &rmp)
58
+ action.post_process
59
+ response = @requestable ? (action_result || {}) : action.invoke(handler)
60
+ response[:meta] = action.meta
61
+ response
62
+ else
63
+ invoke(handler)
64
+ end
41
65
  end
42
66
 
43
- def check_access! handler
44
- !@access_block || @access_block.(handler)
67
+ def repeat time
68
+ @meta[:repeat] = time
45
69
  end
46
70
 
47
- def run_scheme name, *args, &blk
48
- result = instance_exec(*args, &SCHEMES[name])
49
- result.instance_eval(&blk) if blk
50
- result
71
+ def arguments args
72
+ @meta[:arguments] = args
51
73
  end
52
74
 
53
- def define_action name, meta_class = Action.default_meta, assets = {}, &blk
54
- ::Kernel.raise E2Error.new("Action #{name} already defined") if @actions[name]
55
- action = @actions[name] = Action.new(self, name, meta_class, assets)
56
- action.*.pre_run
57
- define_singleton_method! name do |&ablk| # forbidden list
58
- action.instance_eval(&ablk) if ablk
59
- action
60
- end
61
- action.instance_eval(&blk) if blk
62
- action.*.action_defined
63
- action
75
+ def execute time
76
+ @meta[:execute] = time
64
77
  end
65
78
 
66
- def define_action_meta name, meta_class = Action.default_meta, assets = {}, &blk
67
- define_action name, meta_class, assets do
68
- self.* &blk
79
+ def dynamic?
80
+ self != @static
81
+ end
82
+
83
+ # def [] *keys
84
+ # @meta.path(*keys)
85
+ # end
86
+
87
+ # def []= *keys, value
88
+ # @meta.path!(*keys, value)
89
+ # end
90
+
91
+ def lookup *keys
92
+ if dynamic? # we are the request action
93
+ value = @meta.path(*keys)
94
+ value.nil? ? @static.meta.path(*keys) : value
95
+ # value || @static.value.path(keys)
96
+ else
97
+ @meta.path(*keys)
69
98
  end
70
99
  end
71
100
 
72
- def define_action_invoke name, meta_class = Action.default_meta, assets = {}, &blk
73
- define_action name, meta_class, assets do
74
- self.*.define_invoke &blk
101
+ def merge *keys
102
+ if keys.length == 1
103
+ key = keys.first
104
+ dynamic? ? @static.meta[key].merge(@meta[key] || {}) : @meta[key]
105
+ else
106
+ dynamic? ? @static.meta.path(*keys).merge(@meta.path(*keys)) : @meta.path(*keys)
75
107
  end
76
108
  end
77
109
 
78
- def define_action_bundle name, *actions
79
- define_singleton_method!(name) do |&blk|
80
- if blk
81
- actions.each{|a|__send__(a, &blk)} # if @actions[action] ?
110
+ def freeze_action
111
+ hash = @meta
112
+ hash.freeze
113
+ # hash.each_pair{|k, v| freeze(v) if v.is_a? Hash}
114
+ freeze
115
+ end
116
+
117
+ def request_action_proc_params handler
118
+ []
119
+ end
120
+
121
+ def request &blk
122
+ raise E2Error.new("No block given for request action") unless blk
123
+ raise E2Error.new("No request block in request action allowed") if dynamic?
124
+ @request_action_proc = @request_action_proc ? @request_action_proc.chain_args(&blk) : blk
125
+ nil
126
+ end
127
+
128
+ def pre_run
129
+ @action_type = self.class.action_type
130
+ @http_method = self.class.http_method
131
+ end
132
+
133
+ def node_defined
134
+ end
135
+
136
+ def post_run
137
+ if respond_to? :invoke
138
+ @invokable = true
139
+ else
140
+ if @request_action_proc
141
+ @invokable = true
142
+ @requestable = true
82
143
  else
83
- ActionBundle.new(self, actions)
144
+ @meta[:invokable] = false
84
145
  end
85
146
  end
147
+ @meta[:dynamic_meta] = true if @request_action_proc
148
+ post_process
149
+ end
150
+
151
+ def post_process
152
+ end
153
+
154
+ def split_keys id
155
+ Sequel::split_keys(id)
86
156
  end
157
+ end
87
158
 
88
- def define_singleton_method! name, &blk
89
- class << self;self;end.instance_eval do # __realclass__
90
- define_method name, &blk
159
+ module ActionWebSocketSupport
160
+ WS_METHODS ||= Faye::WebSocket::API::TYPES.keys.map(&:to_sym)
161
+ WS_METHODS.each do |method|
162
+ define_method :"ws_#{method}" do |&blk|
163
+ @ws_methods[method] = blk
91
164
  end
92
165
  end
93
166
 
94
- def [] name
95
- @actions[name]
167
+ def pre_run
168
+ super
169
+ @ws_methods = {}
170
+ @meta[:websocket] = {options: {}}
171
+ end
172
+
173
+ def ws_options opts
174
+ @meta[:websocket][:options].merge! opts
175
+ end
176
+
177
+ def ws_execute execute
178
+ (@meta[:websocket][:execute] ||= {}).merge! execute
179
+ end
180
+
181
+ def post_run
182
+ super
183
+ @invokable = true
96
184
  end
97
185
 
98
- def actions_info handler
99
- info = actions.inject({}) do |h, (name, a)|
100
- meta = a.*
101
- h[name] = {
102
- meta_type: meta.meta_type,
103
- method: meta.http_method,
104
- number: a.number,
105
- access: recheck_access ? nil : a.check_access!(handler),
106
- recheck_access: a.recheck_access,
107
- terminal: a.actions.empty?,
108
- meta: !meta.get.empty?
109
- }
110
- h
186
+ def invoke! handler
187
+ if Faye::WebSocket.websocket?(handler.env)
188
+ ws = Faye::WebSocket.new(handler.env)
189
+ @ws_methods.each do |method, blk|
190
+ ws.on(method) do |evt|
191
+ begin
192
+ if method == :message
193
+ blk.(JSON.parse(evt.data, symbolize_names: true), ws, evt)
194
+ else
195
+ blk.(evt, ws, evt)
196
+ end
197
+ rescue Exception => e
198
+ ws.send! error: {exception: e, method: method}
199
+ end
200
+ end
201
+ end
202
+ ws.rack_response
203
+ else
204
+ super
111
205
  end
206
+ end
207
+ end
112
208
 
113
- info.first[1][:default] = true unless actions.empty?
114
- info
209
+ class WebSocketAction < Action
210
+ include ActionWebSocketSupport
211
+ end
212
+
213
+ class InlineAction < Action
214
+ action_type :inline
215
+ end
216
+
217
+ class RootAction < Action
218
+ def initialize *args
219
+ super
220
+ @meta.merge! environment: Handler::environment, application: Engine2::SETTINGS[:name], key_separator: Engine2::SETTINGS[:key_separator], ws_methods: ActionWebSocketSupport::WS_METHODS
221
+ end
222
+ end
223
+
224
+ module ActionAPISupport
225
+ def info field
226
+ (@meta[:info] ||= {})[field.to_sym] ||= {}
227
+ end
228
+
229
+ def config
230
+ @meta[:config] ||= {}
115
231
  end
116
232
 
117
- def access_info handler
118
- @actions.inject({}) do |h, (name, a)|
119
- h[name] = a.check_access!(handler)
120
- h
233
+ def info! *fields, options
234
+ raise E2Error.new("No fields given to info") if fields.empty?
235
+ fields.each do |field|
236
+ info(field).merge! options # rmerge ?
121
237
  end
122
238
  end
123
239
 
124
- def recheck_access!
125
- @recheck_access = true
240
+ def loc! hash
241
+ (@meta[:loc] ||= {}).merge! hash
126
242
  end
127
243
 
128
- def each_action &blk
129
- # no self
130
- @actions.each_pair do |n, a|
131
- a.each_action(&blk) if yield a
244
+ def decorate list
245
+ list.each do |f|
246
+ info(f)[:loc] ||= LOCS[f.to_sym]
132
247
  end
133
248
  end
134
249
 
135
- def to_a_rec root = true, result = [], &blk # optimize
136
- if root && (yield self)
137
- result << self
138
- @actions.each_pair do |n, a|
139
- if yield a
140
- result << a
141
- a.to_a_rec(false, result, &blk)
250
+ def render field, options
251
+ info! field, render: options
252
+ end
253
+
254
+ def hide_fields *flds
255
+ info! *flds, hidden: true
256
+ end
257
+
258
+ def show_fields *flds
259
+ info! *flds, hidden: false
260
+ end
261
+
262
+ def field_filter *flds, filter
263
+ info! *flds, filter: filter
264
+ end
265
+ end
266
+
267
+ module ActionMenuSupport
268
+ def menu menu_name, &blk
269
+ @menus ||= {}
270
+ @menus[menu_name] ||= ActionMenuBuilder.new(:root)
271
+ @menus[menu_name].instance_eval(&blk) if blk
272
+ @menus[menu_name]
273
+ end
274
+
275
+ def menu? menu_name
276
+ @menus && @menus[menu_name]
277
+ end
278
+
279
+ def post_process
280
+ super
281
+ if @menus && !@menus.empty?
282
+ @meta[:menus] = {}
283
+ @menus.each_pair do |name, menu|
284
+ @meta[:menus][name] = {entries: menu.to_a, properties: menu.properties}
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ module ActionModelSupport
291
+ def pre_run
292
+ if !(mdl = @assets[:model])
293
+ act = node
294
+ begin
295
+ act = act.parent
296
+ raise E2Error.new("Model not found in tree for node: #{node.name}") unless act
297
+ mdl = act.*.assets[:model]
298
+ end until mdl
299
+
300
+ if asc = @assets[:assoc]
301
+ @assets[:model] = asc.associated_class
302
+ # raise E2Error.new("Association '#{asc}' for model '#{asc[:class_name]}' not found") unless @assets[:model]
303
+ else
304
+ @assets[:model] = mdl
305
+ asc = act.*.assets[:assoc]
306
+ @assets[:assoc] = asc if asc
307
+ end
308
+ end
309
+
310
+ # @meta[:model!] = assets[:model]
311
+ # @meta[:assoc!] = assets[:assoc] ? assets[:assoc][:name] : nil
312
+ # @meta[:action_class!] = self.class
313
+ super
314
+ end
315
+
316
+ def hide_pk
317
+ hide_fields *assets[:model].primary_keys
318
+ end
319
+
320
+ def show_pk
321
+ show_fields *assets[:model].primary_keys
322
+ end
323
+
324
+ def get_type_info name
325
+ model = assets[:model]
326
+ info = case name
327
+ when Symbol
328
+ model.type_info[name]
329
+ when Sequel::SQL::QualifiedIdentifier
330
+ assoc = model.many_to_one_associations[name.table] || model.many_to_many_associations[name.table]
331
+ raise E2Error.new("Association #{name.table} not found for model #{model}") unless assoc
332
+ assoc.associated_class.type_info[name.column]
333
+ else
334
+ raise E2Error.new("Unknown type info key: #{name} in model #{model}")
335
+ end
336
+
337
+ raise E2Error.new("Type info not found for '#{name}' in model '#{model}'") unless info
338
+ info
339
+ end
340
+
341
+ # def parent_model_name
342
+ # model = @assets[:model]
343
+ # prnt = node.parent
344
+
345
+ # while prnt && prnt.*.assets[:model] == model
346
+ # prnt = prnt.parent
347
+ # end
348
+ # m = prnt.*.assets[:model]
349
+ # m ? m.name : nil
350
+ # end
351
+
352
+ def node_defined
353
+ super
354
+ # p_model_name = parent_model_name
355
+ model = @assets[:model]
356
+
357
+ at = action_type
358
+ case at
359
+ when :list, :star_to_many_list, :star_to_many_link_list, :star_to_many_field, :star_to_many_field_link_list # :many_to_one_list
360
+ model.many_to_one_associations.each do |assoc_name, assoc|
361
+ unless assoc[:propagate] == false # || p_model_name == assoc[:class_name]
362
+ dc = model.type_info[assoc[:keys].first][:decode]
363
+ node.run_scheme :decode, model, assoc_name, dc[:search]
364
+ end
365
+ end
366
+ end
367
+
368
+ case at
369
+ when :modify, :create
370
+ model.many_to_one_associations.each do |assoc_name, assoc|
371
+ unless assoc[:propagate] == false # || p_model_name == assoc[:class_name]
372
+ dc = model.type_info[assoc[:keys].first][:decode]
373
+ node.run_scheme :decode, model, assoc_name, dc[:form]
374
+ end
375
+ end
376
+ end
377
+
378
+ case at
379
+ when :list #, :star_to_many_list, :many_to_one_list # list dropdowns
380
+ divider = false
381
+ model.one_to_many_associations.merge(model.many_to_many_associations).each do |assoc_name, assoc|
382
+ unless assoc[:propagate] == false
383
+ menu(:item_menu).divider unless divider
384
+ divider ||= true
385
+ menu(:item_menu).option :"#{assoc_name}!", icon: "list" # , click: "action.show_assoc($index, \"#{assoc_name}!\")"
386
+ node.run_scheme :star_to_many, :"#{assoc_name}!", assoc
387
+ end
388
+ end
389
+ end
390
+
391
+ case at
392
+ when :modify, :create
393
+ model.type_info.each do |field, info|
394
+ case info[:type]
395
+ when :blob_store
396
+ node.run_scheme :blob_store, model, field
397
+ when :foreign_blob_store
398
+ node.run_scheme :foreign_blob_store, model, field
399
+ when :file_store
400
+ node.run_scheme :file_store, model, field
401
+ when :star_to_many_field
402
+ assoc = model.association_reflections[info[:assoc_name]] # info[:name] ?
403
+ raise E2Error.new("Association '#{info[:assoc_name]}' not found for model '#{model}'") unless assoc
404
+ node.run_scheme :star_to_many_field, assoc, field
142
405
  end
143
406
  end
144
407
  end
145
- result
146
408
  end
147
409
 
148
- def inspect
149
- "Action: #{@name}, meta: #{@meta.class}, meta_type: #{@meta.meta_type}"
410
+ def unsupported_association assoc
411
+ raise E2Error.new("Unsupported association: #{assoc}")
412
+ end
413
+ end
414
+
415
+ module ActionQuerySupport
416
+ def query q, &blk
417
+ @query = blk ? q.naked.with_row_proc(blk) : q.naked
150
418
  end
151
419
 
152
- def setup_action_tree
153
- time = ::Time.now
420
+ def post_run
421
+ query select(*assets[:model].columns) unless @query
422
+ super
423
+ end
424
+
425
+ def get_query # move to query ?
426
+ if dynamic?
427
+ @query || @static.get_query
428
+ else
429
+ @query
430
+ end
431
+ end
432
+
433
+ def find_record handler, id
434
+ get_query[assets[:model].primary_keys_hash_qualified(split_keys(id))]
435
+ end
436
+
437
+ def select *args, use_pk: true, &blk
438
+ ds = assets[:model].select(*args, &blk)
439
+ ds = ds.ensure_primary_key if use_pk
440
+ ds.setup!(@meta[:fields] = [])
441
+ end
442
+ end
443
+
444
+ module ActionTabSupport
445
+ def select_tabs tabs, *args, &blk
446
+ field_tabs tabs
447
+ select *tabs.map{|name, fields|fields}.flatten, *args, &blk
448
+ end
449
+
450
+ def field_tabs hash
451
+ @meta[:tabs] = hash.map{|k, v| {name: k, loc: LOCS[k], fields: v} }
452
+ end
453
+
454
+ def lazy_tab tab_name
455
+ tabs = @meta[:tabs]
456
+ raise E2Error.new("No tabs defined") unless tabs
457
+ tab = tabs.find{|t| t[:name] == tab_name}
458
+ raise E2Error.new("No tab #{tab_name} defined") unless tab
459
+ tab[:lazy] = true
460
+ end
461
+ end
462
+
463
+ module ActionAngularSupport
464
+ def ng_execute expr
465
+ (@meta[:execute] ||= "") << expr + ";"
466
+ end
467
+
468
+ def ng_record! name, value
469
+ value = case value
470
+ when String
471
+ "'#{value}'"
472
+ when nil
473
+ 'null'
474
+ else
475
+ value
476
+ end
477
+
478
+ "action.record['#{name}'] = #{value}"
479
+ end
480
+
481
+ def ng_record name
482
+ "action.record['#{name}']"
483
+ end
484
+
485
+ def ng_info! name, *selector, expression
486
+ # expression = "'#{expression}'" if expression.is_a? String
487
+ "action.meta.info['#{name}'].#{selector.join('.')} = #{expression}"
488
+ end
489
+
490
+ def ng_call name, *args
491
+ # TODO
492
+ end
493
+ end
494
+
495
+ module ActionPanelSupport
496
+ def pre_run
497
+ modal_action true
498
+ super
499
+ end
500
+
501
+ def post_run
502
+ super
503
+ if @meta[:panel]
504
+ panel_panel_template 'menu_m' if panel[:panel_template].nil?
505
+ # modal_action false if panel[:panel_template] == false
506
+ panel_class '' unless panel[:class]
507
+ panel_footer true if panel[:footer] != false && menu?(:panel_menu)
508
+ panel_header true if panel[:header] != false
509
+ end
510
+ end
511
+
512
+ def panel
513
+ @meta[:panel] ||= {}
514
+ end
515
+
516
+ def modal_action modal = true
517
+ panel[:modal_action] = modal
518
+ end
519
+
520
+ def panel_template tmpl
521
+ panel[:template] = tmpl
522
+ end
523
+
524
+ def panel_panel_template tmpl
525
+ panel[:panel_template] = tmpl
526
+ end
154
527
 
155
- model_actions = {}
156
- each_action do |action|
157
- if model = action.*.assets[:model]
158
- model_name = model.name.to_sym
159
- model.synchronize_type_info
160
- model_actions[model_name] = action.to_a_rec{|a| !a.*.assets[:assoc]}
161
- action.run_scheme(model_name) if SCHEMES[model_name, false]
162
- false
528
+ def panel_class cls
529
+ panel[:class] = cls
530
+ end
531
+
532
+ def panel_title tle
533
+ panel[:title] = tle
534
+ end
535
+
536
+ def panel_header hdr
537
+ panel[:header] = hdr
538
+ end
539
+
540
+ def panel_footer ftr
541
+ panel[:footer] = ftr
542
+ end
543
+ end
544
+
545
+ class MenuAction < Action
546
+ include ActionMenuSupport
547
+ action_type :menu
548
+
549
+ def invoke handler
550
+ {}
551
+ end
552
+ end
553
+
554
+ class ConfirmAction < Action
555
+ include ActionPanelSupport, ActionMenuSupport
556
+ action_type :confirm
557
+
558
+ def message msg
559
+ @meta[:message] = msg
560
+ end
561
+
562
+ def pre_run
563
+ super
564
+ panel_template 'scaffold/message'
565
+ panel_title LOCS[:confirmation]
566
+ panel_class 'modal-default'
567
+
568
+ menu :panel_menu do
569
+ option :approve, icon: "ok", loc: LOCS[:ok], disabled: "action.action_pending()"
570
+ option :cancel, icon: "remove"
571
+ end
572
+ end
573
+
574
+ def invoke handler
575
+ params = handler.request.params
576
+ # params.merge({arguments: params.keys})
577
+ end
578
+ end
579
+
580
+ module ActionOnChangeSupport
581
+ def on_change field, &blk
582
+ node_name = :"#{field}_on_change"
583
+ nd = node.define_node node_name, (blk.arity > 2 ? OnChangeGetAction : OnChangePostAction)
584
+ nd.*{request &blk}
585
+
586
+ info! field, remote_onchange: node_name
587
+ info! field, remote_onchange_record: :true if blk.arity > 2
588
+ end
589
+
590
+ class OnChangeAction < Action
591
+ include ActionAPISupport, ActionAngularSupport
592
+
593
+ def request_action_proc_params handler
594
+ if handler.request.post?
595
+ json = handler.post_to_json
596
+ [json[:value], json[:record]]
163
597
  else
164
- true
165
- end
166
- end
167
-
168
- each_action do |action|
169
- meta = action.*
170
- model = meta.assets[:model]
171
- assoc = meta.assets[:assoc]
172
- if model && assoc
173
- if source_actions = model_actions[model.name.to_sym]
174
- source_action = source_actions.select{|sa| sa.meta_proc && sa.*.class >= meta.class}
175
- # source_action = source_actions.select{|sa| sa.meta_proc && meta.class <= sa.*.class}
176
- unless source_action.empty?
177
- # raise E2Error.new("Multiple meta candidates for #{action.inspect} found in '#{source_action.inspect}'") if source_action.size > 1
178
- # puts "#{action.inspect} => #{source_action.inspect}\n"
179
- meta.instance_eval(&source_action.first.meta_proc)
180
- end
598
+ params = handler.request.params
599
+ [params["value"], params["record"]]
600
+ end
601
+ end
602
+
603
+ def invoke handler
604
+ {}
605
+ end
606
+ end
607
+
608
+ class OnChangeGetAction < OnChangeAction
609
+ action_type :on_change
610
+
611
+ def request_action_proc_params handler
612
+ params = handler.request.params
613
+ [params["value"], params["record"]]
614
+ end
615
+ end
616
+
617
+ class OnChangePostAction < OnChangeAction
618
+ http_method :post
619
+ action_type :on_change
620
+
621
+ def request_action_proc_params handler
622
+ json = handler.post_to_json
623
+ [json[:value], json[:record]]
624
+ end
625
+ end
626
+ end
627
+
628
+ module ActionListSupport
629
+ include ActionModelSupport, ActionAPISupport, ActionTabSupport, ActionPanelSupport, ActionMenuSupport, ActionOnChangeSupport
630
+ attr_reader :filters, :orders
631
+
632
+ def pre_run
633
+ super
634
+ config.merge!(per_page: 10, use_count: false, selectable: true) # search_active: false,
635
+
636
+ panel_template 'scaffold/list'
637
+ panel_title "#{:list.icon} #{LOCS[assets[:model].name.to_sym]}"
638
+ loc! LOCS[:list_locs]
639
+ menu :menu do
640
+ properties break: 2, group_class: "btn-group-xs"
641
+ option :search_toggle, icon: "search", show: "action.meta.search_fields", active: "action.ui_state.search_active", button_loc: false
642
+ # divider
643
+ option :refresh, icon: "refresh", button_loc: false
644
+ option :default_order, icon: "signal", button_loc: false
645
+ divider
646
+ option :debug_info, icon: "list-alt" do
647
+ option :show_meta, icon: "eye-open"
648
+ end if Handler::development?
649
+ end
650
+
651
+ menu :item_menu do
652
+ properties break: 1, group_class: "btn-group-xs"
653
+ end
654
+
655
+ @meta[:state] = [:query, :ui_state]
656
+ end
657
+
658
+ def field_tabs hash
659
+ super
660
+ search_template 'scaffold/search_tabs'
661
+ end
662
+
663
+ def select_toggle_menu
664
+ m = menu :menu
665
+ unless m.option_index(:select_toggle, false)
666
+ m.option_after :default_order, :select_toggle, icon: "check", enabled: "action.meta.config.selectable", active: "action.selection", button_loc: false
667
+ end
668
+ end
669
+
670
+ def post_run
671
+ unless panel[:class]
672
+ panel_class case @meta[:fields].size
673
+ when 1..3; ''
674
+ when 4..6; 'modal-large'
675
+ else; 'modal-huge'
676
+ end
677
+ end
678
+
679
+ super
680
+ @meta[:primary_fields] = assets[:model].primary_keys
681
+ end
682
+
683
+ # def find_renderer type_info
684
+ # renderer = DefaultSearchRenderers[type_info[:type]] || DefaultSearchRenderers[type_info[:otype]]
685
+ # raise E2Error.new("No search renderer found for field '#{type_info[:name]}'") unless renderer
686
+ # renderer.(self, type_info)
687
+ # end
688
+
689
+ def post_process
690
+ if fields = @meta[:search_fields]
691
+ fields = fields - static.meta[:search_fields] if dynamic?
692
+
693
+ decorate(fields)
694
+ fields.each do |name|
695
+ type_info = get_type_info(name)
696
+
697
+ # render = info[name][:render]
698
+ # if not render
699
+ # info[name][:render] = find_renderer(type_info)
700
+ # else
701
+ # info[name][:render].merge!(find_renderer(type_info)){|key, v1, v2|v1}
702
+ # end
703
+
704
+ info(name)[:render] ||= begin # set before :fields
705
+ renderer = DefaultSearchRenderers[type_info[:type]] || DefaultSearchRenderers[type_info[:otype]]
706
+ raise E2Error.new("No search renderer found for field '#{type_info[:name]}'") unless renderer
707
+ renderer.(self, type_info)
181
708
  end
709
+
710
+ proc = SearchRendererPostProcessors[type_info[:type]] || ListRendererPostProcessors[type_info[:type]] # ?
711
+ proc.(self, name, type_info) if proc
182
712
  end
713
+ end
183
714
 
184
- meta.instance_eval(&action.meta_proc) if action.meta_proc
185
- true
715
+ if fields = @meta[:fields]
716
+ fields = fields - static.meta[:fields] if dynamic?
717
+
718
+ decorate(fields)
719
+ fields.each do |name|
720
+ type_info = get_type_info(name)
721
+ proc = ListRendererPostProcessors[type_info[:type]]
722
+ proc.(self, name, type_info) if proc
723
+ end
186
724
  end
187
725
 
188
- each_action do |action|
189
- action.*.post_run
190
- action.*.freeze_meta
726
+ super
727
+ end
728
+
729
+ def search_template template
730
+ panel[:search_template] = template
731
+ end
732
+
733
+ def sortable *flds
734
+ flds = @meta[:fields] if flds.empty?
735
+ info! *flds, sort: true
736
+ end
737
+
738
+ def search_live *flds
739
+ flds = @meta[:search_fields] if flds.empty?
740
+ info! *flds, search_live: true
741
+ end
742
+
743
+ def searchable *flds
744
+ @meta.delete(:tabs)
745
+ search_template 'scaffold/search'
746
+ @meta[:search_fields] = *flds
747
+ end
748
+
749
+ def searchable_tabs tabs
750
+ searchable *tabs.map{|name, fields|fields}.flatten
751
+ field_tabs tabs
752
+ end
753
+
754
+ def template
755
+ SearchTemplates
756
+ end
757
+
758
+ def filter name, &blk
759
+ (@filters ||= {})[name] = blk
760
+ end
761
+
762
+ def filter_case_insensitive name
763
+ raise E2Error.new("Field '#{name}' needs to be a string one") unless get_type_info(name)[:otype] == :string
764
+ filter(name){|query, hash, handler| query.where(name.ilike("%#{hash[name]}%")) }
765
+ end
766
+
767
+ def order name, &blk
768
+ (@orders ||= {})[name] = blk
769
+ end
770
+ end
771
+
772
+ module ActionApproveSupport
773
+ include ActionModelSupport
774
+ attr_reader :validations
775
+
776
+ def self.included action
777
+ action.http_method :post if action.is_a? Class
778
+ end
779
+
780
+ def validate_fields *fields
781
+ if fields.empty?
782
+ @validate_fields
783
+ else
784
+ @validate_fields = assets[:model].type_info.keys & (fields + assets[:model].primary_keys).uniq
785
+ end
786
+ end
787
+
788
+ def before_approve handler, record
789
+ end
790
+
791
+ def after_approve handler, record
792
+ end
793
+
794
+ def validate_and_approve handler, record, parent_id
795
+ static.before_approve(handler, record)
796
+ record.valid?
797
+ validate_record(handler, record)
798
+ if record.errors.empty?
799
+ static.after_approve(handler, record)
191
800
  true
801
+ else
802
+ false
192
803
  end
804
+ end
805
+
806
+ def allocate_record handler, json_rec
807
+ model = assets[:model]
808
+ handler.permit json_rec.is_a?(Hash)
809
+ val_fields = (dynamic? ? static.validate_fields : @validate_fields) || model.type_info.keys
810
+ handler.permit (json_rec.keys - val_fields).empty?
193
811
 
194
- ::Kernel::puts "ACTIONS: #{Action.count}, Time: #{::Time.now - time}"
812
+ record = model.call(json_rec)
813
+ record.validate_fields = val_fields
814
+ record
195
815
  end
196
816
 
197
- def p *args
198
- ::Kernel::p *args
817
+ def record handler, record
818
+ {errors: nil}
819
+ end
820
+
821
+ def invoke handler
822
+ json = handler.post_to_json
823
+ record = allocate_record(handler, json[:record])
824
+ validate_and_approve(handler, record, json[:parent_id]) ? static.record(handler, record) : {record!: record.to_hash, errors!: record.errors}
825
+ end
826
+
827
+ def validate name, &blk
828
+ (@validations ||= {})[name] = blk
829
+ end
830
+
831
+ def validate_record handler, record
832
+ @validations.each do |name, val|
833
+ unless record.errors[name]
834
+ result = val.(record, handler)
835
+ record.errors.add(name, result) if result
836
+ end
837
+ end if @validations
838
+ end
839
+
840
+ def pre_run
841
+ super
842
+ execute "action.errors || [action.parent().invoke(), action.panel_close()]"
843
+ end
844
+
845
+ def post_run
846
+ super
847
+ validate_fields *node.parent.*.meta[:fields] unless validate_fields
199
848
  end
200
849
  end
201
850
 
851
+ module ActionSaveSupport
852
+ include ActionApproveSupport
202
853
 
203
- class ActionBundle
204
- def initialize action, action_names
205
- @action = action
206
- @action_names = action_names
854
+ def self.included action
855
+ action.http_method :post
856
+ class << action
857
+ attr_accessor :validate_only
858
+ end
207
859
  end
208
860
 
209
- def method_missing name, *args, &blk
210
- @action_names.each{|an| @action[an].__send__(name, *args, &blk)}
861
+ def validate_and_approve handler, record, parent_id, validate_only = self.class.validate_only
862
+ if validate_only then
863
+ super(handler, record, parent_id)
864
+ else
865
+ record.skip_save_refresh = true
866
+ record.raise_on_save_failure = false
867
+ model = assets[:model]
868
+ assoc = assets[:assoc]
869
+ new_assoc = record.new? && assoc && assoc[:type]
870
+
871
+ save = lambda do|c|
872
+ if super(handler, record, parent_id)
873
+ if new_assoc == :one_to_many
874
+ handler.permit parent_id
875
+ assoc[:keys].zip(split_keys(parent_id)).each{|k, v|record[k] = v}
876
+ end
877
+
878
+ result = record.save(transaction: false, validate: false)
879
+ if result && new_assoc == :many_to_many
880
+ handler.permit parent_id
881
+ model.db[assoc[:join_table]].insert(assoc[:left_keys] + assoc[:right_keys], split_keys(parent_id) + record.primary_key_values)
882
+ end
883
+
884
+ model.association_reflections.each do |name, assoc|
885
+ hash = record[name]
886
+ if hash.is_a?(Hash)
887
+ validate_and_approve_association(handler, record, name, :create, hash)
888
+ validate_and_approve_association(handler, record, name, :modify, hash)
889
+ nd = node.parent[:"#{name}!"]
890
+ raise Sequel::Rollback unless record.errors.empty?
891
+ nd.confirm_delete.delete.*.invoke_delete_db(handler, hash[:delete].to_a, model.table_name) unless hash[:delete].to_a.empty?
892
+ nd.link.*.invoke_link_db(handler, record.primary_key_values, hash[:link].to_a) unless hash[:link].to_a.empty?
893
+ nd.confirm_unlink.unlink.*.invoke_unlink_db(handler, record.primary_key_values, hash[:unlink].to_a) unless hash[:unlink].to_a.empty?
894
+ end
895
+ end
896
+ result
897
+ end
898
+ end
899
+ (model.validation_in_transaction || new_assoc == :many_to_many) ? model.db.transaction(&save) : save.(nil)
900
+ end
901
+ end
902
+
903
+ def validate_and_approve_association handler, record, assoc_name, node_name, hash
904
+ records = hash[node_name].to_a
905
+ unless records.empty?
906
+ action = node.parent[:"#{assoc_name}!"][node_name].approve.*
907
+ parent_id = Sequel::join_keys(record.primary_key_values)
908
+ records.each do |arec|
909
+ rec = action.allocate_record(handler, arec)
910
+ action.validate_and_approve(handler, rec, parent_id, false)
911
+ rec.errors.each do |k, v|
912
+ (record.errors[assoc_name] ||= []).concat(v)
913
+ end unless rec.errors.empty?
914
+ end
915
+ end
916
+ end
917
+ end
918
+
919
+ module ActionInsertSupport
920
+ def allocate_record handler, json_rec
921
+ record = super(handler, json_rec)
922
+ record.instance_variable_set(:"@new", true)
923
+ model = assets[:model]
924
+ model.primary_keys.each{|k|record.values.delete k} unless model.natural_key
925
+ handler.permit !record.has_primary_key? unless model.natural_key
926
+ record
927
+ end
928
+ end
929
+
930
+ module ActionUpdateSupport
931
+ def allocate_record handler, json_rec
932
+ record = super(handler, json_rec)
933
+ model = assets[:model]
934
+ handler.permit record.has_primary_key? unless model.natural_key or self.class.validate_only
935
+ record
211
936
  end
212
937
  end
213
- end
938
+
939
+ module ActionFormSupport
940
+ include ActionModelSupport, ActionAPISupport, ActionTabSupport, ActionPanelSupport, ActionMenuSupport, ActionAngularSupport, ActionOnChangeSupport
941
+
942
+ def field_template template
943
+ panel[:field_template] = template
944
+ end
945
+
946
+ def pre_run
947
+ super
948
+ panel_template 'scaffold/form'
949
+ field_template 'scaffold/fields'
950
+ panel_class 'modal-large'
951
+ top = node.parent.parent == nil
952
+ menu :panel_menu do
953
+ option :approve, icon: "ok", disabled: "action.action_pending()" # text: true,
954
+ option :cancel, icon: "remove" unless top # text: true,
955
+ end
956
+ # modal_action false
957
+ end
958
+
959
+ def field_tabs hash
960
+ super
961
+ panel_template 'scaffold/form_tabs'
962
+ end
963
+
964
+ def record handler, record
965
+ end
966
+
967
+ def post_process
968
+ if fields = @meta[:fields]
969
+ fields = fields - static.meta[:fields] if dynamic?
970
+
971
+ decorate(fields)
972
+
973
+ fields.each do |name|
974
+ # type_info = model.type_info.fetch(name)
975
+ type_info = get_type_info(name)
976
+
977
+ info(name)[:render] ||= begin
978
+ renderer = DefaultFormRenderers[type_info[:type]] # .merge(default: true)
979
+ raise E2Error.new("No form renderer found for field '#{type_info[:name]}' of type '#{type_info[:type]}'") unless renderer
980
+ renderer.(self, type_info)
981
+ end
982
+
983
+ proc = FormRendererPostProcessors[type_info[:type]]
984
+ proc.(self, name, type_info) if proc
985
+ end
986
+
987
+ assoc = assets[:assoc]
988
+ if assoc && assoc[:type] == :one_to_many
989
+ # fields.select{|f| assoc[:keys].include? f}.each do |key|
990
+ # # hide_fields(key) if self[:info, key, :hidden] == nil
991
+ # info! key, disabled: true
992
+ # end
993
+ assoc[:keys].each do |key|
994
+ info! key, disabled: true if fields.include? key
995
+ end
996
+ end
997
+ end
998
+
999
+ super
1000
+ end
1001
+
1002
+ def post_run
1003
+ super
1004
+ @meta[:primary_fields] = assets[:model].primary_keys
1005
+ end
1006
+
1007
+ def template
1008
+ Templates
1009
+ end
1010
+
1011
+ def hr_after field, message = '-'
1012
+ info! field, hr: message
1013
+ end
1014
+ end
1015
+
1016
+ module ActionCreateSupport
1017
+ include ActionFormSupport
1018
+
1019
+ def self.included action
1020
+ action.action_type :create
1021
+ end
1022
+
1023
+ def pre_run
1024
+ super
1025
+ panel_title LOCS[:create_title]
1026
+ node.parent.*.menu(:menu).option_at 0, node.name, icon: "plus-sign", button_loc: false
1027
+
1028
+ hide_pk unless assets[:model].natural_key
1029
+ end
1030
+
1031
+ def record handler, record
1032
+ create_record(handler, record)
1033
+ end
1034
+
1035
+ def create_record handler, record
1036
+ end
1037
+
1038
+ def invoke handler
1039
+ record = {}
1040
+ # if assoc = assets[:assoc]
1041
+ # case assoc[:type]
1042
+ # when :one_to_many
1043
+ # parent = handler.params[:parent_id]
1044
+ # assoc[:keys].zip(split_keys(parent)).each{|key, val| record[key] = val} if parent
1045
+ # end
1046
+ # end
1047
+ static.record(handler, record)
1048
+ {record: record, new: true}
1049
+ end
1050
+ end
1051
+
1052
+ module ActionModifySupport
1053
+ include ActionFormSupport
1054
+
1055
+ def self.included action
1056
+ action.action_type :modify
1057
+ end
1058
+
1059
+ def pre_run
1060
+ super
1061
+ panel_title LOCS[:modify_title]
1062
+ node.parent.*.menu(:item_menu).option node.name, icon: "pencil", button_loc: false
1063
+ end
1064
+
1065
+ def record handler, record
1066
+ modify_record(handler, record)
1067
+ end
1068
+
1069
+ def modify_record handler, record
1070
+ end
1071
+
1072
+ def invoke handler
1073
+ handler.permit id = handler.params[:id]
1074
+ record = find_record(handler, id)
1075
+
1076
+ if record
1077
+ static.record(handler, record)
1078
+ {record: record}
1079
+ else
1080
+ handler.halt_not_found LOCS[:no_entry]
1081
+ end
1082
+ end
1083
+
1084
+ def post_run
1085
+ super
1086
+ assets[:model].primary_keys.each do |key| # pre_run ?
1087
+ info! key, disabled: true
1088
+ end
1089
+ end
1090
+ end
1091
+
1092
+ module ActionViewSupport
1093
+ include ActionModelSupport, ActionAPISupport, ActionTabSupport, ActionPanelSupport, ActionMenuSupport
1094
+
1095
+ def self.included action
1096
+ action.action_type :view
1097
+ end
1098
+
1099
+ def pre_run
1100
+ super
1101
+ panel_template 'scaffold/view'
1102
+ panel_title LOCS[:view_title]
1103
+ panel[:backdrop] = true
1104
+
1105
+ menu(:panel_menu).option :cancel, icon: "remove"
1106
+ node.parent.*.menu(:item_menu).option node.name, icon: "file", button_loc: false
1107
+ end
1108
+
1109
+ def field_tabs hash
1110
+ super
1111
+ panel_template 'scaffold/view_tabs'
1112
+ end
1113
+
1114
+ def record handler, record
1115
+ end
1116
+
1117
+ def invoke handler
1118
+ handler.permit id = handler.params[:id]
1119
+ record = find_record(handler, id)
1120
+ if record
1121
+ static.record(handler, record)
1122
+ {record: record}
1123
+ else
1124
+ handler.halt_not_found LOCS[:no_entry]
1125
+ end
1126
+ end
1127
+
1128
+ def post_process
1129
+ if fields = @meta[:fields]
1130
+ fields = fields - static.meta[:fields] if dynamic?
1131
+
1132
+ decorate(fields)
1133
+ fields.each do |name|
1134
+ type_info = get_type_info(name)
1135
+ proc = ListRendererPostProcessors[type_info[:type]]
1136
+ proc.(self, name, type_info) if proc
1137
+ end
1138
+ end
1139
+
1140
+ super
1141
+ end
1142
+ end
1143
+
1144
+ module ActionDeleteSupport
1145
+ include ActionModelSupport
1146
+
1147
+ def self.included action
1148
+ action.http_method :delete
1149
+ action.action_type :delete
1150
+ end
1151
+
1152
+ def pre_run
1153
+ super
1154
+ execute "action.errors || [action.parent().invoke(), action.panel_close()]"
1155
+ node.parent.parent.*.menu(:item_menu).option :confirm_delete, icon: "trash", show: "action.selected_size() == 0", button_loc: false
1156
+ end
1157
+ end
1158
+
1159
+ module ActionBulkDeleteSupport
1160
+ include ActionModelSupport
1161
+
1162
+ def self.included action
1163
+ action.http_method :delete
1164
+ action.action_type :bulk_delete
1165
+ end
1166
+
1167
+ def pre_run
1168
+ super
1169
+ execute "action.errors || [action.parent().invoke(), action.panel_close()]"
1170
+ node.parent.parent.*.select_toggle_menu
1171
+ node.parent.parent.*.menu(:menu).option_after :default_order, :confirm_bulk_delete, icon: "trash", show: "action.selected_size() > 0"
1172
+ end
1173
+ end
1174
+
1175
+ (FormRendererPostProcessors ||= {}).merge!(
1176
+ boolean: lambda{|action, field, info|
1177
+ action.info(field)[:render].merge! true_value: info[:true_value], false_value: info[:false_value]
1178
+ action.info(field)[:dont_strip] = info[:dont_strip] if info[:dont_strip]
1179
+ },
1180
+ date: lambda{|action, field, info|
1181
+ action.info(field)[:render].merge! format: info[:format], model_format: info[:model_format]
1182
+ if date_to = info[:other_date]
1183
+ action.info(field)[:render].merge! other_date: date_to #, format: info[:format], model_format: info[:model_format]
1184
+ action.hide_fields date_to
1185
+ elsif time = info[:other_time]
1186
+ action.info(field)[:render].merge! other_time: time
1187
+ action.hide_fields time
1188
+ end
1189
+ },
1190
+ time: lambda{|action, field, info|
1191
+ action.info(field)[:render].merge! format: info[:format], model_format: info[:model_format]
1192
+ },
1193
+ decimal_date: lambda{|action, field, info|
1194
+ FormRendererPostProcessors[:date].(action, field, info)
1195
+ action.info! field, type: :decimal_date
1196
+ },
1197
+ decimal_time: lambda{|action, field, info|
1198
+ FormRendererPostProcessors[:time].(action, field, info)
1199
+ action.info! field, type: :decimal_time
1200
+ },
1201
+ datetime: lambda{|action, field, info|
1202
+ 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]
1203
+ },
1204
+ currency: lambda{|action, field, info|
1205
+ action.info(field)[:render].merge! symbol: info[:symbol]
1206
+ },
1207
+ # date_range: lambda{|action, field, info|
1208
+ # action.info[field][:render].merge! other_date: info[:other_date], format: info[:format], model_format: info[:model_format]
1209
+ # action.hide_fields info[:other_date]
1210
+ # action.info[field][:decimal_date] = true if info[:validations][:decimal_date]
1211
+ # },
1212
+ list_select: lambda{|action, field, info|
1213
+ action.info(field)[:render].merge! list: info[:list]
1214
+ },
1215
+ many_to_one: lambda{|action, field, info|
1216
+ field_info = action.info(field)
1217
+ field_info[:assoc] = :"#{info[:assoc_name]}!"
1218
+ field_info[:fields] = info[:keys]
1219
+ field_info[:type] = info[:otype]
1220
+
1221
+ (info[:keys] - [field]).each do |of|
1222
+ f_info = action.info(of)
1223
+ f_info[:hidden] = true
1224
+ f_info[:type] = action.assets[:model].type_info[of].fetch(:otype)
1225
+ end
1226
+ },
1227
+ file_store: lambda{|action, field, info|
1228
+ action.info(field)[:render].merge! multiple: info[:multiple]
1229
+ },
1230
+ star_to_many_field: lambda{|action, field, info|
1231
+ field_info = action.info(field)
1232
+ field_info[:assoc] = :"#{info[:assoc_name]}!"
1233
+ }
1234
+ )
1235
+
1236
+ (ListRendererPostProcessors ||= {}).merge!(
1237
+ boolean: lambda{|action, field, info|
1238
+ action.info! field, type: :boolean # move to action ?
1239
+ action.info(field)[:render] ||= {}
1240
+ action.info(field)[:render].merge! true_value: info[:true_value], false_value: info[:false_value]
1241
+ },
1242
+ list_select: lambda{|action, field, info|
1243
+ action.info! field, type: :list_select
1244
+ action.info(field)[:render] ||= {}
1245
+ action.info(field)[:render].merge! list: info[:list]
1246
+ },
1247
+ datetime: lambda{|action, field, info|
1248
+ action.info! field, type: :datetime
1249
+ },
1250
+ decimal_date: lambda{|action, field, info|
1251
+ action.info! field, type: :decimal_date
1252
+ },
1253
+ decimal_time: lambda{|action, field, info|
1254
+ action.info! field, type: :decimal_time
1255
+ },
1256
+ # date_range: lambda{|action, field, info|
1257
+ # action.info[field][:type] = :decimal_date if info[:validations][:decimal_date] # ? :decimal_date : :date
1258
+ # }
1259
+ )
1260
+
1261
+ (SearchRendererPostProcessors ||= {}).merge!(
1262
+ many_to_one: lambda{|action, field, info|
1263
+ model = action.assets[:model]
1264
+ if model.type_info[field]
1265
+ keys = info[:keys]
1266
+ else
1267
+ action.check_static_action
1268
+ model = model.many_to_one_associations[field.table].associated_class
1269
+ keys = info[:keys].map{|k| model.table_name.q(k)}
1270
+ end
1271
+
1272
+ field_info = action.info(field)
1273
+ field_info[:assoc] = :"#{info[:assoc_name]}!"
1274
+ field_info[:fields] = keys
1275
+ field_info[:type] = info[:otype]
1276
+
1277
+ (keys - [field]).each do |of|
1278
+ f_info = action.info(of)
1279
+ raise E2Error.new("Missing searchable field: '#{of}' in model '#{action.assets[:model]}'") unless f_info
1280
+ f_info[:hidden_search] = true
1281
+ f_info[:type] = model.type_info[of].fetch(:otype)
1282
+ end
1283
+ },
1284
+ date: lambda{|action, field, info|
1285
+ action.info(field)[:render] ||= {}
1286
+ action.info(field)[:render].merge! format: info[:format], model_format: info[:model_format] # Model::DEFAULT_DATE_FORMAT
1287
+ },
1288
+ decimal_date: lambda{|action, field, info|
1289
+ SearchRendererPostProcessors[:date].(action, field, info)
1290
+ }
1291
+ )
1292
+
1293
+ (DefaultFormRenderers ||= {}).merge!(
1294
+ date: lambda{|action, info|
1295
+ info[:other_date] ? Templates.date_range : (info[:other_time] ? Templates.date_time : Templates.date_picker)
1296
+
1297
+ },
1298
+ time: lambda{|action, info| Templates.time_picker},
1299
+ datetime: lambda{|action, info| Templates.datetime_picker},
1300
+ file_store: lambda{|action, info| Templates.file_store},
1301
+ blob: lambda{|action, info| Templates.blob}, # !!!
1302
+ blob_store: lambda{|action, info| Templates.blob},
1303
+ foreign_blob_store: lambda{|action, info| Templates.blob},
1304
+ string: lambda{|action, info| Templates.input_text(info[:length])},
1305
+ text: lambda{|action, info| Templates.text},
1306
+ integer: lambda{|action, info| Templates.integer},
1307
+ decimal: lambda{|action, info| Templates.decimal},
1308
+ decimal_date: lambda{|action, info| DefaultFormRenderers[:date].(action, info)},
1309
+ decimal_time: lambda{|action, info| Templates.time_picker},
1310
+ email: lambda{|action, info| Templates.email(info[:length])},
1311
+ password: lambda{|action, info| Templates.password(info[:length])},
1312
+ # date_range: lambda{|action, info| Templates.date_range},
1313
+ boolean: lambda{|action, info| Templates.checkbox_buttons(optional: !info[:required])},
1314
+ currency: lambda{|action, info| Templates.currency},
1315
+ list_select: lambda{|action, info|
1316
+ length = info[:list].length
1317
+ if length <= 3
1318
+ Templates.list_buttons(optional: !info[:required])
1319
+ elsif length <= 15
1320
+ max_length = info[:list].max_by{|a|a.last.length}.last.length
1321
+ Templates.list_bsselect(max_length, optional: !info[:required])
1322
+ else
1323
+ max_length = info[:list].max_by{|a|a.last.length}.last.length
1324
+ Templates.list_select(max_length, optional: !info[:required])
1325
+ end
1326
+ },
1327
+ star_to_many_field: lambda{|action, info| Templates.scaffold},
1328
+ many_to_one: lambda{|action, info|
1329
+ tmpl_type = info[:decode][:form]
1330
+ case
1331
+ when tmpl_type[:scaffold]; Templates.scaffold_picker
1332
+ when tmpl_type[:list]; Templates.bsselect_picker
1333
+ when tmpl_type[:typeahead];Templates.typeahead_picker
1334
+ else
1335
+ raise E2Error.new("Unknown decode type #{tmpl_type}")
1336
+ end
1337
+ }, # required/opt
1338
+ )
1339
+
1340
+ (DefaultSearchRenderers ||= {}).merge!(
1341
+ date: lambda{|action, info| SearchTemplates.date_range},
1342
+ decimal_date: lambda{|action, info| SearchTemplates.date_range},
1343
+ integer: lambda{|action, info| SearchTemplates.integer_range},
1344
+ string: lambda{|action, info| SearchTemplates.input_text},
1345
+ boolean: lambda{|action, info| SearchTemplates.checkbox_buttons},
1346
+ list_select: lambda{|action, info|
1347
+ length = info[:list].length
1348
+ if length <= 3
1349
+ SearchTemplates.list_buttons
1350
+ elsif length <= 15
1351
+ # max_length = info[:list].max_by{|a|a.last.length}.last.length
1352
+ SearchTemplates.list_bsselect(multiple: info[:multiple])
1353
+ else
1354
+ # max_length = info[:list].max_by{|a|a.last.length}.last.length
1355
+ SearchTemplates.list_select
1356
+ end
1357
+ },
1358
+ many_to_one: lambda{|action, info|
1359
+ tmpl_type = info[:decode][:search]
1360
+ case
1361
+ when tmpl_type[:scaffold]; SearchTemplates.scaffold_picker(multiple: tmpl_type[:multiple])
1362
+ when tmpl_type[:list]; SearchTemplates.bsselect_picker(multiple: tmpl_type[:multiple])
1363
+ when tmpl_type[:typeahead];SearchTemplates.typeahead_picker
1364
+ else
1365
+ raise E2Error.new("Unknown decode type #{tmpl_type}")
1366
+ end
1367
+ }
1368
+ )
1369
+ end