engine2 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
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