engine2 1.0.4 → 1.0.9

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