engine2 1.0.8 → 1.0.9

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