ovto 0.5.0 → 0.6.2

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/CHANGELOG.md +14 -2
  4. data/Gemfile +2 -3
  5. data/Gemfile.lock +23 -27
  6. data/book/SUMMARY.md +2 -0
  7. data/book/api/actions.md +1 -1
  8. data/book/api/middleware.md +99 -0
  9. data/book/api/pure_component.md +3 -0
  10. data/docs/api/Array.html +190 -0
  11. data/docs/api/Hash.html +196 -0
  12. data/docs/api/Ovto/Actions.html +128 -40
  13. data/docs/api/Ovto/App.html +254 -40
  14. data/docs/api/Ovto/Component/MoreThanOneNode.html +6 -6
  15. data/docs/api/Ovto/Component.html +96 -29
  16. data/docs/api/Ovto/Middleware/Actions.html +428 -0
  17. data/docs/api/Ovto/Middleware/Base.html +605 -0
  18. data/docs/api/Ovto/Middleware/Component.html +354 -0
  19. data/docs/api/Ovto/Middleware.html +286 -0
  20. data/docs/api/Ovto/PureComponent/StateIsNotAvailable.html +6 -6
  21. data/docs/api/Ovto/PureComponent.html +11 -11
  22. data/docs/api/Ovto/Runtime.html +7 -7
  23. data/docs/api/Ovto/State/MissingValue.html +6 -6
  24. data/docs/api/Ovto/State/{UnknownKey.html → UnknownStateKey.html} +11 -11
  25. data/docs/api/Ovto/State.html +59 -43
  26. data/docs/api/Ovto/WiredActionSet.html +636 -0
  27. data/docs/api/Ovto/WiredActions.html +78 -35
  28. data/docs/api/Ovto.html +223 -30
  29. data/docs/api/_index.html +77 -11
  30. data/docs/api/actions.html +29 -3
  31. data/docs/api/app.html +28 -2
  32. data/docs/api/class_list.html +3 -3
  33. data/docs/api/component.html +30 -4
  34. data/docs/api/css/style.css +3 -2
  35. data/docs/api/fetch.html +30 -4
  36. data/docs/api/file.README.html +76 -78
  37. data/docs/api/file_list.html +2 -2
  38. data/docs/api/frames.html +2 -2
  39. data/docs/api/index.html +76 -78
  40. data/docs/api/js/app.js +14 -3
  41. data/docs/api/method_list.html +303 -31
  42. data/docs/api/middleware.html +498 -0
  43. data/docs/api/pure_component.html +428 -0
  44. data/docs/api/state.html +28 -2
  45. data/docs/api/top-level-namespace.html +11 -9
  46. data/docs/guides/debugging.html +28 -2
  47. data/docs/guides/development.html +28 -2
  48. data/docs/guides/install.html +28 -2
  49. data/docs/guides/tutorial.html +28 -2
  50. data/docs/index.html +28 -2
  51. data/docs/search_index.json +1 -1
  52. data/examples/sinatra/Gemfile +2 -0
  53. data/examples/sinatra/Gemfile.lock +32 -30
  54. data/examples/sinatra/app.rb +1 -4
  55. data/examples/sinatra/config.ru +6 -6
  56. data/examples/static/Gemfile.lock +9 -9
  57. data/lib/ovto/actions.rb +11 -0
  58. data/lib/ovto/app.rb +37 -13
  59. data/lib/ovto/component.rb +130 -67
  60. data/lib/ovto/middleware.rb +121 -0
  61. data/lib/ovto/pure_component.rb +1 -1
  62. data/lib/ovto/state.rb +13 -5
  63. data/lib/ovto/version.rb +1 -1
  64. data/lib/ovto/wired_action_set.rb +40 -0
  65. data/lib/ovto/wired_actions.rb +49 -11
  66. data/lib/ovto.rb +26 -0
  67. data/ovto.gemspec +1 -1
  68. metadata +21 -11
  69. data/docs/api/pure_component.md +0 -27
@@ -4,3 +4,5 @@ gem 'opal-sprockets'
4
4
  gem 'sinatra'
5
5
  gem 'sinatra-contrib'
6
6
  gem 'rack'
7
+ gem 'rake'
8
+ gem 'puma'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- ovto (0.4.1)
4
+ ovto (0.6.0)
5
5
  opal (>= 0.11, < 2)
6
6
  rack (~> 2.0)
7
7
  thor (~> 0.20)
@@ -9,44 +9,44 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- ast (2.4.0)
13
- backports (3.13.0)
14
- concurrent-ruby (1.1.5)
15
- hike (1.2.3)
16
- multi_json (1.13.1)
17
- mustermann (1.0.3)
18
- opal (0.11.4)
12
+ ast (2.4.2)
13
+ concurrent-ruby (1.1.9)
14
+ multi_json (1.15.0)
15
+ mustermann (1.1.1)
16
+ ruby2_keywords (~> 0.0.1)
17
+ nio4r (2.5.8)
18
+ opal (1.2.0)
19
19
  ast (>= 2.3.0)
20
- hike (~> 1.2)
21
- parser (= 2.3.3.1)
22
- sourcemap (~> 0.1.0)
23
- opal-sprockets (0.4.3.0.11.0.3.7)
24
- opal (~> 0.11.0)
25
- sprockets (~> 3.7)
20
+ parser (~> 3.0)
21
+ opal-sprockets (1.0.2)
22
+ opal (>= 1.0, < 2.0)
23
+ sprockets (~> 4.0)
26
24
  tilt (>= 1.4)
27
- parser (2.3.3.1)
28
- ast (~> 2.2)
29
- rack (2.0.8)
30
- rack-protection (2.0.5)
25
+ parser (3.0.2.0)
26
+ ast (~> 2.4.1)
27
+ puma (5.5.2)
28
+ nio4r (~> 2.0)
29
+ rack (2.2.3)
30
+ rack-protection (2.1.0)
31
31
  rack
32
- sinatra (2.0.5)
32
+ rake (13.0.6)
33
+ ruby2_keywords (0.0.5)
34
+ sinatra (2.1.0)
33
35
  mustermann (~> 1.0)
34
- rack (~> 2.0)
35
- rack-protection (= 2.0.5)
36
+ rack (~> 2.2)
37
+ rack-protection (= 2.1.0)
36
38
  tilt (~> 2.0)
37
- sinatra-contrib (2.0.5)
38
- backports (>= 2.8.2)
39
+ sinatra-contrib (2.1.0)
39
40
  multi_json
40
41
  mustermann (~> 1.0)
41
- rack-protection (= 2.0.5)
42
- sinatra (= 2.0.5)
43
- tilt (>= 1.3, < 3)
44
- sourcemap (0.1.1)
45
- sprockets (3.7.2)
42
+ rack-protection (= 2.1.0)
43
+ sinatra (= 2.1.0)
44
+ tilt (~> 2.0)
45
+ sprockets (4.0.2)
46
46
  concurrent-ruby (~> 1.0)
47
47
  rack (> 1, < 3)
48
48
  thor (0.20.3)
49
- tilt (2.0.9)
49
+ tilt (2.0.10)
50
50
 
51
51
  PLATFORMS
52
52
  ruby
@@ -54,9 +54,11 @@ PLATFORMS
54
54
  DEPENDENCIES
55
55
  opal-sprockets
56
56
  ovto!
57
+ puma
57
58
  rack
59
+ rake
58
60
  sinatra
59
61
  sinatra-contrib
60
62
 
61
63
  BUNDLED WITH
62
- 2.0.1
64
+ 2.2.22
@@ -3,16 +3,13 @@ require 'sinatra/reloader'
3
3
  require 'opal'
4
4
 
5
5
  class SinatraApp < Sinatra::Base
6
- # Proc to generate javascript include tag (see config.ru)
7
- set :generate_javascript_include_tag, nil
8
-
9
6
  configure(:development) do
10
7
  register Sinatra::Reloader
11
8
  also_reload "#{__dir__}/**/*.rb"
12
9
  end
13
10
 
14
11
  get '/' do
15
- @js_tag = options.generate_javascript_include_tag
12
+ @js_tag = $GENERATE_JAVASCRIPT_INCLUDE_TAG.call # Defined in config.ru
16
13
  erb :index # Render views/index.erb
17
14
  end
18
15
  end
@@ -2,20 +2,20 @@ require 'opal/sprockets'
2
2
  require_relative './app.rb'
3
3
 
4
4
  Opal.use_gem 'ovto'
5
- opal = Opal::Sprockets::Server.new {|s|
5
+ opal_server = Opal::Sprockets::Server.new {|s|
6
6
  s.append_path './ovto/'
7
7
  s.main = 'app'
8
8
  }
9
9
 
10
- sprockets = opal.sprockets
10
+ sprockets = opal_server.sprockets
11
11
  prefix = '/assets'
12
12
 
13
13
  map prefix do
14
14
  run sprockets
15
15
  end
16
16
 
17
- run Sinatra.new(SinatraApp){
18
- set :generate_javascript_include_tag, proc{
19
- ::Opal::Sprockets.javascript_include_tag('app', sprockets: sprockets, prefix: prefix, debug: true)
20
- }
17
+ $GENERATE_JAVASCRIPT_INCLUDE_TAG = ->{
18
+ ::Opal::Sprockets.javascript_include_tag('app', sprockets: sprockets, prefix: prefix, debug: true)
21
19
  }
20
+
21
+ run SinatraApp
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- ovto (0.4.1)
4
+ ovto (0.6.0)
5
5
  opal (>= 0.11, < 2)
6
6
  rack (~> 2.0)
7
7
  thor (~> 0.20)
@@ -9,15 +9,15 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- ast (2.4.0)
12
+ ast (2.4.2)
13
13
  ifchanged (1.0.1)
14
- opal (1.0.2)
14
+ opal (1.2.0)
15
15
  ast (>= 2.3.0)
16
- parser (~> 2.6)
17
- parser (2.6.5.0)
18
- ast (~> 2.4.0)
19
- rack (2.0.8)
20
- rake (12.3.2)
16
+ parser (~> 3.0)
17
+ parser (3.0.2.0)
18
+ ast (~> 2.4.1)
19
+ rack (2.2.3)
20
+ rake (13.0.6)
21
21
  thor (0.20.3)
22
22
 
23
23
  PLATFORMS
@@ -29,4 +29,4 @@ DEPENDENCIES
29
29
  rake
30
30
 
31
31
  BUNDLED WITH
32
- 2.0.1
32
+ 2.2.22
data/lib/ovto/actions.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module Ovto
2
2
  # Base class for ovto actions.
3
3
  class Actions
4
+ # WiredActions must be set after initialization
5
+ # (this cannot be an argument of #initialize because Actions and
6
+ # WiredActions have references to each other)
4
7
  attr_writer :wired_actions
5
8
 
6
9
  def actions
@@ -10,5 +13,13 @@ module Ovto
10
13
  def state
11
14
  @wired_actions._app.state
12
15
  end
16
+
17
+ def middleware_name
18
+ WiredActionSet::I_AM_APP_NOT_A_MIDDLEWARE
19
+ end
20
+
21
+ def middleware_path
22
+ []
23
+ end
13
24
  end
14
25
  end
data/lib/ovto/app.rb CHANGED
@@ -1,18 +1,37 @@
1
1
  module Ovto
2
2
  class App
3
+ # List of installed middleware classes
4
+ def self.middlewares
5
+ @middlewares ||= []
6
+ end
7
+
3
8
  # Create an App and start it
4
9
  def self.run(*args)
5
10
  new.run(*args)
6
11
  end
7
12
 
13
+ # Install a middleware
14
+ def self.use(middleware_class)
15
+ self.middlewares.push(middleware_class)
16
+ end
17
+
8
18
  def initialize
9
- @state = self.class.const_get('State').new
10
- @wired_actions = nil
19
+ app_state_class = self.class.const_get('State')
20
+ # Inject middleware states
21
+ app_state_class.item :_middlewares, default_proc: ->{
22
+ Ovto::Middleware.create_middleware_states_class(self.class.middlewares).new
23
+ }
24
+ @state = app_state_class.new
25
+ @wired_action_set = nil
26
+ @main_component = nil
11
27
  end
12
28
  attr_reader :state
13
29
 
30
+ # An instance of YourApp::MainComponent (mainly for testing)
31
+ attr_reader :main_component
32
+
14
33
  def actions
15
- @wired_actions
34
+ @wired_action_set.app_wired_actions
16
35
  end
17
36
 
18
37
  # Internal use only
@@ -36,9 +55,9 @@ module Ovto
36
55
  def _run(id: nil)
37
56
  runtime = Ovto::Runtime.new(self)
38
57
  actions = self.class.const_get('Actions').new
39
- @wired_actions = WiredActions.new(actions, self, runtime)
40
- actions.wired_actions = @wired_actions
41
- view = create_view
58
+ @wired_action_set = WiredActionSet.new(self, actions, [], self.class.middlewares, runtime)
59
+ actions.wired_actions = @wired_action_set.app_wired_actions
60
+ @main_component = create_view(@wired_action_set)
42
61
  if id
43
62
  %x{
44
63
  document.addEventListener('DOMContentLoaded', function(){
@@ -46,16 +65,16 @@ module Ovto
46
65
  if (!container) {
47
66
  throw "Ovto::App#run: tag with id='" + id + "' was not found";
48
67
  }
49
- #{start_application(runtime, view, `container`)}
68
+ #{start_application(runtime, `container`)}
50
69
  });
51
70
  }
52
71
  else
53
- start_application(runtime, view, nil)
72
+ start_application(runtime, nil)
54
73
  end
55
74
  end
56
75
 
57
76
  # Instantiate MyApp::MainComponent
58
- def create_view
77
+ def create_view(wired_action_set)
59
78
  begin
60
79
  main_component_class = self.class.const_get('MainComponent')
61
80
  rescue NameError => orig_ex
@@ -68,12 +87,17 @@ module Ovto
68
87
  "#{self.class}::View to #{self.class}::MainComponent"
69
88
  end
70
89
  end
71
- return main_component_class.new(@wired_actions)
90
+ return main_component_class.new(wired_action_set)
72
91
  end
73
92
 
74
- def start_application(runtime, view, container)
75
- runtime.run(view, container)
76
- setup
93
+ def start_application(runtime, container)
94
+ Ovto.log_error {
95
+ runtime.run(@main_component, container)
96
+ setup
97
+ self.class.middlewares.each do |m|
98
+ m._run_setup(@wired_action_set[m.name])
99
+ end
100
+ }
77
101
  end
78
102
  end
79
103
  end
@@ -13,10 +13,16 @@ module Ovto
13
13
  ret
14
14
  end
15
15
 
16
- def initialize(wired_actions)
17
- @wired_actions = wired_actions
16
+ # (internal) Defined for convenience
17
+ def self.middleware_name
18
+ WiredActionSet::I_AM_APP_NOT_A_MIDDLEWARE
19
+ end
20
+
21
+ def initialize(wired_action_set, middleware_path=[])
22
+ @wired_action_set = wired_action_set || WiredActionSet.dummy
23
+ @middleware_path = middleware_path
18
24
  # Initialize here for the unit tests
19
- @vdom_tree = []
25
+ @vdom_stack = [[]]
20
26
  @components = []
21
27
  @components_index = 0
22
28
  end
@@ -26,7 +32,7 @@ module Ovto
26
32
  end
27
33
 
28
34
  def state
29
- @wired_actions._app.state
35
+ @wired_action_set.app.state
30
36
  end
31
37
 
32
38
  private
@@ -37,35 +43,35 @@ module Ovto
37
43
  do_render({}, state)
38
44
  end
39
45
 
40
- def do_render(args, state)
46
+ # Call #render to generate VDom
47
+ def do_render(args, state, &block)
41
48
  Ovto.debug_trace_log("rendering #{self}")
42
- @vdom_tree = []
49
+ @vdom_stack = [[]]
43
50
  @components_index = 0
44
51
  @done_render = false
45
52
  @current_state = state
46
- parameters = method(:render).parameters
47
- if `!parameters` || parameters.nil? || accepts_state?(parameters)
48
- # We can pass `state:` safely
49
- args_with_state = {state: @current_state}.merge(args)
50
- return render(args_with_state)
53
+ rendered = Ovto.log_error {
54
+ Ovto.send_args_with_state(self, :render, args, state, &block)
55
+ }
56
+ case rendered
57
+ when String
58
+ return rendered
59
+ when Array
60
+ if rendered.length > 1
61
+ raise MoreThanOneNode
62
+ end
63
+ raise "rendered is empty" if rendered.length == 0
64
+ return rendered[0]
51
65
  else
52
- # Check it is empty (see https://github.com/opal/opal/issues/1872)
53
- return args.empty? ? render() : render(**args)
54
- end
55
- end
56
-
57
- # Return true if the method accepts `state:` keyword
58
- def accepts_state?(parameters)
59
- parameters.each do |item|
60
- return true if item == [:key, :state] ||
61
- item == [:keyreq, :state] ||
62
- item[0] == :keyrest
66
+ console.error("#render returned unknown value", rendered)
67
+ raise "#render returned unknown value"
63
68
  end
64
- return false
65
69
  end
66
70
 
67
71
  def actions
68
- @wired_actions
72
+ return @middleware_path.inject(@wired_action_set){|wa_set, middleware_name|
73
+ wa_set[middleware_name]
74
+ }[WiredActionSet::THE_MIDDLEWARE_ITSELF]
69
75
  end
70
76
 
71
77
  # o 'div', 'Hello.'
@@ -78,31 +84,39 @@ module Ovto
78
84
  # o 'h1', 'Hello.'
79
85
  # end
80
86
  # o 'div', `{nodeName: ....}` # Inject VDom spec directly
87
+ # o SubComponentClass
88
+ # o SubComponentClass do ... end # Ovto passes the block to SubComponent#render
81
89
  def o(_tag_name, arg1=nil, arg2=nil, &block)
82
- if native?(arg1)
90
+ if native?(arg1) # Embed VDom directly
83
91
  attributes = {}
84
92
  content = arg1
85
- elsif arg1.is_a?(Hash)
93
+ elsif arg1.is_a?(Hash) # Has attributes
86
94
  attributes = arg1
87
95
  content = arg2
88
- elsif arg2 == nil
96
+ elsif arg2 == nil # Has content instead of attributes, or both are nil
89
97
  attributes = {}
90
98
  content = arg1
91
99
  else
92
100
  raise ArgumentError
93
101
  end
94
102
 
95
- children = render_children(content, block)
96
103
  case _tag_name
97
104
  when Class
98
- result = render_component(_tag_name, attributes, children)
105
+ if content
106
+ raise ArgumentError, "use a block to pass content to sub component"
107
+ end
108
+ result = render_component(_tag_name, attributes, &block)
99
109
  when 'text'
100
110
  unless attributes.empty?
101
111
  raise ArgumentError, "text cannot take attributes"
102
112
  end
103
113
  result = content
104
114
  when String
115
+ children = render_children(content, block)
105
116
  tag_name, base_attributes = *extract_attrs(_tag_name)
117
+ if tag_name == "textarea" && (content || block)
118
+ raise ArgumentError, "Use `value:` to specify content of a textarea"
119
+ end
106
120
  # Ignore nil/false
107
121
  more_attributes = attributes.reject{|k, v| !v}
108
122
  result = render_tag(tag_name, merge_attrs(base_attributes, more_attributes), children)
@@ -110,16 +124,8 @@ module Ovto
110
124
  raise TypeError, "tag_name must be a String or Component but got "+
111
125
  Ovto.inspect(tag_name)
112
126
  end
113
- if @vdom_tree.empty?
114
- if @done_render
115
- raise MoreThanOneNode, "#{self.class}#render must generate a single DOM node. Please wrap the tags with a 'div' or something."
116
- end
117
- @done_render = true
118
- return result
119
- else
120
- @vdom_tree.last.push(result)
121
- return @vdom_tree.last
122
- end
127
+ @vdom_stack.last.push(result)
128
+ return @vdom_stack.last
123
129
  end
124
130
 
125
131
  def extract_attrs(tag_name)
@@ -164,37 +170,52 @@ module Ovto
164
170
  [content.to_s]
165
171
  end
166
172
  when block
167
- @vdom_tree.push []
168
- block_value = block.call
169
- results = @vdom_tree.pop
170
- if results.length > 0 # 'o' was called at least once
171
- results
172
- elsif native?(block_value)
173
- # Inject VDom tree written in JS object
174
- # eg. Embed markdown
175
- [block_value]
176
- elsif block_value.is_a?(String)
177
- # When 'o' is never called in the child block, use the last value
178
- # eg.
179
- # o 'span' do
180
- # 'Hello' #=> This will be the content of the span tag
181
- # end
182
- [block_value]
183
- else
184
- # o 'div' do
185
- # # When items is `[]`, 'o' is never called and `block_value` will be `[]`
186
- # items.each{ o 'div', '...' }
187
- # end
188
- []
189
- end
173
+ render_block(block)
190
174
  else
191
175
  []
192
176
  end
193
177
  end
194
178
 
195
- def render_component(comp_class, args, children)
179
+ def render_block(block)
180
+ @vdom_stack.push []
181
+ orig_depth = @vdom_stack.length
182
+ block_value = block.call
183
+ @vdom_stack = @vdom_stack.first(orig_depth)
184
+ results = @vdom_stack.pop
185
+
186
+ if results.length > 0 # 'o' was called at least once
187
+ results
188
+ elsif native?(block_value)
189
+ # Inject VDom tree written in JS object
190
+ # eg. Embed markdown
191
+ [block_value]
192
+ elsif block_value.is_a?(String)
193
+ # When 'o' is never called in the child block, use the last value
194
+ # eg.
195
+ # o 'span' do
196
+ # 'Hello' #=> This will be the content of the span tag
197
+ # end
198
+ [block_value]
199
+ elsif block_value.is_a?(Array)
200
+ # Case 1
201
+ # o "div", &block
202
+ # Case 2
203
+ # items = []
204
+ # o 'div' do items.each{ o ... } end # == o 'div' do [] end
205
+ block_value
206
+ else
207
+ console.error("Invalid block_value:", Ovto.inspect(block_value))
208
+ raise "Invalid block value"
209
+ end
210
+ end
211
+
212
+ # Instantiate component and call its #render to get VDom
213
+ def render_component(comp_class, args, &block)
196
214
  comp = new_component(comp_class)
197
- return comp.do_render(args, @current_state){ children }
215
+ orig_stack, @vdom_stack = @vdom_stack, [[]]
216
+ ret = comp.do_render(args, @current_state, &block)
217
+ @vdom_stack = orig_stack
218
+ ret
198
219
  end
199
220
 
200
221
  def new_component(comp_class)
@@ -204,14 +225,56 @@ module Ovto
204
225
  return comp
205
226
  end
206
227
 
207
- comp = @components[@components_index] = comp_class.new(@wired_actions)
228
+ middleware_path = new_middleware_path(comp_class)
229
+ comp = @components[@components_index] = comp_class.new(@wired_action_set, middleware_path)
208
230
  @components_index += 1
209
231
  comp
210
232
  end
211
233
 
234
+ # Make new middleware_path by adding comp_class
235
+ def new_middleware_path(comp_class)
236
+ mw_name = comp_class.middleware_name
237
+ if (idx = @middleware_path.index(mw_name))
238
+ # eg. suppose OvtoIde uses OvtoWindow
239
+ # class CompI < OvtoIde::Component
240
+ # def render
241
+ # o Window do
242
+ # o AnotherComponentOfOvtoIde
243
+ # end
244
+ # end
245
+ # end
246
+ # class Window < OvtoWindow::Component
247
+ # def render(&block)
248
+ # o ".window", &block
249
+ # end
250
+ # end
251
+ # Rendering order:
252
+ # 1. CompI (ovto_ide)
253
+ # 2. Window (ovto_window)
254
+ # 3. AnotherComponentOfOvtoIde (ovto_ide again)
255
+ @middleware_path[0..idx]
256
+ else
257
+ if comp_class.middleware_name == WiredActionSet::I_AM_APP_NOT_A_MIDDLEWARE
258
+ @middleware_path
259
+ else
260
+ @middleware_path + [comp_class.middleware_name]
261
+ end
262
+ end
263
+ # TODO: it would be nice if we could raise an error when comp_class
264
+ # is invalid middleware (i.e. not use'd)
265
+ end
266
+
212
267
  def render_tag(tag_name, attributes, children)
213
- js_attributes = Component.hash_to_js_obj(attributes || {})
214
- if (style = attributes['style'])
268
+ attributes_ = attributes.map{|k, v|
269
+ if k.start_with?("on")
270
+ # Inject log_error to event handlers
271
+ [k, ->(e){ Ovto.log_error{ v.call(e) }}]
272
+ else
273
+ [k, v]
274
+ end
275
+ }.to_h
276
+ js_attributes = Component.hash_to_js_obj(attributes_ || {})
277
+ if (style = attributes_['style'])
215
278
  `js_attributes.style = #{Component.hash_to_js_obj(style)}`
216
279
  end
217
280
  children ||= `null`