engine2 1.0.8 → 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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -3
  3. data/app/actions.coffee +87 -50
  4. data/app/app.css +2 -1
  5. data/app/engine2.coffee +37 -19
  6. data/app/modal.coffee +8 -3
  7. data/bower.json +3 -2
  8. data/conf/message.yaml +3 -0
  9. data/conf/message_pl.yaml +5 -2
  10. data/config.coffee +16 -15
  11. data/engine2.gemspec +1 -1
  12. data/lib/engine2/action.rb +51 -23
  13. data/lib/engine2/action/array.rb +94 -1
  14. data/lib/engine2/action/decode.rb +29 -3
  15. data/lib/engine2/action/delete.rb +1 -2
  16. data/lib/engine2/action/infra.rb +5 -4
  17. data/lib/engine2/action/list.rb +70 -25
  18. data/lib/engine2/action/save.rb +0 -2
  19. data/lib/engine2/action_node.rb +2 -4
  20. data/lib/engine2/core.rb +30 -21
  21. data/lib/engine2/handler.rb +5 -5
  22. data/lib/engine2/model.rb +18 -9
  23. data/lib/engine2/models/Files.rb +2 -0
  24. data/lib/engine2/scheme.rb +1 -6
  25. data/lib/engine2/templates.rb +32 -6
  26. data/lib/engine2/type_info.rb +9 -5
  27. data/lib/engine2/version.rb +1 -1
  28. data/package.json +9 -8
  29. data/views/fields/checkbox_button.slim +6 -0
  30. data/views/fields/checkbox_buttons.slim +1 -1
  31. data/views/fields/list_mbuttons.slim +9 -0
  32. data/views/fields/list_mbuttons_opt.slim +11 -0
  33. data/views/fields/list_mselect.slim +12 -0
  34. data/views/fields/scaffold.slim +1 -1
  35. data/views/fields/typeahead_picker.slim +8 -5
  36. data/views/infra/inspect.slim +19 -16
  37. data/views/scaffold/fields.slim +3 -1
  38. data/views/scaffold/search.slim +2 -2
  39. data/views/scaffold/search_collapse.slim +2 -2
  40. data/views/scaffold/search_tabs.slim +2 -2
  41. data/views/search_fields/date.slim +20 -0
  42. data/views/search_fields/list_mbuttons.slim +12 -0
  43. data/views/search_fields/typeahead_picker.slim +4 -3
  44. metadata +11 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7de9cf2c556b8224ab4bdf70f0fd45ddb3f2f5b54dac443eac49184fc553633
4
- data.tar.gz: b515c7f5b0af25479c54fa6e9459a1484efbd99ce270e78ba206f7e11480ca67
3
+ metadata.gz: cc5654aaf1573375a69ca96d3d957b07e1970ef777e0deb65c18f28c40666489
4
+ data.tar.gz: aa79d8a00f564a0e23e4520ea1198446a6049c241b5bdb714416aa7ed378de6d
5
5
  SHA512:
6
- metadata.gz: d8ff090077415370c9c1d885aa791462a2b44a69c4794be276016665e6d743ef15de579781becbb77ab02f43e17ed8bda3e1965b63052c237778bb5b230e10f9
7
- data.tar.gz: 7cf93650cf3a106d54c95df0a49c7dd8a49e491f2e9185a45b8e14fa1577af77fc808b464c3c7e84ffaa2c2a20179d1c81f3cdd000987c8b4f3892e940b39f5a
6
+ metadata.gz: 271c8416bd7551d08fa8574319be948aa9b8658fc431e432c65a19deb456deead3fc92e213259e2f9ad0783a77099a8dbce13c460fe45d715abab7bf09efc392
7
+ data.tar.gz: b65bd605e3d622586f433d5900d9da115e880e62a0827913f03c2e18dc9ce744fb6ebaa88f4a31e3582251c8551be9b71a0f03299498d1042082dd3eeb0f3074
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
13
  open("app/templates.js", "wb") << <<-EOF
14
- angular.module('Engine2').run(['$templateCache', function($templateCache) {
14
+ angular.module('Engine2').run(['$templateCache', function(c) {
15
15
  #{slims.join("\n")}
16
16
  }]);
17
17
  EOF
@@ -51,9 +51,16 @@ 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
+
54
58
  @websocket_connect() if @meta.websocket
55
59
  @initialize()
56
60
 
61
+ broadcast: (sub_action, args) ->
62
+ @scope().$broadcast(@action_info().action_resource + '/' + sub_action, args)
63
+
57
64
  initialize: ->
58
65
  @process_static_meta()
59
66
  console.info "CREATE #{@action_info().action_resource}"
@@ -68,7 +75,13 @@ angular.module('Engine2')
68
75
  if action_info.access
69
76
  $rootScope.$broadcast "relogin", element?, create
70
77
  else
71
- @globals().modal().error("#{err.status}: #{err.data.message}", err.data.cause || err.data.message)
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)
84
+
72
85
  $q.reject(err)
73
86
 
74
87
  save_state: () ->
@@ -100,28 +113,35 @@ angular.module('Engine2')
100
113
 
101
114
  create_action_path: (action_names, sc, elem) ->
102
115
  last_name = action_names.pop()
103
- _.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 = @
104
117
  act.create_action(last_name, sc, elem).then (act) -> sc.action = act
105
118
 
106
119
  globals: -> globals
120
+ _ : -> _
121
+ current: -> @globals().current_action
107
122
  action_pending: -> globals.action_pending == @
108
123
  pre_invoke: ->
109
124
  post_invoke: ->
110
125
 
111
126
  invoke: (params) ->
127
+ @globals().current_action = @
112
128
  params ?= {}
113
129
  @globals().action_pending = if @meta.panel then @ else @parent()
114
130
  @pre_invoke(params)
115
- _.merge(params, @meta.arguments) if @meta.arguments
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
134
+
116
135
 
117
136
  info = @action_info()
118
137
  get_invoke = if @meta.invokable == false then $q.when(data: (response: {})) else
119
138
  params.initial = true if @meta.panel && !@action_invoked && info.method == 'get'
120
139
  $http[info.method](info.action_resource, if info.method == 'post' then params else (params: params))
121
140
 
141
+ @execute_commands('pre_execute')
122
142
  get_invoke.then (response) =>
123
143
  @arguments = _.keys(response.data)
124
- E2.merge(@, response.data)
144
+ E2.merge_meta(@, response.data)
125
145
  @process_meta()
126
146
 
127
147
  promise = if @meta.panel # persistent action
@@ -131,20 +151,22 @@ angular.module('Engine2')
131
151
  else
132
152
  prnt = @parent()
133
153
  throw "Attempted parent merge for root action: #{info.name}" unless prnt
134
- E2.merge(prnt, response.data)
154
+ E2.merge_meta(prnt, response.data)
135
155
 
136
156
  @post_invoke(params)
137
- @execute_commands() if @meta.execute
157
+ @execute_commands('execute')
138
158
  if @meta.repeat
139
159
  @scope().$on "$destroy", => @destroyed = true
140
160
  $timeout (=> @invoke(params)), @meta.repeat unless @destroyed
141
161
  delete @meta.repeat
142
162
 
143
163
  @globals().action_pending = false
164
+ @globals().current_action = null
144
165
  promise
145
166
  ,
146
167
  (err) =>
147
168
  @globals().action_pending = false
169
+ @globals().current_action = null
148
170
  @handle_error(err, info, @element())
149
171
 
150
172
  panel_render: ->
@@ -203,26 +225,28 @@ angular.module('Engine2')
203
225
  websocket_connect: ->
204
226
  l = $location
205
227
  ws_meta = @meta.websocket
206
- ws = $websocket "ws#{l.protocol().slice(4, 5)}://#{l.host()}:#{l.port()}/#{@action_info().action_resource}", undefined, ws_meta.options
228
+ ws = $websocket "ws#{l.protocol().slice(4, 5)}://#{l.host()}:#{l.port()}#{'/'}#{@action_info().action_resource}", undefined, ws_meta.options
207
229
  _.each @globals().ws_methods, (method) =>
208
230
  ws_method_impl = @["ws_#{method}"]
209
231
  ws["on#{_.capitalize(method)}"] (evt) =>
210
232
  if method == 'message'
211
233
  msg = JSON.parse(evt.data)
212
234
  if msg.error then @globals().modal().error("WebSocket [#{evt.origin}] - #{msg.error.method}", msg.error.exception) else
213
- E2.merge(@, msg)
235
+ E2.merge_meta(@, msg)
214
236
  @process_meta()
215
237
  else msg = evt
216
238
  ws_method_impl.bind(@)(msg, ws, evt) if ws_method_impl
217
- @execute_commands() if @meta.execute
218
- delete @meta.execute
239
+ @execute_commands('execute')
219
240
 
220
241
  @web_socket = -> ws
221
242
  @scope().$on "$destroy", -> ws.close()
222
243
 
223
- execute_commands: ->
224
- scope = @scope()
225
- _.reduce(@meta.execute, ((pr, cmd) -> pr.then -> scope.$eval(cmd)), $q.when())
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]
226
250
 
227
251
  console_log: (o) ->
228
252
  console.log o
@@ -303,7 +327,7 @@ angular.module('Engine2')
303
327
  $urlRouter.otherwise(otherwise)
304
328
  @register(menu.entries)
305
329
  @scope().routes = menu.entries
306
- out = if _.size(menu.entries) == 0 then angular.element("<div></div>") else $compile(@traverse(menu.entries))(@scope())
330
+ out = $compile(@traverse(menu.entries))(@scope())
307
331
  @element().replaceWith(out)
308
332
  @element = -> out
309
333
  loc = $location.path().slice(1)
@@ -331,7 +355,9 @@ angular.module('Engine2')
331
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>")
332
356
  animation = @meta.menus.menu.properties.animation
333
357
  out = routes.map (route, i) ->
334
- if route.menu
358
+ if route.render == false
359
+ ''
360
+ else if route.menu
335
361
  menu_sub_tmpl
336
362
  dropdown: "routes[#{i}].menu.entries"
337
363
  animation: animation
@@ -346,7 +372,8 @@ angular.module('Engine2')
346
372
  show: route.show && "ng-show=\"#{route.show}\"" || ''
347
373
  hide: route.hide && "ng-hide=\"#{route.hide}\"" || ''
348
374
  icon: route.icon && E2.icon(route.icon) || ''
349
- out.join('')
375
+ out = out.join('')
376
+ if _.size(out) == 0 then "<div></div>" else out
350
377
 
351
378
  list: class ListAction extends Action
352
379
  initialize: ->
@@ -391,7 +418,7 @@ angular.module('Engine2')
391
418
  menu_show_meta: ->
392
419
  @globals().modal().show
393
420
  the_meta: @meta
394
- 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)
395
422
 
396
423
  # show_assoc: (index, assoc) ->
397
424
  # # parent_id = E2.id_for(@entries[index], @meta)
@@ -453,12 +480,12 @@ angular.module('Engine2')
453
480
  search_field_change: (f) ->
454
481
  info = @meta.fields[f]
455
482
 
456
- @scope().$eval(info.onchange) if info.onchange
483
+ @scope().$eval(info.onchange.action) if info.onchange
457
484
 
458
485
  if remote_onchange = info.remote_onchange
459
486
  params = value: @query.search[f]
460
- params.record = @query.search if info.remote_onchange_record
461
- @invoke_action(remote_onchange, params).then =>
487
+ params.record = @query.search if remote_onchange.record
488
+ @invoke_action(remote_onchange.action, params).then =>
462
489
  @load_new() if info.search_live
463
490
  else
464
491
  @load_new() if info.search_live
@@ -490,6 +517,11 @@ angular.module('Engine2')
490
517
  entry_moved: (index) ->
491
518
  @moved_from = index
492
519
 
520
+ list_parent_action: ->
521
+ parent = @parent()
522
+ parent = parent.parent() until parent instanceof ListAction
523
+ parent
524
+
493
525
  bulk_delete: class BulkDeleteAction extends Action
494
526
  invoke: ->
495
527
  super(ids: [_.keys(@parent().parent().selection)]).then =>
@@ -502,17 +534,6 @@ angular.module('Engine2')
502
534
  form_base_action: class FormBaseAction extends Action
503
535
  initialize: ->
504
536
  super()
505
- _.each @meta.fields, (info, name) =>
506
- if info.remote_onchange
507
- @scope().$watch (=> @record?[name]), (n) => if n? #if typeof(n) != "undefined"
508
- params = value: @record[name]
509
- params.record = @record if info.remote_onchange_record
510
- @invoke_action(info.remote_onchange, params)
511
-
512
- if info.onchange
513
- @scope().$watch (=> @record?[name]), (n) => if n?
514
- @scope().$eval(info.onchange)
515
-
516
537
  if @meta.tab_list
517
538
  @scope().$watch "action.activeTab", (tab) => if tab? # && tab >= 0
518
539
  @panel_shown()
@@ -522,12 +543,26 @@ angular.module('Engine2')
522
543
  post_invoke: (args) ->
523
544
  super()
524
545
  _.each @meta.fields, (info, name) =>
525
- if _.isString(@record[name]) && !info.dont_strip
546
+ if @record[name] is undefined
547
+ @record[name] = null
548
+ else if _.isString(@record[name]) && !info.dont_strip
526
549
  @record[name] = @record[name].trim()
527
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
+
528
565
  panel_menu_default_action: ->
529
- _.each @meta.fields, (v, n) =>
530
- @record[n] = null if @record[n] is undefined
531
566
  params = record: @record
532
567
  params.parent_id ?= @parent().query?.parent_id # and StarToManyList ?
533
568
  @invoke_action(@default_action_name, params).then =>
@@ -734,29 +769,31 @@ angular.module('Engine2')
734
769
  typeahead: class TypeAheadAction extends DecodeAction
735
770
  initialize: ->
736
771
  super()
737
- @if_fk_values (fk_values) =>
738
- @invoke(id: E2.join_keys(fk_values)).then =>
739
- if @entry
740
- @decode = id: E2.id_for(@entry, @meta), value: @decode_description(@entry)
741
-
742
772
  @scope().$on "$typeahead.select", (e, v, index) =>
743
773
  e.stopPropagation()
744
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()
745
776
  @parentp().search_field_change?(@decode_field)
746
777
 
747
- @scope().$watch "action.decode", (e) => if e?
748
- @reset() if e.length == 0
778
+ @scope().$watch "action.decode", (e) => @reset() if e == null
779
+
780
+ @if_fk_values (fk_values) =>
781
+ @invoke(id: E2.join_keys(fk_values)).then =>
782
+ if @entry
783
+ @decode = id: E2.id_for(@entry, @meta), value: @decode_description(@entry)
784
+
785
+ @decode = '' unless @decode?
786
+ # @dinfo.render.min_length == 0
749
787
 
750
788
  load: (value) ->
751
- if value? && value.length > 0 && @key_pressed # check again after strap updates ?
752
- @invoke(query: value).then =>
753
- if @entries # ?
754
- @values = @entries.map (e) => id: E2.id_for(e, @meta), value: @decode_description(e)
755
- delete @entries
756
- @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
757
794
 
758
795
  clean: ->
759
- delete @decode
796
+ @decode = ''
760
797
  @clear_record()
761
798
 
762
799
  many_to_one_list: class ManyToOneListAction extends ListAction
@@ -780,7 +817,7 @@ angular.module('Engine2')
780
817
  star_to_many_list: class StarToManyList extends ListAction
781
818
  initialize: ->
782
819
  super()
783
- @query.parent_id = @parent().current_id()
820
+ @query.parent_id = @list_parent_action().current_id()
784
821
 
785
822
  # link_list: implicit
786
823
  item_menu_confirm_unlink: (args) ->
@@ -860,7 +897,7 @@ angular.module('Engine2')
860
897
  else
861
898
  pparent.changes.modify.push @parent().record
862
899
  else # CreateAction
863
- _(@parent().meta.primary_fields).each (k) => @parent().record[k] = E2.uuid(5)
900
+ _(@parent().meta.primary_fields).each (k) => @parent().record[k] = E2.uuid()
864
901
  if draggable = pparent.meta.draggable
865
902
  max = _.maxBy(pparent.entries, (e) -> e.position)
866
903
  @parent().record[draggable.position_field] = if max then max[draggable.position_field] + 1 else 1
@@ -62,7 +62,8 @@ tr.tr_hover {
62
62
  }
63
63
 
64
64
  textarea {
65
- white-space: nowrap;
65
+ /*white-space: nowrap;*/
66
+ white-space: pre-wrap;
66
67
  }
67
68
 
68
69
  /*.modal-backdrop{
@@ -13,7 +13,7 @@ require 'angular-drag-and-drop-lists'
13
13
 
14
14
  _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
15
15
 
16
- angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStrap', 'ngFileUpload', 'ui.tree', 'LocalStorageModule', 'angularLoad', 'ngWebSocket', 'ui.router', 'dndLists']) # 'ui.select'
16
+ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStrap', 'ngFileUpload', 'ui.tree', 'LocalStorageModule', 'angularLoad', 'ngWebSocket', 'ui.router', 'dndLists', 'toastr']) # 'ui.select'
17
17
  .config ($httpProvider, $compileProvider, localStorageServiceProvider, $logProvider, $qProvider, $locationProvider, $provide) ->
18
18
  $httpProvider.interceptors.push 'e2HttpInterceptor'
19
19
  $provide.decorator '$httpBackend', ($delegate) ->
@@ -44,6 +44,9 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
44
44
  $delegate
45
45
 
46
46
  .factory 'PushJS', -> require 'push.js'
47
+ .factory 'PrettyYAML', -> require 'json-to-pretty-yaml'
48
+ .filter 'yaml', (PrettyYAML) -> (input) -> PrettyYAML.stringify(input, 4)
49
+
47
50
  .factory 'MetaCache', ($cacheFactory) -> $cacheFactory('MetaCache')
48
51
  .factory 'e2HttpInterceptor', ($q, $injector, E2Snippets) ->
49
52
  loaderToggle = (toggle) -> angular.element(document.querySelectorAll('.loader')).eq(-1).css("visibility", toggle)
@@ -97,7 +100,7 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
97
100
  _.each(o.class, (v, k) -> out.push "'#{k}': #{v}") if o.class?
98
101
  if out.length > 0 then "ng-class=\"{#{out.join(',')}}\"" else ""
99
102
 
100
- .factory 'E2', ($templateCache, $http, E2Snippets, $q, $dateFormatter, $parse, PushJS, $state, $e2Modal) ->
103
+ .factory 'E2', ($templateCache, $http, E2Snippets, $q, $dateFormatter, $parse, PushJS, $state, $e2Modal, toastr) ->
101
104
  globals:
102
105
  element: (id) ->
103
106
  element = document.querySelector(id)
@@ -107,11 +110,12 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
107
110
  notification: (name, body, icon, timeoutx, on_close) ->
108
111
  PushJS.create name, body: body, icon: icon, timeout: timeoutx, onClick: on_close
109
112
 
113
+ toastr: -> toastr
110
114
  state: -> $state
111
115
  modal: -> $e2Modal
112
116
 
113
117
  uuid: (length) ->
114
- Math.random().toString(36).substring(length)
118
+ Math.random().toString(10).substr(2, 8)
115
119
 
116
120
  compact: (o) ->
117
121
  _.each o, (v, k) =>
@@ -125,17 +129,19 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
125
129
  else if _.isObject(v) && !_.isDate(v)
126
130
  @clean(v)
127
131
  else
128
- # delete o[k]
129
- o[k] = null
132
+ o[k] = null # delete o[k]
130
133
 
131
- merge: (dst, src) ->
134
+ merge_meta: (dst, src) ->
132
135
  for k, v of src
133
136
  throw "Attempted to override function '#{k}'" if _.isFunction(dst[k])
134
- insn = k.slice(-1)
135
- if _.isObject(v) && !_.isArray(v)
136
- if insn == '!' then dst[k.slice(0, -1)] = v else dst[k] = @merge(dst[k] ? {}, v)
137
+ if (k == 'execute' || k == 'pre_execute') && dst[k] && src[k]
138
+ dst[k] = dst[k].concat(src[k])
137
139
  else
138
- if insn == '?' then dst[k.slice(0, -1)] ?= v else dst[k] = v
140
+ insn = k.slice(-1)
141
+ if _.isObject(v) && !_.isArray(v)
142
+ if insn == '!' then dst[k.slice(0, -1)] = v else dst[k] = @merge_meta(dst[k] ? {}, v)
143
+ else
144
+ if insn == '?' then dst[k.slice(0, -1)] ?= v else dst[k] = v
139
145
  dst
140
146
 
141
147
  transpose: (a) ->
@@ -289,7 +295,7 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
289
295
  if elem[0].type in ['text', 'password']
290
296
  elem.on 'keypress', (ev) ->
291
297
  scope.$apply ->
292
- scope.action.panel_menu_default_action() if ev.keyCode == 13
298
+ scope.action.panel_menu_default_action?() if ev.keyCode == 13
293
299
 
294
300
  ev.stopPropagation()
295
301
 
@@ -337,9 +343,13 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
337
343
  scope: false
338
344
  restrict: 'A'
339
345
  link: (scope, elem, attrs) ->
346
+ table_scope = null
340
347
  scope.$on 'render_table', (a, ev) ->
341
348
  # ev.stopPropagation()
342
- action = scope.action
349
+ table_scope.$destroy() if table_scope
350
+ table_scope = scope.$new(false)
351
+
352
+ action = table_scope.action
343
353
  meta = action.meta
344
354
  draggable = meta.draggable
345
355
  position = meta.menus.item_menu.properties.position ? 0
@@ -382,7 +392,7 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
382
392
 
383
393
  tbody_attrs = if draggable then 'dnd-list=\"action.entries\" dnd-drop=\"action.entry_dropped(index, external, type)\"' else ''
384
394
  elem.empty()
385
- elem.append($compile(table_tmpl thead: thead, tbody: tbody, tbody_attrs: tbody_attrs)(scope))
395
+ elem.append($compile(table_tmpl thead: thead, tbody: tbody, tbody_attrs: tbody_attrs)(table_scope))
386
396
 
387
397
  .directive 'e2Dropdown', ($parse, $dropdown, $timeout, E2Snippets) ->
388
398
  event_num = 0
@@ -466,7 +476,7 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
466
476
  hide: m.hide && "ng-hide=\"#{m.hide}\"" || ''
467
477
  icon: m.icon && E2Snippets.icon(m.icon) || ''
468
478
  loc: !(m.button_loc == false) && m.loc || ''
469
- title: (m.button_loc == false) && "title=\"#{m.loc}\"" || ''
479
+ title: if m.title then "title=\"#{m.title}\"" else ((m.button_loc == false) && "title=\"#{m.loc}\"" || '')
470
480
 
471
481
  if menu.entries.length > brk
472
482
  out += button_set_arr_tmpl
@@ -516,27 +526,35 @@ angular.module('Engine2', ['ngSanitize', 'ngAnimate', 'ngCookies', 'mgcrea.ngStr
516
526
  when "decimal_date"
517
527
  match = value.match(/^(\d{4}|\d{2})(\d{2})(\d{2})$/)
518
528
  if match then match.slice(1, 4).join('-') else value
519
- else value
529
+ else
530
+ match = value.match(/(.*)(?:\s[-+]\d+)$/)
531
+ if match then match[1] else value
520
532
 
521
533
  scope: true
522
534
  require: 'ngModel'
523
535
  link: (scope, element, attr, controller) ->
524
536
  action = scope.action
525
537
  mode = attr.e2Datepicker
538
+ has_mode = !_.isEmpty(mode)
526
539
  field = scope.other_date ? scope.other_time ? scope.f
527
540
  info = action.meta.fields[field]
528
541
 
529
542
  if action.query
530
- scope.value[mode] = parse(action.query.search[scope.f][mode], info)
531
- scope.$on "search_reset", -> scope.value[mode] = null
543
+ f = action.query.search[scope.f]
544
+ if has_mode
545
+ scope.value[mode] = parse(f[mode], info)
546
+ scope.$on "search_reset", -> scope.value[mode] = null
547
+ else
548
+ scope.value.at = parse(f, info)
549
+ scope.$on "search_reset", -> scope.value.at = null
532
550
  else
533
551
  value = parse(action.record[field], info)
534
- if mode then scope.value[mode] = value else scope.value = value
552
+ if has_mode then scope.value[mode] = value else scope.value = value
535
553
 
536
554
  scope.$watch attr.ngModel, (model, o) -> if model != o
537
555
  date = format(model, attr.e2ModelFormat)
538
556
  if action.query
539
- action.query.search[scope.f][mode] = date
557
+ if has_mode then action.query.search[scope.f][mode] = date else action.query.search[scope.f] = date
540
558
  scope.action.search_field_change(scope.f) if date?
541
559
  else
542
560
  action.record[field] = date