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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6174073612b4abd1d1a7c2fefbbf071874c2c566
4
- data.tar.gz: 081dee914669d77bb0a9e32d13c219612aa79d75
2
+ SHA256:
3
+ metadata.gz: cc5654aaf1573375a69ca96d3d957b07e1970ef777e0deb65c18f28c40666489
4
+ data.tar.gz: aa79d8a00f564a0e23e4520ea1198446a6049c241b5bdb714416aa7ed378de6d
5
5
  SHA512:
6
- metadata.gz: 5631fd58edca95f2c3966ccc240a29b0089c78e7a80b9d3a7e5e239e2a001e0a02f7baa9ed6cdff6a0d98f061022f02d0768282513b457bf6e9fd180c8adf96e
7
- data.tar.gz: bcc3a19a71996a4c2e8f5f2d511354dc1085de8bdd5eacce667cbe1f24a91587dee04892fb22d51276f7a152a4b21c72681e90f379731ad2f1d546f9c5940294
6
+ metadata.gz: 271c8416bd7551d08fa8574319be948aa9b8658fc431e432c65a19deb456deead3fc92e213259e2f9ad0783a77099a8dbce13c460fe45d715abab7bf09efc392
7
+ data.tar.gz: b65bd605e3d622586f433d5900d9da115e880e62a0827913f03c2e18dc9ce744fb6ebaa88f4a31e3582251c8551be9b71a0f03299498d1042082dd3eeb0f3074
data/Gemfile CHANGED
File without changes
data/Rakefile CHANGED
@@ -1,17 +1,17 @@
1
1
  desc "Compile SLIM"
2
2
  task :compile_slim do
3
3
  require 'slim'
4
- view_dirs = ["fields", "scaffold", "search_fields", "modals"]
4
+ view_dirs = ["fields", "scaffold", "search_fields", "modals", "panels"]
5
5
  slims = view_dirs.each.map do |view_dir|
6
6
  Dir["views/#{view_dir}/*.slim"].map do |slim_file|
7
7
  slim = Slim::Template.new(slim_file).render.gsub('"', '\"')
8
8
  tpl_name = slim_file.sub("views/", "").sub(".slim", "")
9
- "$templateCache.put('#{tpl_name}', \"#{slim}\");"
9
+ "c.put('#{tpl_name}', \"#{slim}\");"
10
10
  end
11
11
  end
12
12
 
13
- open("app/engine2templates.js", "wb") << <<-EOF
14
- angular.module('Engine2').run(['$templateCache', function($templateCache) {
13
+ open("app/templates.js", "wb") << <<-EOF
14
+ angular.module('Engine2').run(['$templateCache', function(c) {
15
15
  #{slims.join("\n")}
16
16
  }]);
17
17
  EOF
@@ -8,7 +8,7 @@ angular.module('Engine2')
8
8
  throw "Invalid action path: '#{action_attr}'" unless action_names
9
9
  action_names = action_names.split('/') if _.isString(action_names)
10
10
  create = (action) ->
11
- action.create_action_path(action_names, $scope, $element).then (act) -> act.invoke() if $attrs.invoke
11
+ action.create_action_path(action_names, $scope, $element).then (act) -> act.invoke($parse($attrs.invoke)($scope)) if $attrs.invoke?
12
12
 
13
13
  sc = $scope
14
14
  if sc.action
@@ -22,13 +22,13 @@ angular.module('Engine2')
22
22
  $http.get("api/meta").then (mresponse) -> $scope.$broadcast "bootstrap_action",
23
23
  $scope.action = new E2Actions.root(mresponse.data, $scope, null, $element, action_resource: 'api')
24
24
 
25
- .factory 'E2Actions', (E2, $http, $timeout, $e2Modal, $injector, $compile, $templateCache, $q, localStorageService, $route, $window, $rootScope, $location) ->
25
+ .factory 'E2Actions', (E2, $http, $timeout, $injector, $compile, $templateCache, $q, localStorageService, $rootScope, $location, angularLoad, $websocket, MetaCache, $stateRegistry, $urlRouter) ->
26
26
  globals = E2.globals
27
27
  action: class Action
28
28
  constructor: (response, scope, parent, element, action_info) ->
29
29
  @find_action_info = (name, raise = true) ->
30
30
  act = response.actions[name]
31
- throw "Undefined action '#{name}' for action #{@action_info().name} (under #{@parent()?.action_info().action_resource})" if raise && !act
31
+ throw "Undefined action '#{name}' for action #{@parent()?.action_info().action_resource}/#{@action_info().name}" if raise && !act
32
32
  act
33
33
 
34
34
  _.each response.actions, (act, nm) -> act.name = nm
@@ -51,11 +51,19 @@ angular.module('Engine2')
51
51
  @meta.panel.modal_action = false
52
52
  @meta.panel.footer = true unless @meta.panel.footer == false
53
53
 
54
+
55
+ if scope && @meta.invokable != false
56
+ scope.$on action_info.action_resource, (e, args) => @invoke(args)
57
+
58
+ @websocket_connect() if @meta.websocket
54
59
  @initialize()
55
60
 
61
+ broadcast: (sub_action, args) ->
62
+ @scope().$broadcast(@action_info().action_resource + '/' + sub_action, args)
63
+
56
64
  initialize: ->
57
65
  @process_static_meta()
58
- console.log "CREATE #{@action_info().action_resource}"
66
+ console.info "CREATE #{@action_info().action_resource}"
59
67
 
60
68
  process_static_meta: ->
61
69
  if @meta.menus
@@ -67,45 +75,28 @@ angular.module('Engine2')
67
75
  if action_info.access
68
76
  $rootScope.$broadcast "relogin", element?, create
69
77
  else
70
- $e2Modal.error("#{err.status}: #{err.data.message}", err.data.cause || err.data.message)
71
- $q.reject(err)
72
-
73
- perform_invoke: (params) ->
74
- info = @action_info()
75
- get_invoke = if @meta.invokable == false then $q.when(data: (response: {})) else
76
- (params ?= {}).initial = true if @meta.panel && !@action_invoked && info.method == 'get'
77
- $http[info.method](info.action_resource, if info.method == 'post' then params else (params: params))
78
+ tle = "#{err.status}: #{err.data.message}"
79
+ msg = err.data.cause || err.data.message
80
+ if msg.length > 500
81
+ @globals().modal().error(tle, msg)
82
+ else
83
+ @globals().toastr().error(tle, msg, extendedTimeOut: 5000, closeButton: true)
78
84
 
79
- globals.action_pending = if @meta.panel then @ else @parent()
85
+ $q.reject(err)
80
86
 
81
- get_invoke.then (response) =>
82
- if exec = response.data.$execute
83
- @scope().$eval(exec)
84
- delete response.data.$execute
87
+ save_state: () ->
88
+ _.each @meta.state, (s) => localStorageService.set("#{@globals().application}/#{@action_info().action_resource}/#{s}", @[s])
89
+ load_state: () ->
90
+ _.each @meta.state, (s) => _.merge(@[s], localStorageService.get("#{@globals().application}/#{@action_info().action_resource}/#{s}"))
85
91
 
86
- E2.merge(@, response.data)
87
- @process_meta()
88
- (if @meta.reload_routes then $route.load_routes() else $q.when({})).then =>
89
- @arguments = _.keys(response.data)
90
- unless @meta.panel # persistent action
91
- prnt = @parent()
92
- throw "Attempted parent merge for root action: #{info.name}" unless prnt
93
- E2.merge(prnt, response.data)
94
-
95
- globals.action_pending = false
96
- if @meta.panel && !@action_invoked
97
- @action_invoked = true
98
- @panel_render()
99
- ,
100
- (err) =>
101
- globals.action_pending = false
102
- @handle_error(err, info, @element())
92
+ destroy: (e) ->
93
+ console.log "DESTROY #{@action_info().action_resource}"
103
94
 
104
95
  create_action: (name, sc, el) ->
105
96
  info = @find_action_info(name)
106
97
  info.action_resource = "#{@action_info().action_resource}/#{info.name}"
107
98
  get_meta = if !info.terminal || info.meta
108
- $http.get("#{info.action_resource}/meta", cache: true).then (response) =>
99
+ $http.get("#{info.action_resource}/meta", cache: MetaCache).then (response) =>
109
100
  if info.recheck_access
110
101
  $http.get("#{info.action_resource}/meta", params: (access: true, parent_id: @current_id())).then (aresponse) ->
111
102
  response.data.actions[k].access = v for k, v of aresponse.data
@@ -113,7 +104,7 @@ angular.module('Engine2')
113
104
  else response # $q.when ^
114
105
  else $q.when(data: (meta: {}, actions: []))
115
106
  E2A = $injector.get("E2Actions")
116
- get_meta.then (mresponse) => new (E2A[info.meta_type] ? E2A.default_action)(mresponse.data, sc, @, el, info)
107
+ get_meta.then (mresponse) => new (E2A[info.action_type] ? E2A.default_action)(mresponse.data, sc, @, el, info)
117
108
  ,
118
109
  (err) => @handle_error(err, info, el)
119
110
 
@@ -122,34 +113,61 @@ angular.module('Engine2')
122
113
 
123
114
  create_action_path: (action_names, sc, elem) ->
124
115
  last_name = action_names.pop()
125
- _.reduce(action_names, ((pr, nm) -> pr.then (act) -> act.create_action(nm)), $q.when(@)).then (act) ->
116
+ _.reduce(action_names, ((pr, nm) -> pr.then (act) -> act.create_action(nm)), $q.when(@)).then (act) -> # self = @
126
117
  act.create_action(last_name, sc, elem).then (act) -> sc.action = act
127
118
 
128
119
  globals: -> globals
120
+ _ : -> _
121
+ current: -> @globals().current_action
129
122
  action_pending: -> globals.action_pending == @
130
123
  pre_invoke: ->
131
124
  post_invoke: ->
132
125
 
133
- invoke: (args) ->
134
- @pre_invoke(args)
135
- _.merge(args ?= {}, @meta.arguments) if @meta.arguments
136
- @perform_invoke(args).then =>
137
- @post_invoke(args)
138
- @scope().$eval(@meta.execute) if @meta.execute
139
- if @meta.repeat
140
- unless @meta.destroy_repeat
141
- @scope().$on("$destroy", => delete @meta.repeat)
142
- @meta.destroy_repeat = true
143
- $timeout (=> @invoke(args)), @meta.repeat
144
- @
126
+ invoke: (params) ->
127
+ @globals().current_action = @
128
+ params ?= {}
129
+ @globals().action_pending = if @meta.panel then @ else @parent()
130
+ @pre_invoke(params)
131
+ if @meta.arguments # _.merge(params, @meta.arguments)
132
+ _.each @meta.arguments, (v, k) =>
133
+ if _.endsWith(k, '!') then params[k.slice(0, -1)] = @scope().$eval(v) else params[k] = v
145
134
 
146
- save_state: () ->
147
- _.each @meta.state, (s) => localStorageService.set("#{globals.application}/#{@action_info().action_resource}/#{s}", @[s])
148
- load_state: () ->
149
- _.each @meta.state, (s) => _.merge(@[s], localStorageService.get("#{globals.application}/#{@action_info().action_resource}/#{s}"))
150
135
 
151
- destroy: (e) ->
152
- console.log "DESTROY #{@action_info().action_resource}"
136
+ info = @action_info()
137
+ get_invoke = if @meta.invokable == false then $q.when(data: (response: {})) else
138
+ params.initial = true if @meta.panel && !@action_invoked && info.method == 'get'
139
+ $http[info.method](info.action_resource, if info.method == 'post' then params else (params: params))
140
+
141
+ @execute_commands('pre_execute')
142
+ get_invoke.then (response) =>
143
+ @arguments = _.keys(response.data)
144
+ E2.merge_meta(@, response.data)
145
+ @process_meta()
146
+
147
+ promise = if @meta.panel # persistent action
148
+ if !@action_invoked
149
+ @action_invoked = true
150
+ @panel_render()
151
+ else
152
+ prnt = @parent()
153
+ throw "Attempted parent merge for root action: #{info.name}" unless prnt
154
+ E2.merge_meta(prnt, response.data)
155
+
156
+ @post_invoke(params)
157
+ @execute_commands('execute')
158
+ if @meta.repeat
159
+ @scope().$on "$destroy", => @destroyed = true
160
+ $timeout (=> @invoke(params)), @meta.repeat unless @destroyed
161
+ delete @meta.repeat
162
+
163
+ @globals().action_pending = false
164
+ @globals().current_action = null
165
+ promise
166
+ ,
167
+ (err) =>
168
+ @globals().action_pending = false
169
+ @globals().current_action = null
170
+ @handle_error(err, info, @element())
153
171
 
154
172
  panel_render: ->
155
173
  if @meta.panel.modal_action
@@ -161,13 +179,13 @@ angular.module('Engine2')
161
179
  @panel_shown?()
162
180
 
163
181
  else
164
- $e2Modal.show(@)
182
+ @globals().modal().show(@).then => @
165
183
  else
166
184
  @panel_scope?().$destroy()
167
185
  act = @
168
186
  act = act.parent() until act.element()
169
187
  element = act.element() # @element()
170
- is_modal = $e2Modal.is_modal() && !@element()
188
+ is_modal = @globals().modal().is_modal() && !@element()
171
189
  E2.fetch_panel(@meta.panel, is_modal).then (template) =>
172
190
  @panel_show?()
173
191
  # @panel_scope().$destroy()
@@ -190,7 +208,7 @@ angular.module('Engine2')
190
208
  panel_close: ->
191
209
  if @meta.panel.modal_action
192
210
  @modal_hide()
193
- else
211
+ else if @parent().parent()
194
212
  # @parent().panel_refresh()
195
213
  @panel_hide?()
196
214
  @panel_hidden()
@@ -201,12 +219,47 @@ angular.module('Engine2')
201
219
  panel_menu_cancel: ->
202
220
  @panel_close()
203
221
 
222
+ panel_menu_close: ->
223
+ @panel_close()
224
+
225
+ websocket_connect: ->
226
+ l = $location
227
+ ws_meta = @meta.websocket
228
+ ws = $websocket "ws#{l.protocol().slice(4, 5)}://#{l.host()}:#{l.port()}#{'/'}#{@action_info().action_resource}", undefined, ws_meta.options
229
+ _.each @globals().ws_methods, (method) =>
230
+ ws_method_impl = @["ws_#{method}"]
231
+ ws["on#{_.capitalize(method)}"] (evt) =>
232
+ if method == 'message'
233
+ msg = JSON.parse(evt.data)
234
+ if msg.error then @globals().modal().error("WebSocket [#{evt.origin}] - #{msg.error.method}", msg.error.exception) else
235
+ E2.merge_meta(@, msg)
236
+ @process_meta()
237
+ else msg = evt
238
+ ws_method_impl.bind(@)(msg, ws, evt) if ws_method_impl
239
+ @execute_commands('execute')
240
+
241
+ @web_socket = -> ws
242
+ @scope().$on "$destroy", -> ws.close()
243
+
244
+ execute_commands: (execute) ->
245
+ if @meta[execute]
246
+ scope = @scope()
247
+ _.reduce(@meta[execute], ((pr, cmd) -> pr.then -> scope.$eval(cmd)), $q.when())
248
+ @meta[execute].splice(0, @meta[execute].length)
249
+ delete @meta[execute]
250
+
251
+ console_log: (o) ->
252
+ console.log o
253
+
204
254
  root: class RootAction extends Action
205
255
  initialize: ->
206
256
  super()
207
257
  _.merge(globals, @meta)
208
258
  @meta = {}
209
259
 
260
+ invoke: (args) ->
261
+ console.warn "Root action invoked"
262
+
210
263
  default_action: class DefaultAction extends Action
211
264
  initialize: ->
212
265
  super()
@@ -217,6 +270,8 @@ angular.module('Engine2')
217
270
  super()
218
271
  @tree = actions: [name: 'api', number: 0, access: true]
219
272
  @invoke_action('models')
273
+ @invoke_action('environment')
274
+ @local_storage = localStorageService
220
275
 
221
276
  open: (stack, node, collapsed, expand) ->
222
277
  tree = @tree
@@ -244,7 +299,10 @@ angular.module('Engine2')
244
299
  _.each response.data.actions, (act, nm) -> act.name = nm
245
300
  tree.actions ?= _.toArray(response.data.actions)
246
301
  @meta_json = response.data.meta
247
- @action_state = if @meta_json.state then _.zipObject(@meta_json.state.map (k) -> [k, localStorageService.get("#{path}/#{k}")]) else {}
302
+ if @meta_json.state
303
+ @action_state = {}
304
+ _.each @meta_json.state, (s) => @action_state[s] = localStorageService.get("#{@globals().application}/#{path.join('/')}/#{s}")
305
+ @action_state
248
306
  ,
249
307
  (err) =>
250
308
  delete @meta_json
@@ -253,65 +311,69 @@ angular.module('Engine2')
253
311
  has_assoc: (model) ->
254
312
  _.size(model.assoc) > 0
255
313
 
314
+ ws_message: (msg, ws, evt) ->
315
+ @event = evt
316
+
256
317
  menu: class MenuAction extends Action
257
318
  process_static_meta: ->
258
319
 
259
320
  initialize: ->
260
321
  super()
261
- $route.load_routes = =>
322
+ $stateRegistry.load_routes = (init) =>
262
323
  @invoke().then =>
263
- _.each _.keys($route.routes), (k) -> delete $route.routes[k]
264
324
  menu = @meta.menus.menu
265
- $route.routes[null] = reloadOnSearch: true, redirectTo: '/' + (menu.properties.default ? menu.entries[0].name)
325
+ _.each $stateRegistry.get(), (s) -> $stateRegistry.deregister(s.name) unless _.isEmpty(s.name)
326
+ otherwise = menu.properties.default ? menu.entries[0].name
327
+ $urlRouter.otherwise(otherwise)
266
328
  @register(menu.entries)
267
- $route.reload() # $location.path('')
268
329
  @scope().routes = menu.entries
269
- out = if _.size(menu.entries) == 0 then angular.element("<div></div>") else $compile(@traverse(menu.entries))(@scope())
330
+ out = $compile(@traverse(menu.entries))(@scope())
270
331
  @element().replaceWith(out)
271
332
  @element = -> out
272
- $route.load_routes()
333
+ loc = $location.path().slice(1)
334
+ @globals().state().go(if init && !_.isEmpty($location.path()) && $stateRegistry.get(loc)? then loc else otherwise)
335
+
336
+ $stateRegistry.load_routes(true)
273
337
 
274
338
  register: (routes) ->
275
339
  _.each routes, (route) =>
276
- if route.menu then @register(route.menu.entries) else
277
- name = '/' + route.name
278
- route.href = '#' + route.name
279
- if route.bootstrap?
280
- action = if route.bootstrap == true then '' else route.bootstrap + '/'
281
- $templateCache.put(route.name + '_route_template!', "<div e2-action='' action=\"'#{action}#{route.name}'\" invoke='true'></div>")
282
-
283
- $route.routes[name] =
284
- reloadOnSearch: true
285
- templateUrl: if route.bootstrap? then route.name + '_route_template!' else route.name
286
- originalPath: name
287
- regexp: new RegExp("^#{name}$")
288
- keys: []
289
-
290
- $route.routes[name + '/'] =
291
- redirectTo: name
292
- originalPath: name + '/'
293
- regexp: new RegExp("^#{name}/$")
294
- keys: []
340
+ unless route.divider
341
+ if route.menu then @register(route.menu.entries) else
342
+ route.href = route.name
343
+ if route.bootstrap?
344
+ action = if route.bootstrap == true then '' else route.bootstrap + '/'
345
+ $templateCache.put(route.name + '_route_template!', "<div e2-action='' action=\"'#{action}#{route.name}'\" invoke='true'></div>")
346
+
347
+ $stateRegistry.register
348
+ name: route.name
349
+ templateUrl: if route.bootstrap? then route.name + '_route_template!' else route.name
350
+ url: '/' + route.name
351
+ # reloadOnSearch: true
295
352
 
296
353
  traverse: (routes) ->
297
- menu_tmpl = _.template("<li><a href='{{href}}'>{{icon}}{{aicon}} {{loc}}</a></li>")
298
- menu_sub_tmpl = _.template("<li e2-dropdown='{{dropdown}}' data-animation='{{animation}}'><a href='javascript://'>{{icon}}{{aicon}} {{loc}}<span class='caret'></span></a></li>")
354
+ menu_tmpl = _.template("<li {{show}} {{hide}} ui-sref-active='active'><a {{href}}>{{icon}} {{loc}}</a></li>")
355
+ menu_sub_tmpl = _.template("<li {{show}} {{hide}} e2-dropdown='{{dropdown}}' nav='true' data-animation='{{animation}}'><a href='javascript://'>{{icon}} {{loc}}<span class='caret'></span></a></li>")
299
356
  animation = @meta.menus.menu.properties.animation
300
357
  out = routes.map (route, i) ->
301
- if route.menu
358
+ if route.render == false
359
+ ''
360
+ else if route.menu
302
361
  menu_sub_tmpl
303
362
  dropdown: "routes[#{i}].menu.entries"
304
363
  animation: animation
305
- icon: route.menu.icon && E2.icon(route.menu.icon) || ""
306
- aicon: route.menu.aicon && E2.aicon(route.menu.aicon) || ""
307
364
  loc: route.menu.loc
365
+ show: route.show && "ng-show=\"#{route.show}\"" || ''
366
+ hide: route.hide && "ng-hide=\"#{route.hide}\"" || ''
367
+ icon: route.menu.icon && E2.icon(route.menu.icon) || ""
308
368
  else
309
369
  menu_tmpl
310
- href: route.href
370
+ href: "ui-sref='#{route.name}'"
311
371
  loc: route.loc
372
+ show: route.show && "ng-show=\"#{route.show}\"" || ''
373
+ hide: route.hide && "ng-hide=\"#{route.hide}\"" || ''
312
374
  icon: route.icon && E2.icon(route.icon) || ''
313
- aicon: route.aicon && E2.aicon(route.aicon) || ''
314
- out.join('')
375
+ out = out.join('')
376
+ if _.size(out) == 0 then "<div></div>" else out
315
377
 
316
378
  list: class ListAction extends Action
317
379
  initialize: ->
@@ -320,9 +382,8 @@ angular.module('Engine2')
320
382
  @ui_state = {}
321
383
  @load_state()
322
384
 
323
- delete @query.order unless @meta.info[@query.order]?.sort # _.includes(@meta.fields, @query.order)
324
- _.each @query.search, ((sv, sn) => delete @query.search[sn] unless _.includes(@meta.search_fields, sn))
325
- # $window.addEventListener 'beforeunload', (e, v) => @save_state()
385
+ delete @query.order unless @meta.fields[@query.order]?.sort # _.includes(@meta.field_list, @query.order)
386
+ _.each @query.search, ((sv, sn) => delete @query.search[sn] unless _.includes(@meta.search_field_list, sn))
326
387
 
327
388
  destroy: ->
328
389
  @save_state()
@@ -331,10 +392,13 @@ angular.module('Engine2')
331
392
  process_meta: ->
332
393
  super()
333
394
  meta = @meta
334
- meta.fields = meta.fields.filter((f) => !meta.info[f].hidden) if meta.fields
395
+ meta.field_list = meta.field_list.filter((f) => !meta.fields[f].hidden) if meta.field_list
335
396
 
336
397
  # confirm_create, view, confirm_modify, confirm_delete, assocs - implicit
337
398
 
399
+ render_table: ->
400
+ @scope().$broadcast 'render_table'
401
+
338
402
  menu_search_toggle: ->
339
403
  @ui_state.search_active = !@ui_state.search_active
340
404
  @save_state() unless @ui_state.search_active
@@ -348,12 +412,13 @@ angular.module('Engine2')
348
412
 
349
413
  menu_select_toggle: ->
350
414
  if @selection then delete @selection else @selection = {}
351
- @scope().$broadcast 'render_table'
415
+ @render_table()
416
+
352
417
 
353
418
  menu_show_meta: ->
354
- $e2Modal.show
419
+ @globals().modal().show
355
420
  the_meta: @meta
356
- meta: panel: (panel_template: "close_m", template_string: "<pre>{{action.the_meta | json}}</pre>", title: "Meta", class: "modal-huge", backdrop: true, footer: true)
421
+ meta: panel: (panel_template: "close_m", template_string: "<pre>{{action.the_meta | yaml}}</pre>", title: "Meta", class: "modal-huge", backdrop: true, footer: true)
357
422
 
358
423
  # show_assoc: (index, assoc) ->
359
424
  # # parent_id = E2.id_for(@entries[index], @meta)
@@ -370,7 +435,7 @@ angular.module('Engine2')
370
435
  E2.id_for(@current_entry(), @meta)
371
436
 
372
437
  list_cell: (e, f) ->
373
- E2.render_field(e, f, @meta)
438
+ E2.render_field(e, f, @meta, "<br>")
374
439
 
375
440
  invoke: (args = {}) ->
376
441
  @save_state()
@@ -380,7 +445,7 @@ angular.module('Engine2')
380
445
  super(query).then =>
381
446
  @ui = _.pick @query, ['order', 'asc', 'page']
382
447
  @ui.pagination_active = @ui.page != 0 || @entries.length >= @meta.config.per_page
383
- @scope().$broadcast 'render_table'
448
+ @render_table()
384
449
 
385
450
  load_new: ->
386
451
  @query.page = 0
@@ -413,14 +478,14 @@ angular.module('Engine2')
413
478
  @load_new()
414
479
 
415
480
  search_field_change: (f) ->
416
- info = @meta.info[f]
481
+ info = @meta.fields[f]
417
482
 
418
- @scope().$eval(info.onchange) if info.onchange
483
+ @scope().$eval(info.onchange.action) if info.onchange
419
484
 
420
485
  if remote_onchange = info.remote_onchange
421
486
  params = value: @query.search[f]
422
- params.record = @query.search if info.remote_onchange_record
423
- @invoke_action(remote_onchange, params).then =>
487
+ params.record = @query.search if remote_onchange.record
488
+ @invoke_action(remote_onchange.action, params).then =>
424
489
  @load_new() if info.search_live
425
490
  else
426
491
  @load_new() if info.search_live
@@ -441,6 +506,22 @@ angular.module('Engine2')
441
506
  selected_info: ->
442
507
  @meta.loc.selected + ": " + @selected_size()
443
508
 
509
+ entry_dropped: (moved_to, render = true) ->
510
+ from = @entries[@moved_from]
511
+ @entries.splice(@moved_from, 1)
512
+ @entries.splice((if moved_to > @moved_from then moved_to - 1 else moved_to), 0, from)
513
+ delete @moved_from
514
+ @render_table() if render
515
+ true
516
+
517
+ entry_moved: (index) ->
518
+ @moved_from = index
519
+
520
+ list_parent_action: ->
521
+ parent = @parent()
522
+ parent = parent.parent() until parent instanceof ListAction
523
+ parent
524
+
444
525
  bulk_delete: class BulkDeleteAction extends Action
445
526
  invoke: ->
446
527
  super(ids: [_.keys(@parent().parent().selection)]).then =>
@@ -448,79 +529,81 @@ angular.module('Engine2')
448
529
 
449
530
  view: class ViewAction extends Action
450
531
  view_cell: (e, f) ->
451
- E2.render_field(e, f, @meta)
532
+ E2.render_field(e, f, @meta, "<br>")
452
533
 
453
534
  form_base_action: class FormBaseAction extends Action
454
535
  initialize: ->
455
536
  super()
456
- _.each @meta.info, (info, name) =>
457
- if info.remote_onchange
458
- @scope().$watch (=> @record?[name]), (n) => if n? #if typeof(n) != "undefined"
459
- params = value: @record[name]
460
- params.record = @record if info.remote_onchange_record
461
- @invoke_action(info.remote_onchange, params)
462
-
463
- if info.onchange
464
- @scope().$watch (=> @record?[name]), (n) => if n?
465
- @scope().$eval(info.onchange)
466
-
467
- if @meta.tabs
537
+ if @meta.tab_list
468
538
  @scope().$watch "action.activeTab", (tab) => if tab? # && tab >= 0
469
539
  @panel_shown()
470
540
 
471
541
  @["panel_menu_#{@default_action_name}"] = -> @panel_menu_default_action()
472
- @scope().$on "return_pressed", (e) => @panel_menu_default_action()
473
542
 
474
543
  post_invoke: (args) ->
475
544
  super()
476
- _.each @meta.info, (info, name) =>
477
- if _.isString(@record[name]) && !info.dont_strip
545
+ _.each @meta.fields, (info, name) =>
546
+ if @record[name] is undefined
547
+ @record[name] = null
548
+ else if _.isString(@record[name]) && !info.dont_strip
478
549
  @record[name] = @record[name].trim()
479
550
 
551
+ if info.onchange || info.remote_onchange
552
+ onchange = => @scope().$eval(info.onchange.action)
553
+ remote_onchange = =>
554
+ params = value: @record[name]
555
+ params.record = @record if info.remote_onchange.record
556
+ @invoke_action(info.remote_onchange.action, params)
557
+
558
+ @scope().$watch (=> @record[name]), (n, o) => if n != o
559
+ onchange() if info.onchange
560
+ remote_onchange() if info.remote_onchange
561
+
562
+ onchange() if info.onchange?.trigger_on_start
563
+ remote_onchange() if info.remote_onchange?.trigger_on_start
564
+
480
565
  panel_menu_default_action: ->
481
- _.each @meta.info, (v, n) =>
482
- @record[n] = null if @record[n] is undefined
483
566
  params = record: @record
484
567
  params.parent_id ?= @parent().query?.parent_id # and StarToManyList ?
485
568
  @invoke_action(@default_action_name, params).then =>
486
569
  dfd = $q.defer()
487
570
  if @errors
488
- if @meta.tabs
571
+ if @meta.tab_list
489
572
  [i, first, curr] = [0, null, false]
490
- for tab in @meta.tabs
491
- if _(tab.fields).find((f) => @errors[f])
573
+ for tab_name in @meta.tab_list
574
+ tab = @meta.tabs[tab_name]
575
+ if _(tab.field_list).find((f) => @errors[f])
492
576
  first = i if not first?
493
577
  act = true if @activeTab == i
494
578
  i++
495
579
  @activeTab = first unless act
496
580
 
497
581
  if @activeTab?
498
- field = _(@meta.tabs[@activeTab].fields).find((f) => @errors[f])
582
+ field = _(@meta.tabs[@meta.tab_list[@activeTab]].field_list).find((f) => @errors[f])
499
583
  # console.log field undefined ?
500
584
  else
501
585
  @activeTab = 0
502
586
  @alert = @errors
503
587
  else
504
- field = _(@meta.fields).find((f) => @errors[f])
505
- @alert = @errors if (!field || !@meta.info[field] || @meta.info[field].hidden) # ?
588
+ field = _(@meta.field_list).find((f) => @errors[f])
589
+ @alert = @errors if (!field || !@meta.fields[field] || @meta.fields[field].hidden) # ?
506
590
  $timeout => @scope().$broadcast("focus_field", field)
507
591
  #e.scope.$eval(meta.execute) if meta.execute # ?
508
- dfd.reject(@errors)
592
+ dfd.resolve()
509
593
  else
510
- @panel_close()
511
594
  dfd.resolve(@record) # $q.when(true) ?
512
595
  dfd.promise
513
596
 
514
597
  panel_shown: ->
515
- field = if @meta.tabs
516
- tab = @meta.tabs[@activeTab]
598
+ field = if @meta.tab_list
599
+ tab = @meta.tabs[@meta.tab_list[@activeTab]]
517
600
  if @errors
518
- _(tab.fields).find((f) => @errors[f]) || _(tab.fields).find((f) => !@meta.info[f].hidden)
601
+ _(tab.field_list).find((f) => @errors[f]) || _(tab.field_list).find((f) => !@meta.fields[f].hidden)
519
602
  else
520
- tab ?= @meta.tabs[0]
521
- _(tab.fields).find((f) => !@meta.info[f].hidden && !@meta.info[f].disabled)
603
+ tab ?= @meta.tabs[@meta.tab_list[0]]
604
+ _(tab.field_list).find((f) => !@meta.fields[f].hidden && !@meta.fields[f].disabled)
522
605
  else
523
- _(@meta.fields).find((f) => !@meta.info[f].hidden && !@meta.info[f].disabled)
606
+ _(@meta.field_list).find((f) => !@meta.fields[f].hidden && !@meta.fields[f].disabled)
524
607
  $timeout (=> @scope().$broadcast("focus_field", field)), 300 # hack, on shown ?
525
608
 
526
609
  infra: class InfraAction extends Action
@@ -530,33 +613,34 @@ angular.module('Engine2')
530
613
  if @user
531
614
  @invoke_action('login_form').then (act) =>
532
615
  act.record = name: @user.name
533
- act.meta.info.name.disabled = true
616
+ act.meta.fields.name.disabled = true
534
617
  act.dont_reload_routes = !reload_routes # true
535
618
  else
536
- @invoke().then => @set_access(true, true)
619
+ @invoke().then => @set_access(true, true, @user)
537
620
 
538
- set_access: (login, load_routes) ->
539
- @find_action_info('logout_form').access = login
540
- @find_action_info('inspect_modal').access = login
541
- @find_action_info('login_form').access = !login
542
- $route.load_routes() if load_routes
621
+ @scope().$on "set_access", (evt, login, load_routes, user) => @set_access(login, load_routes, user)
622
+
623
+ set_access: (login, load_routes, user) ->
624
+ if user || !login
625
+ @user = user
626
+ @find_action_info('logout_form').access = login
627
+ @find_action_info('inspect_modal').access = login
628
+ @find_action_info('login_form').access = !login
629
+ $stateRegistry.load_routes() if load_routes
543
630
 
544
631
  login_form: class LoginFormAction extends FormBaseAction
545
632
  panel_menu_default_action: ->
546
633
  super().then =>
547
- @parent().user = @user
548
- @parent().set_access(true, !@dont_reload_routes)
634
+ $rootScope.$broadcast "set_access", true, !@dont_reload_routes, @user
549
635
 
550
636
  logout_form: class LogoutForm extends Action
551
637
  panel_menu_logout: ->
552
638
  @invoke_action('logout').then =>
553
- @parent().user = null
554
- @parent().set_access(false, true)
639
+ $rootScope.$broadcast "set_access", false, true, null
555
640
  @panel_close()
641
+ MetaCache.removeAll()
556
642
 
557
643
  form: class FormAction extends FormBaseAction
558
- panel_menu_default_action: ->
559
- super().then => @parent().invoke()
560
644
 
561
645
  create: class CreateAction extends FormAction
562
646
  invoke: (args) ->
@@ -568,26 +652,18 @@ angular.module('Engine2')
568
652
  modify: class ModifyAction extends FormAction
569
653
  # invoke: (args) ->
570
654
  # super(args).then =>
571
- # _.each @meta.primary_fields, (f) => @meta.info[f].disabled = true
572
-
573
- on_change: class OnChangeAction extends Action
574
- post_invoke: ->
575
- super()
576
- @parent().scope().$eval(@meta.execute) if @meta.execute
655
+ # _.each @meta.primary_fields, (f) => @meta.fields[f].disabled = true
577
656
 
578
657
  confirm: class ConfirmAction extends Action
579
658
  panel_menu_approve: ->
580
659
  @initial_arguments ?= @arguments
581
- @invoke_action(@default_action_name, _.pick(@, @initial_arguments)).then (act) =>
582
- unless @errors
583
- @parent().invoke()
584
- @panel_close()
660
+ @invoke_action(@default_action_name, _.pick(@, @initial_arguments))
585
661
 
586
662
  decode_action: class DecodeAction extends Action
587
663
  initialize: ->
588
664
  super()
589
665
  @decode_field = @scope().f
590
- @dinfo = @parentp().meta.info[@decode_field]
666
+ @dinfo = @parentp().meta.fields[@decode_field]
591
667
  throw "Primary and foreign key list lengths dont match: [#{@meta.primary_fields}] and [#{@dinfo.fields}]" unless @meta.primary_fields.length == @dinfo.fields.length
592
668
  @scope().$on "search_reset", => @clean()
593
669
 
@@ -606,8 +682,7 @@ angular.module('Engine2')
606
682
  @parentp().search_field_change?(@decode_field)
607
683
 
608
684
  decode_description: (entry) ->
609
- fields = @meta.decode_fields ? @meta.fields
610
- fields.map((f) => E2.render_field(entry, f, @meta)).join(@meta.separator)
685
+ @meta.decode_fields.map((f) => E2.render_field(entry, f, @meta, ', ')).join(@meta.separator)
611
686
 
612
687
  parentp: ->
613
688
  @parent().parent()
@@ -634,11 +709,11 @@ angular.module('Engine2')
634
709
  if @selected.length > 0
635
710
  _.each @dinfo.fields, (fk) -> record[fk] = []
636
711
  _.each @selected, (sel) =>
637
- _(@dinfo.fields).zip(E2.split_keys(sel)).each(([fk, k]) => record[fk].push E2.parse_entry(k, @parentp().meta.info[fk])).value
712
+ _(@dinfo.fields).zip(E2.split_keys(sel)).each(([fk, k]) => record[fk].push E2.parse_entry(k, @parentp().meta.fields[fk])).value
638
713
  else @clear_record()
639
714
  else
640
715
  if @selected
641
- _(@dinfo.fields).zip(E2.split_keys(@selected)).each(([fk, k]) => record[fk] = E2.parse_entry(k, @parentp().meta.info[fk])).value
716
+ _(@dinfo.fields).zip(E2.split_keys(@selected)).each(([fk, k]) => record[fk] = E2.parse_entry(k, @parentp().meta.fields[fk])).value
642
717
  else @clear_record()
643
718
 
644
719
  @parentp().search_field_change?(@decode_field)
@@ -660,12 +735,12 @@ angular.module('Engine2')
660
735
  if @multiple
661
736
  _.each @dinfo.fields, (fk) => record[fk] = []
662
737
  _.each sel, (rec, ids) =>
663
- _(@dinfo.fields).zip(E2.split_keys(ids)).each(([k, v]) => record[k].push E2.parse_entry(v, @parentp().meta.info[k])).value
738
+ _(@dinfo.fields).zip(E2.split_keys(ids)).each(([k, v]) => record[k].push E2.parse_entry(v, @parentp().meta.fields[k])).value
664
739
  @invoke_decode _.values(sel)
665
740
  delete @decode if _.isEmpty(sel)
666
741
  else
667
742
  [ids, rec] = _(sel).toPairs().head()
668
- _(@dinfo.fields).zip(E2.split_keys(ids)).each(([k, v]) => record[k] = E2.parse_entry(v, @parentp().meta.info[k])).value
743
+ _(@dinfo.fields).zip(E2.split_keys(ids)).each(([k, v]) => record[k] = E2.parse_entry(v, @parentp().meta.fields[k])).value
669
744
  @invoke_decode [rec]
670
745
  @parentp().search_field_change?(@decode_field)
671
746
 
@@ -675,7 +750,7 @@ angular.module('Engine2')
675
750
  else
676
751
  decode_descriptions = (recs) => @decode = recs.map((fields) => @decode_description(fields)).join(' | ')
677
752
  recs = recs.map (r) => if _.isArray(r) then E2.from_id(r, @meta) else r
678
- if _(recs).every((r) => _(@meta.fields).every((f) -> r[f]?)) then decode_descriptions(recs) else
753
+ if _(recs).every((r) => _(@meta.field_list).every((f) -> r[f]?)) && !@meta.dynamic_meta then decode_descriptions(recs) else
679
754
  @invoke(ids: [recs.map((r) => @meta.primary_fields.map (k) -> r[k])]).then => decode_descriptions(@entries)
680
755
 
681
756
  open: ->
@@ -694,29 +769,31 @@ angular.module('Engine2')
694
769
  typeahead: class TypeAheadAction extends DecodeAction
695
770
  initialize: ->
696
771
  super()
772
+ @scope().$on "$typeahead.select", (e, v, index) =>
773
+ e.stopPropagation()
774
+ _(@dinfo.fields).zip(E2.split_keys(@values[index].id)).each(([fk, k]) => @record()[fk] = E2.parse_entry(k, @parentp().meta.fields[fk])).value
775
+ @parentp().scope().$digest()
776
+ @parentp().search_field_change?(@decode_field)
777
+
778
+ @scope().$watch "action.decode", (e) => @reset() if e == null
779
+
697
780
  @if_fk_values (fk_values) =>
698
781
  @invoke(id: E2.join_keys(fk_values)).then =>
699
782
  if @entry
700
783
  @decode = id: E2.id_for(@entry, @meta), value: @decode_description(@entry)
701
784
 
702
- @scope().$on "$typeahead.select", (e, v, index) =>
703
- e.stopPropagation()
704
- _(@dinfo.fields).zip(E2.split_keys(@values[index].id)).each(([fk, k]) => @record()[fk] = E2.parse_entry(k, @parentp().meta.info[fk])).value
705
- @parentp().search_field_change?(@decode_field)
706
-
707
- @scope().$watch "action.decode", (e) => if e?
708
- @reset() if e.length == 0
785
+ @decode = '' unless @decode?
786
+ # @dinfo.render.min_length == 0
709
787
 
710
788
  load: (value) ->
711
- if value? && value.length > 0 && @key_pressed # check again after strap updates ?
712
- @invoke(query: value).then =>
713
- if @entries # ?
714
- @values = @entries.map (e) => id: E2.id_for(e, @meta), value: @decode_description(e)
715
- delete @entries
716
- @values
789
+ if _.isString(value)
790
+ @invoke(query: value).then => if @entries # ?
791
+ @values = @entries.map (e) => id: E2.id_for(e, @meta), value: @decode_description(e)
792
+ delete @entries
793
+ @values
717
794
 
718
795
  clean: ->
719
- delete @decode
796
+ @decode = ''
720
797
  @clear_record()
721
798
 
722
799
  many_to_one_list: class ManyToOneListAction extends ListAction
@@ -740,11 +817,12 @@ angular.module('Engine2')
740
817
  star_to_many_list: class StarToManyList extends ListAction
741
818
  initialize: ->
742
819
  super()
743
- @query.parent_id = @parent().current_id()
820
+ @query.parent_id = @list_parent_action().current_id()
744
821
 
745
822
  # link_list: implicit
746
- item_menu_confirm_unlink: (index) ->
747
- @invoke_action('confirm_unlink', id: E2.id_for(@entries[index], @meta), parent_id: @query.parent_id)
823
+ item_menu_confirm_unlink: (args) ->
824
+ args.parent_id = @query.parent_id
825
+ @item_menu_confirm_unlink_super(args)
748
826
 
749
827
  star_to_many_bulk_unlink: class StarToManyBulkUnlinkAction extends Action
750
828
  invoke: ->
@@ -762,26 +840,81 @@ angular.module('Engine2')
762
840
  panel_menu_link: ->
763
841
  selection = _.keys(@selection)
764
842
  if selection.length > 0
765
- @invoke_action('link', parent_id: @query.parent_id, ids: selection).then (act) =>
766
- unless @errors
767
- @parent().invoke()
768
- @panel_close()
843
+ @invoke_action('link', parent_id: @query.parent_id, ids: selection)
769
844
 
770
845
  star_to_many_field: class StarToManyField extends ListAction
771
846
  initialize: ->
772
847
  super()
773
848
  @query.parent_id = E2.id_for(@parent().record, @parent().meta)
774
- links = @parent().record[@scope().$parent.f]
775
- @links = links ? (linked: [], unlinked: [])
849
+ @changes = (@parent().record[@scope().$parent.f] ?= (link: [], unlink: [], create: [], modify: [], delete: []))
776
850
  @invoke()
777
851
 
778
852
  invoke: ->
779
- @query.unlinked = [@links.unlinked]
780
- @query.linked = [@links.linked]
853
+ @query.changes = @changes
781
854
  super()
782
855
 
783
- sync_record: ->
784
- @parent().record[@scope().$parent.f] = @links
856
+ entry_dropped: (moved_to) ->
857
+ pos_field = @meta.draggable.position_field
858
+ positions = @entries.map (e) -> e[pos_field]
859
+ super(moved_to, false)
860
+ _.each positions, (p, i) =>
861
+ if @entries[i][pos_field] != p
862
+ if entry = @current_entry_is('create', @entries[i]) ? @current_entry_is('modify', @entries[i])
863
+ entry[pos_field] = p
864
+ else
865
+ @changes.modify.push(@entries[i])
866
+ @entries[i][pos_field] = p
867
+
868
+ @render_table() # @invoke()
869
+ true
870
+
871
+ current_entry_is: (mode, entry = @current_entry()) ->
872
+ key = E2.id_for(entry, @meta)
873
+ _.find(@changes[mode], (e) => E2.id_for(e, @meta) == key)
874
+
875
+ star_to_many_field_view: class StarToManyFieldView extends ViewAction
876
+ invoke: (args) ->
877
+ if entry = @parent().current_entry_is('create') ? @parent().current_entry_is('modify')
878
+ @meta.invokable = false
879
+ @record = entry
880
+ super(args)
881
+
882
+ star_to_many_field_modify: class StarToManyFieldModifyAction extends ModifyAction
883
+ invoke: (args) ->
884
+ if entry = @parent().current_entry_is('create') ? @parent().current_entry_is('modify')
885
+ @meta.invokable = false
886
+ @record = entry
887
+ super(args)
888
+
889
+ star_to_many_field_approve: class StarToManyFieldApprove extends Action
890
+ post_invoke: (args) ->
891
+ super(args)
892
+ unless @errors
893
+ pparent = @parent().parent()
894
+ if @parent() instanceof StarToManyFieldModifyAction
895
+ if entry = pparent.current_entry_is('create') ? pparent.current_entry_is('modify')
896
+ _.assign(entry, @parent().record)
897
+ else
898
+ pparent.changes.modify.push @parent().record
899
+ else # CreateAction
900
+ _(@parent().meta.primary_fields).each (k) => @parent().record[k] = E2.uuid()
901
+ if draggable = pparent.meta.draggable
902
+ max = _.maxBy(pparent.entries, (e) -> e.position)
903
+ @parent().record[draggable.position_field] = if max then max[draggable.position_field] + 1 else 1
904
+ pparent.changes.create.push @parent().record
905
+
906
+ star_to_many_field_delete: class StarToManyFieldDelete extends Action
907
+ invoke: (args) ->
908
+ pparent = @parent().parent()
909
+ if entry = pparent.current_entry_is('create')
910
+ _.remove(pparent.changes.create, entry)
911
+ else if entry = pparent.current_entry_is('modify')
912
+ _.remove(pparent.changes.modify, entry)
913
+ pparent.changes.delete.push args.id
914
+ else
915
+ pparent.changes.delete.push args.id
916
+ @meta.invokable = false
917
+ super(args)
785
918
 
786
919
  star_to_many_field_link_list: class StarToManyFieldLinkList extends ListAction
787
920
  initialize: ->
@@ -791,57 +924,50 @@ angular.module('Engine2')
791
924
  @selection = {}
792
925
 
793
926
  invoke: ->
794
- @query.unlinked = [@parent().links.unlinked]
795
- @query.linked = [@parent().links.linked]
927
+ @query.changes = @parent().changes
796
928
  super()
797
929
 
798
930
  panel_menu_link: ->
799
931
  if @selected_size() > 0
800
932
  _.each @selection, (v, k) =>
801
933
  id = k
802
- if _.includes(@parent().links.unlinked, id) then _.pull(@parent().links.unlinked, id) else @parent().links.linked.push id
934
+ if _.includes(@parent().changes.unlink, id) then _.pull(@parent().changes.unlink, id) else @parent().changes.link.push id
803
935
  @parent().invoke()
804
- @parent().sync_record()
805
936
  @panel_close()
806
937
 
807
938
  star_to_many_field_unlink: class StarToManyFieldUnlink extends Action
808
939
  invoke: (args) ->
809
940
  id = args.id
810
941
  pparent = @parent().parent()
811
- if _.includes(pparent.links.linked, id) then _.pull(pparent.links.linked, id) else pparent.links.unlinked.push id
812
- pparent.sync_record()
942
+ if _.includes(pparent.changes.link, id) then _.pull(pparent.changes.link, id) else pparent.changes.unlink.push id
943
+ @meta.invokable = false
944
+ super(args)
813
945
 
814
946
  file_store: class FileStoreAction extends Action
815
947
  initialize: ->
816
948
  super()
817
949
  @progress = 0
818
950
  id = E2.id_for(@parent().record, @parent().meta)
819
- if id.length > 0
820
- @invoke(owner: id).then => @sync_record()
821
- else
822
- @files = []
823
- @sync_record()
824
-
825
- sync_record: ->
826
- @parent().record[@scope().f] = @files
951
+ files = @parent().record[@scope().f]
952
+ @invoke(owner: id).then =>
953
+ @parent().record[@scope().f] = if files? then files else @files
954
+ delete @files
827
955
 
828
956
  select: (files) ->
829
957
  _.each files, (file) =>
830
958
  upload = $injector.get('Upload').upload url: "#{@action_info().action_resource}/upload", file: file
831
959
  upload.progress (e) =>
832
- globals.action_pending = false
960
+ @globals().action_pending = false
833
961
  @progress = parseInt(100.0 * e.loaded / e.total)
834
962
  upload.success (data, status, headers, config) =>
835
- @files.push mime: file.type, name: file.name, rackname: data.rackname, id: data.id
963
+ @parent().record[@scope().f].push mime: file.type, name: file.name, rackname: data.rackname, id: data.id, new: true
836
964
  @message = "Wysłano, #{file.name}"
837
- globals.action_pending = false
838
- @sync_record()
965
+ @globals().action_pending = false
839
966
 
840
967
  delete_file: (file) ->
841
968
  @scope().$broadcast 'confirm_delete',
842
969
  confirm: =>
843
- @sync_record()
844
- file.deleted = true
970
+ if file.new then _.pull(@parent().record[@scope().f], file) else file.deleted = true
845
971
  @scope().$broadcast 'confirm_delete_close'
846
972
 
847
973
  show_file: (file) ->