engine2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/Rakefile +138 -0
  4. data/conf/message.yaml +93 -0
  5. data/conf/message_pl.yaml +93 -0
  6. data/engine2.gemspec +34 -0
  7. data/lib/engine2.rb +34 -0
  8. data/lib/engine2/action.rb +217 -0
  9. data/lib/engine2/core.rb +572 -0
  10. data/lib/engine2/handler.rb +134 -0
  11. data/lib/engine2/meta.rb +969 -0
  12. data/lib/engine2/meta/decode_meta.rb +110 -0
  13. data/lib/engine2/meta/delete_meta.rb +73 -0
  14. data/lib/engine2/meta/form_meta.rb +144 -0
  15. data/lib/engine2/meta/infra_meta.rb +292 -0
  16. data/lib/engine2/meta/link_meta.rb +133 -0
  17. data/lib/engine2/meta/list_meta.rb +284 -0
  18. data/lib/engine2/meta/save_meta.rb +63 -0
  19. data/lib/engine2/meta/view_meta.rb +22 -0
  20. data/lib/engine2/model.rb +390 -0
  21. data/lib/engine2/models/Files.rb +38 -0
  22. data/lib/engine2/models/UserInfo.rb +24 -0
  23. data/lib/engine2/post_bootstrap.rb +83 -0
  24. data/lib/engine2/pre_bootstrap.rb +27 -0
  25. data/lib/engine2/scheme.rb +202 -0
  26. data/lib/engine2/templates.rb +229 -0
  27. data/lib/engine2/type_info.rb +342 -0
  28. data/lib/engine2/version.rb +9 -0
  29. data/public/assets/javascripts.js +13 -0
  30. data/public/assets/styles.css +4 -0
  31. data/public/css/angular-motion.css +1022 -0
  32. data/public/css/angular-ui-tree.min.css +1 -0
  33. data/public/css/app.css +196 -0
  34. data/public/css/bootstrap-additions.css +1560 -0
  35. data/public/css/bootstrap.min.css +11 -0
  36. data/public/css/font-awesome.min.css +4 -0
  37. data/public/favicon.ico +0 -0
  38. data/public/fonts/FontAwesome.otf +0 -0
  39. data/public/fonts/fontawesome-webfont.eot +0 -0
  40. data/public/fonts/fontawesome-webfont.svg +655 -0
  41. data/public/fonts/fontawesome-webfont.ttf +0 -0
  42. data/public/fonts/fontawesome-webfont.woff +0 -0
  43. data/public/fonts/fontawesome-webfont.woff2 +0 -0
  44. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  45. data/public/fonts/glyphicons-halflings-regular.svg +288 -0
  46. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  47. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  48. data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  49. data/public/images/file.png +0 -0
  50. data/public/images/folder-closed.png +0 -0
  51. data/public/images/folder.png +0 -0
  52. data/public/images/node-closed-2.png +0 -0
  53. data/public/images/node-closed-light.png +0 -0
  54. data/public/images/node-closed.png +0 -0
  55. data/public/images/node-opened-2.png +0 -0
  56. data/public/images/node-opened-light.png +0 -0
  57. data/public/images/node-opened.png +0 -0
  58. data/public/img/ajax-loader-dark.gif +0 -0
  59. data/public/img/ajax-loader-light.gif +0 -0
  60. data/public/img/ajax-loader.gif +0 -0
  61. data/public/js/angular-animate.js +4115 -0
  62. data/public/js/angular-cookies.js +322 -0
  63. data/public/js/angular-local-storage.js +455 -0
  64. data/public/js/angular-route.js +1022 -0
  65. data/public/js/angular-sanitize.js +717 -0
  66. data/public/js/angular-strap.js +4339 -0
  67. data/public/js/angular-strap.tpl.js +43 -0
  68. data/public/js/angular-ui-tree.js +1569 -0
  69. data/public/js/angular.js +30714 -0
  70. data/public/js/i18n/angular-locale_pl.js +115 -0
  71. data/public/js/lodash.custom.min.js +97 -0
  72. data/public/js/ng-file-upload-shim.min.js +2 -0
  73. data/public/js/ng-file-upload.min.js +3 -0
  74. data/views/app.coffee +3 -0
  75. data/views/engine2.coffee +557 -0
  76. data/views/engine2actions.coffee +849 -0
  77. data/views/engine2templates.coffee +0 -0
  78. data/views/fields/blob.slim +22 -0
  79. data/views/fields/bs_select.slim +10 -0
  80. data/views/fields/bsselect_picker.slim +18 -0
  81. data/views/fields/bsselect_picker_opt.slim +22 -0
  82. data/views/fields/checkbox.slim +11 -0
  83. data/views/fields/checkbox_buttons.slim +6 -0
  84. data/views/fields/checkbox_buttons_opt.slim +8 -0
  85. data/views/fields/currency.slim +10 -0
  86. data/views/fields/date.slim +21 -0
  87. data/views/fields/date_range.slim +44 -0
  88. data/views/fields/date_time.slim +42 -0
  89. data/views/fields/datetime.slim +42 -0
  90. data/views/fields/decimal.slim +11 -0
  91. data/views/fields/decimal_date.slim +22 -0
  92. data/views/fields/decimal_time.slim +26 -0
  93. data/views/fields/email.slim +13 -0
  94. data/views/fields/file_store.slim +61 -0
  95. data/views/fields/input_text.slim +14 -0
  96. data/views/fields/integer.slim +11 -0
  97. data/views/fields/list_bsselect.slim +18 -0
  98. data/views/fields/list_bsselect_opt.slim +21 -0
  99. data/views/fields/list_buttons.slim +3 -0
  100. data/views/fields/list_buttons_opt.slim +5 -0
  101. data/views/fields/list_select.slim +11 -0
  102. data/views/fields/list_select_opt.slim +15 -0
  103. data/views/fields/password.slim +14 -0
  104. data/views/fields/radio_checkbox.slim +10 -0
  105. data/views/fields/scaffold.slim +2 -0
  106. data/views/fields/scaffold_picker.slim +20 -0
  107. data/views/fields/select_picker.slim +12 -0
  108. data/views/fields/select_picker_opt.slim +16 -0
  109. data/views/fields/text_area.slim +10 -0
  110. data/views/fields/time.slim +22 -0
  111. data/views/fields/typeahead_picker.slim +25 -0
  112. data/views/index.slim +44 -0
  113. data/views/infra/index.slim +5 -0
  114. data/views/infra/inspect.slim +81 -0
  115. data/views/modals/close_m.slim +15 -0
  116. data/views/modals/confirm_m.slim +19 -0
  117. data/views/modals/empty_m.slim +12 -0
  118. data/views/modals/menu_m.slim +13 -0
  119. data/views/modals/yes_no_m.slim +19 -0
  120. data/views/panels/menu_m.slim +9 -0
  121. data/views/scaffold/confirm.slim +3 -0
  122. data/views/scaffold/fields.slim +10 -0
  123. data/views/scaffold/form.slim +11 -0
  124. data/views/scaffold/list.slim +42 -0
  125. data/views/scaffold/message.slim +3 -0
  126. data/views/scaffold/search.slim +20 -0
  127. data/views/scaffold/view.slim +18 -0
  128. data/views/search_fields/bsmselect_picker.slim +25 -0
  129. data/views/search_fields/bsselect_picker.slim +24 -0
  130. data/views/search_fields/checkbox.slim +11 -0
  131. data/views/search_fields/checkbox2.slim +14 -0
  132. data/views/search_fields/checkbox_buttons.slim +10 -0
  133. data/views/search_fields/date_range.slim +46 -0
  134. data/views/search_fields/decimal_date_range.slim +47 -0
  135. data/views/search_fields/input_text.slim +18 -0
  136. data/views/search_fields/integer.slim +18 -0
  137. data/views/search_fields/integer_range.slim +27 -0
  138. data/views/search_fields/list_bsmselect.slim +24 -0
  139. data/views/search_fields/list_bsselect.slim +22 -0
  140. data/views/search_fields/list_buttons.slim +8 -0
  141. data/views/search_fields/list_select.slim +17 -0
  142. data/views/search_fields/scaffold_picker.slim +19 -0
  143. data/views/search_fields/select_picker.slim +17 -0
  144. data/views/search_fields/typeahead_picker.slim +25 -0
  145. metadata +327 -0
@@ -0,0 +1,849 @@
1
+ angular.module('Engine2')
2
+ .directive 'e2Action', (E2Actions) ->
3
+ scope: true
4
+ controller: ($scope, $attrs, $parse, $element, $http) ->
5
+ if action_attr = $attrs.action
6
+ action_names = $parse(action_attr)($scope)
7
+ throw "Invalid action path: '#{action_attr}'" unless action_names
8
+ action_names = action_names.split('/') if _.isString(action_names)
9
+ create = (action) ->
10
+ action.create_action_path(action_names, $scope, $element).then (act) -> act.invoke() if $attrs.invoke
11
+
12
+ sc = $scope
13
+ if sc.action
14
+ sc = sc.$parent until sc.action instanceof E2Actions.action
15
+ create(sc.action)
16
+ else
17
+ bootstrap_action_off = $scope.$on "bootstrap_action", (evt, action) ->
18
+ bootstrap_action_off()
19
+ create(action)
20
+ else
21
+ $http.get("api/meta").then (mresponse) -> $scope.$broadcast "bootstrap_action",
22
+ $scope.action = new E2Actions.default_action(mresponse.data, $scope, null, $element, action_resource: 'api')
23
+
24
+ .factory 'E2Actions', (E2, $http, $timeout, $e2Modal, $injector, $compile, $templateCache, $q, localStorageService, $route, $window, $rootScope, $location) ->
25
+ action: class Action
26
+ constructor: (response, scope, parent, element, action_info) ->
27
+ @find_action_info = (name, raise = true) ->
28
+ act = response.actions[name]
29
+ throw "Undefined action '#{name}' for action #{@action_info().name} (under #{@parent().action_info().action_resource})" if raise && !act
30
+ act
31
+
32
+ _.each response.actions, (act, nm) -> act.name = nm
33
+ @meta = response.meta
34
+ (@scope = -> scope) if scope
35
+ @element = -> element
36
+ @action_info = -> action_info
37
+ @parent = -> parent
38
+
39
+ if @meta.panel
40
+ @default_action_name = _(response.actions).find((o) -> o.default)?.name
41
+
42
+ scope = scope.$new(true)
43
+ scope.$on "$destroy", (e) => @destroy(e)
44
+ scope.action = @
45
+
46
+ act = parent
47
+ act = act.parent() while act && !act.meta.panel
48
+ unless act # no modal for top level panels
49
+ @meta.panel.modal_action = false
50
+ if @meta.menus.panel_menu
51
+ _.remove(@meta.menus.panel_menu.entries, (m) -> m.name == 'cancel')
52
+
53
+ @initialize()
54
+
55
+ initialize: ->
56
+ @process_static_meta()
57
+ @process_meta()
58
+ console.log "CREATE #{@action_info().action_resource}"
59
+
60
+ process_static_meta: ->
61
+ if @meta.menus
62
+ _.each @meta.menus, (menu, name) => E2.process_menu @, name
63
+ process_meta: ->
64
+
65
+ handle_error: (err, action_info, element, create) ->
66
+ if err.status == 401
67
+ if action_info.access
68
+ $rootScope.$broadcast "relogin", element?, create
69
+ 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 info.invokable
76
+ params.initial = true if !@action_invoked && params && info.method == 'get'
77
+ $http[info.method](info.action_resource, if info.method == 'post' then params else (params: params))
78
+ else $q.when(data: (response: {}))
79
+
80
+ get_invoke.then (response) =>
81
+ E2.merge(@meta, response.data.meta)
82
+ @process_meta()
83
+ _.assign(@, response.data.response)
84
+ if @meta.response
85
+ E2.merge(@, @meta.response)
86
+ delete @meta.response
87
+ @arguments = _.keys(response.data.response)
88
+ unless @meta.panel # persistent action
89
+ prnt = @parent()
90
+ throw "Attempted parent merge for root action: #{info.name}" unless prnt
91
+ E2.merge(prnt.meta, @meta)
92
+ _.assign(prnt, response.data.response)
93
+
94
+ # promise = if @meta.panel && !@action_invoked
95
+ # $q.when(promise) # .then -> response.data
96
+ if @meta.panel && !@action_invoked
97
+ @action_invoked = true
98
+ @panel_render()
99
+ # else $q.when()
100
+ ,
101
+ (err) =>
102
+ @parent().action_pending = false
103
+ @handle_error(err, info, @element())
104
+
105
+ create_action: (name, sc, el) ->
106
+ info = @find_action_info(name)
107
+ info.action_resource = "#{@action_info().action_resource}/#{info.name}"
108
+ get_meta = if !info.terminal || info.meta
109
+ $http.get("#{info.action_resource}/meta", cache: true).then (response) =>
110
+ if info.recheck_access
111
+ $http.get("#{info.action_resource}/meta", params: (access: true, parent_id: @current_id)).then (aresponse) ->
112
+ response.data.actions[k].access = v for k, v of aresponse.data
113
+ response
114
+ else response # $q.when ^
115
+ else $q.when(data: (meta: {}, actions: []))
116
+ E2A = $injector.get("E2Actions")
117
+ get_meta.then (mresponse) => new (E2A[info.meta_type] ? E2A.default_action)(mresponse.data, sc, @, el, info)
118
+ ,
119
+ (err) => @handle_error(err, info, el)
120
+
121
+ invoke_action: (name, arg) ->
122
+ @create_action(name, @scope()).then (act) -> act.invoke(arg)
123
+
124
+ create_action_path: (action_names, sc, elem) ->
125
+ last_name = action_names.pop()
126
+ _.reduce(action_names, ((pr, nm) -> pr.then (act) -> act.create_action(nm)), $q.when(@)).then (act) ->
127
+ act.create_action(last_name, sc, elem).then (act) -> sc.action = act
128
+
129
+ pre_invoke: ->
130
+ @parent().action_pending = true
131
+ # @parent().parent().action_pending = true if @parent().parent()
132
+ post_invoke: ->
133
+ delete @parent().action_pending # = false
134
+ # @parent().parent().action_pending = false if @parent().parent()
135
+ invoke: ->
136
+ args = arguments
137
+ @pre_invoke(args...)
138
+ @perform_invoke(args...).then (response) =>
139
+ @post_invoke(args...)
140
+ @
141
+
142
+ save_state: () ->
143
+ _.each @meta.state, (s) => localStorageService.set("#{@action_info().action_resource}/#{s}", @[s])
144
+ load_state: () ->
145
+ _.each @meta.state, (s) => E2.merge(@[s], localStorageService.get("#{@action_info().action_resource}/#{s}"))
146
+
147
+ destroy: (e) ->
148
+ console.log "DESTROY #{@action_info().action_resource}"
149
+
150
+ panel_render: ->
151
+ if @meta.panel.modal_action
152
+ if @element()
153
+ E2.fetch_panel(@meta.panel, true).then (template) =>
154
+ @panel_show?()
155
+ compiled = $compile(template)(@scope())
156
+ @element().empty().append(compiled.contents())
157
+ @panel_shown?()
158
+
159
+ else
160
+ $e2Modal.show(@)
161
+ else
162
+ @panel_scope?().$destroy()
163
+ act = @
164
+ act = act.parent() until act.element()
165
+ element = act.element() # @element()
166
+ is_modal = $e2Modal.is_modal() && !@element()
167
+ E2.fetch_panel(@meta.panel, is_modal).then (template) =>
168
+ @panel_show?()
169
+ # @panel_scope().$destroy()
170
+ # @panel_scope = -> @scope().$new(false)
171
+ # @scope().$broadcast "$destroy"
172
+ @panel_scope = -> @scope().$new()
173
+ compiled = $compile(template)(@panel_scope())
174
+ if is_modal
175
+ element.empty().append(compiled.contents())
176
+ else
177
+ # element.empty().$destroy()
178
+ element.empty().append(compiled)
179
+ @panel_shown?()
180
+
181
+ # panel_refresh: ->
182
+
183
+ panel_hidden: ->
184
+ @scope().$destroy()
185
+
186
+ panel_close: ->
187
+ if @meta.panel.modal_action
188
+ @modal_hide()
189
+ else
190
+ # @parent().panel_refresh()
191
+ @panel_hide?()
192
+ @panel_hidden()
193
+ @scope().$destroy()
194
+ @parent().action_invoked = false
195
+ @parent().invoke()
196
+
197
+ panel_menu_cancel: ->
198
+ @panel_close()
199
+
200
+ default_action: class DefaultAction extends Action
201
+ initialize: ->
202
+ super()
203
+ # console.log "DEFAULT ACTION: #{@action_info().action_resource}"
204
+
205
+ inspect: class InspectAction extends Action
206
+ initialize: ->
207
+ super()
208
+ @tree = actions: [name: 'api', number: 0, access: true]
209
+ @invoke_action('models')
210
+
211
+ open: (stack, node, collapsed, expand) ->
212
+ tree = @tree
213
+ path = []
214
+ _.each stack, (index) -> # fold ?
215
+ tree = tree.actions[index]
216
+ path.push tree.name
217
+
218
+ # if !expand
219
+ @number = tree.number
220
+ if _.size(stack) > 1
221
+ $http.get("#{_.dropRight(path).join('/')}/meta").then (response) => @action_json = _.toArray(response.data.actions)[_.last(stack)]
222
+ else
223
+ @action_json = {}
224
+
225
+ # if expand && collapsed
226
+ $http.get("#{path.join('/')}/meta").then (response) =>
227
+ get_meta = if tree.recheck_access
228
+ $http.get("#{path.join('/')}/meta", params: (access: true)).then (aresponse) ->
229
+ response.data.actions[k].access = v for k, v of aresponse.data
230
+ response
231
+ else $q.when(response)
232
+
233
+ get_meta.then (response) =>
234
+ _.each response.data.actions, (act, nm) -> act.name = nm
235
+ tree.actions ?= _.toArray(response.data.actions)
236
+ @meta_json = response.data.meta
237
+ @action_state = if @meta_json.state then _.zipObject(@meta_json.state.map (k) -> [k, localStorageService.get("#{path}/#{k}")]) else {}
238
+ ,
239
+ (err) =>
240
+ delete @meta_json
241
+ @handle_error(err, access: false)
242
+
243
+ has_assoc: (model) ->
244
+ _.size(model.assoc) > 0
245
+
246
+ menu: class MenuAction extends Action
247
+ process_static_meta: ->
248
+
249
+ initialize: ->
250
+ super()
251
+ $route.load_routes = =>
252
+ @invoke().then =>
253
+ _.each _.keys($route.routes), (k) -> delete $route.routes[k]
254
+ menu = @meta.menus.menu
255
+ $route.routes[null] = reloadOnSearch: true, redirectTo: '/' + (menu.properties.default ? menu.entries[0].name)
256
+ @register(menu.entries)
257
+ $route.reload() # $location.path('')
258
+ @scope().routes = menu.entries
259
+ out = if _.size(menu.entries) == 0 then angular.element("<div></div>") else $compile(@traverse(menu.entries))(@scope())
260
+ @element().replaceWith(out)
261
+ @element = -> out
262
+ $route.load_routes()
263
+
264
+ register: (routes) ->
265
+ _.each routes, (route) =>
266
+ if route.menu then @register(route.menu.entries) else
267
+ name = '/' + route.name
268
+ route.href = '#' + route.name
269
+ $templateCache.put(route.name, "<div e2-action='' action=\"'#{route.bootstrap}/#{route.name}'\" invoke='true'></div>") if route.bootstrap
270
+ $route.routes[name] =
271
+ reloadOnSearch: true
272
+ templateUrl: route.name
273
+ originalPath: name
274
+ regexp: new RegExp("^#{name}$")
275
+ keys: []
276
+
277
+ $route.routes[name + '/'] =
278
+ redirectTo: name
279
+ originalPath: name + '/'
280
+ regexp: new RegExp("^#{name}/$")
281
+ keys: []
282
+
283
+ traverse: (routes) ->
284
+ menu_tmpl = _.template("<li><a href='{{href}}'>{{icon}}{{aicon}} {{loc}}</a></li>")
285
+ menu_sub_tmpl = _.template("<li e2-drop-down='{{dropdown}}'><a href='javascript://'>{{icon}}{{aicon}}{{loc}}<span class='caret'></span></a></li>")
286
+ out = routes.map (route, i) ->
287
+ if route.menu
288
+ menu_sub_tmpl
289
+ icon: route.menu.icon && E2.icon(route.menu.icon) || ""
290
+ aicon: route.menu.aicon && E2.aicon(route.menu.aicon) || ""
291
+ loc: route.menu.loc
292
+ dropdown: "routes[#{i}].menu.entries"
293
+ else
294
+ menu_tmpl
295
+ href: route.href
296
+ loc: route.loc
297
+ icon: route.icon && E2.icon(route.icon) || ''
298
+ aicon: route.aicon && E2.aicon(route.aicon) || ''
299
+ out.join('')
300
+
301
+ # dummy: class DummyAction extends Action
302
+
303
+ list: class ListAction extends Action
304
+ initialize: ->
305
+ super()
306
+ @query = page: 0, asc: true, search: {} #, search_tab: 0
307
+ @ui_state = {}
308
+ @load_state()
309
+
310
+ delete @query.order unless _.includes(@meta.fields, @query.order)
311
+ _.each @query.search, ((sv, sn) => delete @query.search[sn] unless _.includes(@meta.search_fields, sn))
312
+
313
+ _.each @meta.info, (info, name) =>
314
+ if info.remote_onchange
315
+ @scope().$watch (=> @query.search?[name]), (n) => if n?
316
+ params = value: @query.search[name]
317
+ params.record = @query.search if info.remote_onchange_record
318
+ @invoke_action(info.remote_onchange, params)
319
+
320
+ if info.onchange
321
+ @scope().$watch (=> @query.search?[name]), (n) => if n?
322
+ @scope().$eval(info.onchange)
323
+
324
+ # $window.addEventListener 'beforeunload', (e, v) => @save_state()
325
+
326
+ destroy: ->
327
+ @save_state()
328
+ super()
329
+
330
+ process_meta: ->
331
+ super()
332
+ meta = @meta
333
+ meta.fields = meta.fields.filter((f) => !meta.info[f].hidden) if meta.fields
334
+
335
+ # confirm_create, view, confirm_modify, confirm_delete, assocs - implicit
336
+
337
+ menu_search_toggle: ->
338
+ @ui_state.search_active = !@ui_state.search_active
339
+ @save_state() unless @ui_state.search_active
340
+
341
+ menu_refresh: ->
342
+ @invoke(refresh: true)
343
+
344
+ menu_default_order: ->
345
+ delete @query.order
346
+ @invoke()
347
+
348
+ menu_select_toggle: ->
349
+ if @selection then delete @selection else @selection = {}
350
+ @scope().$broadcast 'render_table'
351
+
352
+ menu_show_meta: ->
353
+ $e2Modal.show
354
+ the_meta: @meta
355
+ meta: panel: (panel_template: "close_m", template_string: "<pre>{{action.the_meta | json}}</pre>", title: "Meta", class: "modal-huge")
356
+
357
+ # show_assoc: (index, assoc) ->
358
+ # # parent_id = E2.id_for(@entries[index], @meta)
359
+ # # @create_action(assoc, @scope(), null, parent_id).then (action) =>
360
+ # # action.query.parent_id = parent_id # E2.id_for(@entries[index], @meta)
361
+ # # action.invoke()
362
+ # @current_id = E2.id_for(@entries[index], @meta)
363
+ # @invoke_action(assoc)
364
+
365
+ list_cell: (e, f) ->
366
+ E2.render_field(e, f, @meta)
367
+
368
+ invoke: (arg = {}) ->
369
+ @save_state()
370
+ query = _.cloneDeep(@query)
371
+ delete query.search if _.isEmpty(E2.compact(query.search))
372
+ _.merge(query, arg)
373
+ super(query).then =>
374
+ @ui = _.pick @query, ['order', 'asc', 'page']
375
+ @scope().$broadcast 'render_table'
376
+
377
+ load_new: ->
378
+ @query.page = 0
379
+ @invoke()
380
+
381
+ order: (col) ->
382
+ @query.asc = if @query.order == col then !@query.asc else true
383
+ @query.order = col
384
+ @load_new()
385
+
386
+ prev_active: -> @query.page > 0
387
+ prev: ->
388
+ @query.page = Math.max(0, @query.page - @meta.config.per_page)
389
+ @invoke()
390
+
391
+ next_active: -> @entries.length == @meta.config.per_page
392
+ next: ->
393
+ @query.page += @meta.config.per_page # min & count
394
+ @invoke()
395
+
396
+ page_info: ->
397
+ page = @ui.page / @meta.config.per_page + 1
398
+ if @count then "#{page} / #{Math.ceil(@count / @meta.config.per_page)} (#{@count})" else page || ''
399
+
400
+ search_reset: ->
401
+ E2.clean(@query.search)
402
+ @scope().$broadcast "search_reset"
403
+ @load_new()
404
+
405
+ search_live: (f) ->
406
+ @load_new() if @meta.info[f].search_live
407
+
408
+ selected_class: (index) ->
409
+ (entry = @entries[index]) && @selection && @selection[E2.id_for(entry, @meta)] && 'info'
410
+
411
+ select: (index, ev) ->
412
+ if ev.target.nodeName == "TD"
413
+ if @selection
414
+ rec = @entries[index]
415
+ id = E2.id_for(rec, @meta)
416
+ if @selection[id] then delete @selection[id] else @selection[id] = rec
417
+
418
+ selected_size: ->
419
+ _.size(@selection)
420
+
421
+ bulk_delete: class BulkDeleteAction extends Action
422
+ invoke: ->
423
+ super(ids: [_.keys(@parent().parent().selection)]).then =>
424
+ @parent().parent().selection = {}
425
+
426
+ view: class ViewAction extends Action
427
+ view_cell: (e, f) ->
428
+ E2.render_field(e, f, @meta)
429
+
430
+ form_base_action: class FormBaseAction extends Action
431
+ initialize: ->
432
+ super()
433
+ _.each @meta.info, (info, name) =>
434
+ if info.remote_onchange
435
+ @scope().$watch (=> @record?[name]), (n) => if n? #if typeof(n) != "undefined"
436
+ params = value: @record[name]
437
+ params.record = @record if info.remote_onchange_record
438
+ @invoke_action(info.remote_onchange, params)
439
+
440
+ if info.onchange
441
+ @scope().$watch (=> @record?[name]), (n) => if n?
442
+ @scope().$eval(info.onchange)
443
+
444
+ if @meta.tabs
445
+ @scope().$watch "action.activeTab", (tab) => if tab? # && tab >= 0
446
+ @panel_shown()
447
+
448
+ @["panel_menu_#{@default_action_name}"] = -> @panel_menu_default_action()
449
+ @scope().$on "return_pressed", (e) => @panel_menu_default_action()
450
+
451
+ post_invoke: (args) ->
452
+ super()
453
+ _.each @meta.info, (info, name) =>
454
+ if _.isString(@record[name]) && !info.dont_strip
455
+ @record[name] = @record[name].trim()
456
+
457
+ panel_menu_default_action: ->
458
+ _.each @meta.info, (v, n) =>
459
+ @record[n] = null if @record[n] is undefined
460
+ params = record: @record
461
+ params.parent_id ?= @parent().query?.parent_id # and StarToManyList ?
462
+ @invoke_action(@default_action_name, params).then =>
463
+ dfd = $q.defer()
464
+ if @errors
465
+ if @meta.tabs
466
+ [i, first, curr] = [0, null, false]
467
+ for tab in @meta.tabs
468
+ if _(tab.fields).find((f) => @errors[f])
469
+ first = i if not first?
470
+ act = true if @activeTab == i
471
+ i++
472
+ @activeTab = first unless act
473
+
474
+ if @activeTab?
475
+ field = _(@meta.tabs[@activeTab].fields).find((f) => @errors[f])
476
+ # console.log field undefined ?
477
+ else
478
+ @activeTab = 0
479
+ @alert = @errors
480
+ else
481
+ field = _(@meta.fields).find((f) => @errors[f])
482
+ @alert = @errors if (!field || !@meta.info[field] || @meta.info[field].hidden) # ?
483
+ $timeout => @scope().$broadcast("focus_field", field)
484
+ #e.scope.$eval(meta.execute) if meta.execute # ?
485
+ dfd.reject(@errors)
486
+ else
487
+ @panel_close()
488
+ dfd.resolve(@record) # $q.when(true) ?
489
+ dfd.promise
490
+
491
+ panel_shown: ->
492
+ field = if @meta.tabs
493
+ tab = @meta.tabs[@activeTab]
494
+ if @errors
495
+ _(tab.fields).find((f) => @errors[f]) || _(tab.fields).find((f) => !@meta.info[f].hidden)
496
+ else
497
+ tab ?= @meta.tabs[0]
498
+ _(tab.fields).find((f) => !@meta.info[f].hidden && !@meta.info[f].disabled)
499
+ else
500
+ _(@meta.fields).find((f) => !@meta.info[f].hidden && !@meta.info[f].disabled)
501
+ $timeout (=> @scope().$broadcast("focus_field", field)), 300 # hack, on shown ?
502
+
503
+ infra: class InfraAction extends Action
504
+ initialize: ->
505
+ super()
506
+ @scope().$on "relogin", (evt, reload_routes, create) =>
507
+ if @user
508
+ @invoke_action('login_form').then (act) =>
509
+ act.record = name: @user.name
510
+ act.meta.info.name.disabled = true
511
+ act.dont_reload_routes = !reload_routes # true
512
+ else
513
+ @invoke().then => @set_access(true, true)
514
+
515
+ set_access: (login, load_routes) ->
516
+ @find_action_info('logout_form').access = login
517
+ @find_action_info('inspect_modal').access = login
518
+ @find_action_info('login_form').access = !login
519
+ $route.load_routes() if load_routes
520
+
521
+ login_form: class LoginFormAction extends FormBaseAction
522
+ panel_menu_default_action: ->
523
+ super().then =>
524
+ @parent().user = @user
525
+ @parent().set_access(true, !@dont_reload_routes)
526
+
527
+ logout_form: class LogoutForm extends Action
528
+ panel_menu_logout: ->
529
+ @invoke_action('logout').then =>
530
+ @parent().user = null
531
+ @parent().set_access(false, true)
532
+ @panel_close()
533
+
534
+ form: class FormAction extends FormBaseAction
535
+ panel_menu_default_action: ->
536
+ super().then => @parent().invoke()
537
+
538
+ create: class CreateAction extends FormAction
539
+ invoke: (args) ->
540
+ if parent_id = @parent().query.parent_id
541
+ args ?= {}
542
+ args.parent_id = parent_id
543
+ super(args)
544
+
545
+ modify: class ModifyAction extends FormAction
546
+ # invoke: (args) ->
547
+ # super(args).then =>
548
+ # _.each @meta.primary_fields, (f) => @meta.info[f].disabled = true
549
+
550
+ on_change: class OnChangeAction extends Action
551
+ post_invoke: ->
552
+ super()
553
+ @parent().scope().$eval(@meta.execute) if @meta.execute
554
+
555
+ confirm: class ConfirmAction extends Action
556
+ panel_menu_approve: ->
557
+ @initial_arguments ?= @arguments
558
+ @invoke_action(@default_action_name, _.pick(@, @initial_arguments)).then (act) =>
559
+ unless @errors
560
+ @parent().invoke()
561
+ @panel_close()
562
+
563
+ decode_action: class DecodeAction extends Action
564
+ initialize: ->
565
+ super()
566
+ @decode_field = @scope().f
567
+ @dinfo = @parentp().meta.info[@decode_field]
568
+ @scope().$on "search_reset", => @clean()
569
+
570
+ if_fk_values: (f) ->
571
+ fk_values = @dinfo.fields.map((f) => @record()[f])
572
+ f(fk_values) if _(fk_values).every((f) -> f?) # null_value
573
+
574
+ record: ->
575
+ @parentp().query?.search || @parentp().record
576
+
577
+ clear_record: ->
578
+ _.each @dinfo.fields, (fk) => @record()[fk] = null # null_value
579
+
580
+ reset: ->
581
+ @clean()
582
+ @parentp().search_live?(@decode_field)
583
+
584
+ decode_description: (entry) ->
585
+ fields = @meta.decode_fields ? @meta.fields
586
+ fields.map((f) => E2.render_field(entry, f, @meta)).join(@meta.separator)
587
+
588
+ parentp: ->
589
+ @parent().parent()
590
+
591
+ decode_list: class DecodeListAction extends DecodeAction
592
+ initialize: ->
593
+ super()
594
+ @multiple = @dinfo.render.multiple
595
+ @clear_selected()
596
+ @if_fk_values (fk_values) =>
597
+ @selected = if @multiple then E2.transpose(fk_values).map(E2.join_keys) else E2.join_keys(fk_values)
598
+ @invoke()
599
+
600
+ clear_selected: -> @selected = if @multiple then [] else null # no need to null complex keys
601
+
602
+ post_invoke: (args) ->
603
+ super()
604
+ @values = @entries.map (e) => id: E2.id_for(e, @meta), value: @decode_description(e)
605
+ delete @entries
606
+
607
+ change: ->
608
+ record = @record()
609
+ if @multiple
610
+ if @selected.length > 0
611
+ _.each @dinfo.fields, (fk) -> record[fk] = []
612
+ _.each @selected, (sel) =>
613
+ _(@dinfo.fields).zip(E2.split_keys(sel)).each(([fk, k]) => record[fk].push E2.parse_entry(k, @parentp().meta.info[fk])).value
614
+ else @clear_record()
615
+ else
616
+ if @selected
617
+ _(@dinfo.fields).zip(E2.split_keys(@selected)).each(([fk, k]) => record[fk] = E2.parse_entry(k, @parentp().meta.info[fk])).value
618
+ else @clear_record()
619
+
620
+ @parentp().search_live?(@decode_field)
621
+
622
+ clean: ->
623
+ @clear_selected()
624
+ @clear_record()
625
+
626
+ decode_entry: class DecodeEntryAction extends DecodeAction
627
+ initialize: ->
628
+ super()
629
+ @multiple = @dinfo.render.multiple
630
+ @if_fk_values (fk_values) =>
631
+ @invoke_decode (if @multiple then E2.transpose(fk_values) else [fk_values])
632
+
633
+ @scope().$on "picked", (ev, sel, sel_meta) =>
634
+ ev.stopPropagation()
635
+ record = @record()
636
+ if @multiple
637
+ _.each @dinfo.fields, (fk) => record[fk] = []
638
+ _.each sel, (rec, ids) =>
639
+ _(@dinfo.fields).zip(E2.split_keys(ids)).each(([k, v]) => record[k].push E2.parse_entry(v, @parentp().meta.info[k])).value
640
+ @invoke_decode _.values(sel)
641
+ delete @decode if _.isEmpty(sel)
642
+ else
643
+ [ids, rec] = _(sel).toPairs().head()
644
+ _(@dinfo.fields).zip(E2.split_keys(ids)).each(([k, v]) => record[k] = E2.parse_entry(v, @parentp().meta.info[k])).value
645
+ @invoke_decode [rec]
646
+ @parentp().search_live?(@decode_field)
647
+
648
+ invoke_decode: (recs, f) ->
649
+ if @multiple && _.size(recs) > @meta.show_max_selected
650
+ @decode = "#{_.size(recs)} #{@meta.decode_selected}"
651
+ else
652
+ decode_descriptions = (recs) => @decode = recs.map((fields) => @decode_description(fields)).join(' | ')
653
+ recs = recs.map (r) => if _.isArray(r) then E2.from_id(r, @meta) else r
654
+ if _(recs).every((r) => _(@meta.fields).every((f) -> r[f]?)) then decode_descriptions(recs) else
655
+ @invoke(ids: [recs.map((r) => @meta.primary_fields.map (k) -> r[k])]).then => decode_descriptions(@entries)
656
+
657
+ open: ->
658
+ fk_values = @dinfo.fields.map((f) => @record()[f]).filter((f) -> f?)
659
+ @create_action('list', @scope()).then (action) =>
660
+ if @multiple
661
+ action.selection = E2.transpose(fk_values).reduce(((rec, keys) => rec[E2.join_keys(keys)] = E2.from_id(keys, @meta); rec), {})
662
+ else
663
+ action.selection[E2.join_keys(fk_values)] = E2.from_id(fk_values, @meta) if fk_values.length > 0
664
+ action.invoke()
665
+
666
+ clean: ->
667
+ delete @decode
668
+ @clear_record()
669
+
670
+ typeahead: class TypeAheadAction extends DecodeAction
671
+ initialize: ->
672
+ super()
673
+ @if_fk_values (fk_values) =>
674
+ @invoke(id: E2.join_keys(fk_values)).then =>
675
+ if @entry
676
+ @decode = id: E2.id_for(@entry, @meta), value: @decode_description(@entry)
677
+
678
+ @scope().$on "$typeahead.select", (e, v, index) =>
679
+ e.stopPropagation()
680
+ _(@dinfo.fields).zip(E2.split_keys(@values[index].id)).each(([fk, k]) => @record()[fk] = E2.parse_entry(k, @parentp().meta.info[fk])).value
681
+ @parentp().search_live?(@decode_field)
682
+
683
+ @scope().$watch "action.decode", (e) => if e?
684
+ @reset() if e.length == 0
685
+
686
+ load: (value) ->
687
+ if value? && value.length > 0 && @key_pressed # check again after strap updates ?
688
+ @invoke(query: value).then =>
689
+ if @entries # ?
690
+ @values = @entries.map (e) => id: E2.id_for(e, @meta), value: @decode_description(e)
691
+ delete @entries
692
+ @values
693
+
694
+ clean: ->
695
+ delete @decode
696
+ @clear_record()
697
+
698
+ many_to_one_list: class ManyToOneListAction extends ListAction
699
+ initialize: ->
700
+ super()
701
+ @selection = {}
702
+
703
+ select: (index, ev) ->
704
+ if ev.target.nodeName == "TD"
705
+ @selection = {} unless @parent().multiple
706
+ super(index, ev)
707
+ unless @parent().multiple
708
+ @scope().$emit "picked", @selection, @meta
709
+ @panel_close()
710
+
711
+ panel_menu_choose: (e) ->
712
+ # if _.size(@selection) > 0
713
+ @scope().$emit "picked", @selection, @meta
714
+ @panel_close()
715
+
716
+ star_to_many_list: class StarToManyList extends ListAction
717
+ initialize: ->
718
+ super()
719
+ @query.parent_id = @parent().current_id
720
+
721
+ # link_list: implicit
722
+ item_menu_confirm_unlink: (index) ->
723
+ @invoke_action('confirm_unlink', id: E2.id_for(@entries[index], @meta), parent_id: @query.parent_id)
724
+
725
+ star_to_many_bulk_unlink: class StarToManyBulkUnlinkAction extends Action
726
+ invoke: ->
727
+ parent = @parent().parent()
728
+ super(ids: [_.keys(parent.selection)], parent_id: parent.query.parent_id).then =>
729
+ parent.selection = {}
730
+
731
+ star_to_many_link_list: class StarToManyLinkList extends ListAction
732
+ initialize: ->
733
+ super()
734
+ @query.negate = true
735
+ @query.parent_id = @parent().query.parent_id
736
+ @selection = {}
737
+
738
+ panel_menu_link: ->
739
+ selection = _.keys(@selection)
740
+ if selection.length > 0
741
+ @invoke_action('link', parent_id: @query.parent_id, ids: selection).then (act) =>
742
+ unless @errors
743
+ @parent().invoke()
744
+ @panel_close()
745
+
746
+ star_to_many_field: class StarToManyField extends ListAction
747
+ initialize: ->
748
+ super()
749
+ @query.parent_id = E2.id_for(@parent().record, @parent().meta)
750
+ links = @parent().record[@scope().$parent.f]
751
+ @links = links ? (linked: [], unlinked: [])
752
+ @invoke()
753
+
754
+ invoke: ->
755
+ @query.unlinked = [@links.unlinked]
756
+ @query.linked = [@links.linked]
757
+ super()
758
+
759
+ sync_record: ->
760
+ @parent().record[@scope().$parent.f] = @links
761
+
762
+ star_to_many_field_link_list: class StarToManyFieldLinkList extends ListAction
763
+ initialize: ->
764
+ super()
765
+ @query.negate = true
766
+ @query.parent_id = @parent().query.parent_id
767
+ @selection = {}
768
+
769
+ invoke: ->
770
+ @query.unlinked = [@parent().links.unlinked]
771
+ @query.linked = [@parent().links.linked]
772
+ super()
773
+
774
+ panel_menu_link: ->
775
+ if @selected_size() > 0
776
+ _.each @selection, (v, k) =>
777
+ id = k
778
+ if _.includes(@parent().links.unlinked, id) then _.pull(@parent().links.unlinked, id) else @parent().links.linked.push id
779
+ @parent().invoke()
780
+ @parent().sync_record()
781
+ @panel_close()
782
+
783
+ star_to_many_field_unlink: class StarToManyFieldUnlink extends Action
784
+ invoke: (arg) ->
785
+ id = arg.id
786
+ pparent = @parent().parent()
787
+ if _.includes(pparent.links.linked, id) then _.pull(pparent.links.linked, id) else pparent.links.unlinked.push id
788
+ pparent.sync_record()
789
+
790
+ file_store: class FileStoreAction extends Action
791
+ initialize: ->
792
+ super()
793
+ @progress = 0
794
+ id = E2.id_for(@parent().record, @parent().meta)
795
+ if id.length > 0
796
+ @invoke(owner: id).then => @sync_record()
797
+ else
798
+ @files = []
799
+ @sync_record()
800
+
801
+ sync_record: ->
802
+ @parent().record[@scope().f] = @files
803
+
804
+ select: (files) ->
805
+ _.each files, (file) =>
806
+ upload = $injector.get('Upload').upload url: "#{@action_info().action_resource}/upload", file: file
807
+ upload.progress (e) =>
808
+ @progress = parseInt(100.0 * e.loaded / e.total)
809
+ # @parent().action_pending = true
810
+ upload.success (data, status, headers, config) =>
811
+ @files.push mime: file.type, name: file.name, rackname: data.response.rackname, id: data.response.id
812
+ @message = "Wysłano, #{file.name}"
813
+ # @parent().action_pending = false
814
+ @sync_record()
815
+
816
+ delete_file: (file) ->
817
+ @scope().$broadcast 'confirm_delete',
818
+ confirm: =>
819
+ @sync_record()
820
+ file.deleted = true
821
+ @scope().$broadcast 'confirm_delete_close'
822
+
823
+ show_file: (file) ->
824
+ @file = file
825
+ @scope().$broadcast 'show_file', file: file
826
+
827
+ blob_store: class BlobStore extends Action
828
+ initialize: ->
829
+ super()
830
+ @record_id = E2.id_for(@parent().record, @parent().meta)
831
+ @progress = 0
832
+ if @record_id.length > 0
833
+ @invoke(owner: @record_id) # .then => @sync_record()
834
+ else
835
+ # @file = {}
836
+ # @sync_record()
837
+
838
+ sync_record: ->
839
+ @parent().record[@scope().f] = @file
840
+
841
+ select: (files) ->
842
+ _.each files, (file) =>
843
+ upload = $injector.get('Upload').upload url: "#{@action_info().action_resource}/upload", file: file
844
+ upload.progress (e) =>
845
+ @progress = parseInt(100.0 * e.loaded / e.total)
846
+ upload.success (data, status, headers, config) =>
847
+ @file = mime: file.type, name: file.name, rackname: data.response.rackname, id: data.response.id
848
+ @message = "Wysłano, #{file.name}"
849
+ @sync_record()